2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
9 package eu
.etaxonomy
.cdm
.cache
;
11 import java
.beans
.PropertyDescriptor
;
12 import java
.io
.PrintStream
;
13 import java
.lang
.reflect
.InvocationTargetException
;
14 import java
.util
.ArrayList
;
15 import java
.util
.Collection
;
16 import java
.util
.HashMap
;
17 import java
.util
.HashSet
;
18 import java
.util
.List
;
22 import org
.apache
.commons
.beanutils
.BeanUtilsBean
;
23 import org
.apache
.commons
.beanutils
.PropertyUtils
;
24 import org
.apache
.commons
.beanutils
.PropertyUtilsBean
;
25 import org
.apache
.commons
.lang3
.builder
.HashCodeBuilder
;
26 import org
.apache
.logging
.log4j
.LogManager
;
27 import org
.apache
.logging
.log4j
.Logger
;
28 import org
.hibernate
.Hibernate
;
29 import org
.hibernate
.collection
.internal
.AbstractPersistentCollection
;
30 import org
.hibernate
.envers
.internal
.entities
.mapper
.relation
.lazy
.proxy
.CollectionProxy
;
31 import org
.hibernate
.envers
.internal
.entities
.mapper
.relation
.lazy
.proxy
.MapProxy
;
32 import org
.hibernate
.envers
.internal
.entities
.mapper
.relation
.lazy
.proxy
.SortedMapProxy
;
34 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
35 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
36 import eu
.etaxonomy
.cdm
.model
.common
.VersionableEntity
;
37 import eu
.etaxonomy
.cdm
.model
.reference
.INomenclaturalReference
;
38 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
41 * @author a.kohlbecker
44 public class CdmEntityCache
implements EntityCache
{
46 private static final Logger logger
= LogManager
.getLogger();
49 protected static final String COPY_ENTITY
= "!";
51 protected Set
<CdmBase
> entities
= new HashSet
<>();
53 private Map
<EntityKey
, CdmBase
> entityyMap
= new HashMap
<>();
55 private List
<String
> entityPathList
= new ArrayList
<>();
57 private Map
<EntityKey
, List
<String
>> entityPathsMap
= new HashMap
<>();
59 private Set
<EntityKey
> copyEntitiyKeys
= new HashSet
<>();
61 private Set
<Object
> objectsSeen
= new HashSet
<>();
63 protected CdmEntityCache(){
68 * @param entity the first entity to be cached. can be <code>null</code>.
69 * Further entities can be added to the cache with {@link CdmEntityCache#add(CdmBase)}
71 public CdmEntityCache(CdmBase entity
){
73 this.entities
.add(entity
);
79 public boolean update() {
81 entityPathList
.clear();
82 entityPathsMap
.clear();
84 copyEntitiyKeys
.clear();
86 for(CdmBase entity
: entities
){
87 analyzeEntity(entity
, "");
90 return copyEntitiyKeys
.isEmpty();
96 protected void analyzeEntity(CdmBase bean
, String propertyPath
) {
102 CdmBase proxyBean
= bean
;
104 bean
= HibernateProxyHelper
.deproxy(proxyBean
, CdmBase
.class);
106 EntityKey entityKey
= new EntityKey(bean
);
108 propertyPath
+= "[" + entityKey
;
110 CdmBase mappedEntity
= entityyMap
.put(entityKey
, proxyBean
);
112 if(mappedEntity
!= null && mappedEntity
!= bean
) {
113 copyEntitiyKeys
.add(entityKey
);
114 flags
+= COPY_ENTITY
+ bean
.hashCode();
117 flags
= analyzeMore(bean
, entityKey
, flags
, mappedEntity
);
119 if(!flags
.isEmpty()){
120 propertyPath
+= "(" + flags
+ ")";
124 logger
.debug(propertyPath
);
126 entityPathList
.add(propertyPath
);
127 if(!entityPathsMap
.containsKey(entityKey
)){
128 entityPathsMap
.put(entityKey
, new ArrayList
<>());
130 entityPathsMap
.get(entityKey
).add(propertyPath
);
132 if(!objectsSeen
.add(bean
)){
133 // avoid cycles, do not recurse into properties of objects that have been analyzed already
137 Set
<PropertyDescriptor
> properties
= AbstractBeanInitializer
.getProperties(bean
, null);
138 for(PropertyDescriptor prop
: properties
){
141 Object propertyValue
= PropertyUtils
.getProperty(bean
, prop
.getName());
143 if(propertyValue
== null){
147 String propertyPathSuffix
= "." + prop
.getName();
148 logger
.debug("\t\tproperty:" + propertyPathSuffix
);
150 if(Hibernate
.isInitialized(propertyValue
)) {
152 if(CdmBase
.class.isAssignableFrom(prop
.getPropertyType())
153 || INomenclaturalReference
.class.equals(prop
.getPropertyType())
155 analyzeEntity(HibernateProxyHelper
.deproxy(propertyValue
, CdmBase
.class), propertyPath
+ propertyPathSuffix
);
159 Collection
<CdmBase
> collection
= null;
160 if(propertyValue
instanceof AbstractPersistentCollection
){
161 if (propertyValue
instanceof Collection
) {
162 collection
= (Collection
<CdmBase
>) propertyValue
;
163 } else if (propertyValue
instanceof Map
) {
164 collection
= ((Map
<?
,CdmBase
>)propertyValue
).values();
166 logger
.error("unhandled subtype of AbstractPersistentCollection");
168 } else if (propertyValue
instanceof CollectionProxy
169 || propertyValue
instanceof MapProxy
<?
, ?
>
170 || propertyValue
instanceof SortedMapProxy
<?
, ?
>){
171 //hibernate envers collections
172 // FIXME this won't work!!!!
173 collection
= (Collection
<CdmBase
>)propertyValue
;
176 if(collection
!= null){
177 for(CdmBase collectionItem
: collection
){
178 analyzeEntity(HibernateProxyHelper
.deproxy(collectionItem
, CdmBase
.class), propertyPath
+ propertyPathSuffix
);
181 // logger.error("Unhandled property type " + propertyValue.getClass().getName());
185 } catch (IllegalAccessException e
) {
186 String message
= "Illegal access on property " + prop
;
187 logger
.error(message
);
188 throw new RuntimeException(message
, e
);
189 } catch (InvocationTargetException e
) {
190 String message
= "Cannot invoke property " + prop
+ " not found";
191 logger
.error(message
);
192 throw new RuntimeException(message
, e
);
193 } catch (NoSuchMethodException e
) {
194 String message
= "Property " + prop
.getName() + " not found for class " + bean
.getClass();
195 logger
.error(message
);
202 * Empty method which can be implemented by subclasses which do further analysis.
204 protected String
analyzeMore(CdmBase bean
, EntityKey entityKey
, String flags
, CdmBase mappedEntity
) {
208 public void printEntityGraph(PrintStream printStream
){
209 printLegend(printStream
);
210 for(String path
: entityPathList
) {
211 printStream
.println(path
);
215 public void printCopyEntities(PrintStream printStream
){
216 printStream
.println("-------------- Copy Entities --------------");
217 printLegend(printStream
);
218 for(EntityKey key
: copyEntitiyKeys
){
219 for(String path
: entityPathsMap
.get(key
)) {
220 printStream
.println(path
);
228 protected void printLegend(PrintStream printStream
) {
229 printStream
.println(this.getClass().getSimpleName() + " legend: ");
230 printStream
.println(" - '!{objectHash}': detected copy entity, followed by object hash");
233 public class EntityKey
{
238 public EntityKey(Class type
, int id
){
243 public EntityKey(CdmBase entity
){
244 type
= entity
.getClass();
248 public Class
getType() {
257 public int hashCode() {
258 return new HashCodeBuilder(15, 33)
265 public boolean equals(Object obj
) {
266 EntityKey other
= (EntityKey
)obj
;
267 return this.id
== other
.id
&& this.type
== other
.type
;
272 public String
toString() {
273 return type
.getSimpleName() + "#" + getId();
280 * @return the entities in this cache
282 public Set
<CdmBase
> getEntities(){
289 * In case the cached bean is a HibernateProxy it will be unproxied
290 * before returning it.
293 public <CDM
extends CdmBase
> CDM
find(CDM value
) {
295 EntityKey entityKey
= new EntityKey(HibernateProxyHelper
.deproxy(value
));
296 CDM cachedBean
= (CDM
) entityyMap
.get(entityKey
);
297 if(cachedBean
!= null){
298 return (CDM
) HibernateProxyHelper
.deproxy(cachedBean
, CdmBase
.class);
304 private <CDM
extends CdmBase
> CDM
findProxy(CDM value
) {
306 EntityKey entityKey
= new EntityKey(HibernateProxyHelper
.deproxy(value
));
307 return (CDM
) entityyMap
.get(entityKey
);
315 * In case the cached bean is a HibernateProxy it will be unproxied
316 * before returning it.
319 public <CDM
extends CdmBase
> CDM
find(Class
<CDM
> type
, int id
) {
320 EntityKey entityKey
= new EntityKey(type
, id
);
321 CDM cachedBean
= (CDM
) entityyMap
.get(entityKey
);
322 if(cachedBean
!= null){
323 return (CDM
) HibernateProxyHelper
.deproxy(cachedBean
, CdmBase
.class);
329 public <CDM
extends CdmBase
> CDM
findAndUpdate(CDM value
){
330 CDM cachedBean
= findProxy(value
);
331 if(cachedBean
!= null && VersionableEntity
.class.isAssignableFrom(cachedBean
.getClass())){
332 updatedCachedIfEarlier((VersionableEntity
)cachedBean
, (VersionableEntity
)value
);
334 if(cachedBean
!= null){
335 return (CDM
) HibernateProxyHelper
.deproxy(cachedBean
, CdmBase
.class);
345 private <CDM
extends VersionableEntity
> void updatedCachedIfEarlier(CDM cachedValue
, CDM value
) {
346 if(cachedValue
!= null && value
!= null && value
.getUpdated() != null){
347 if(cachedValue
.getUpdated() == null || value
.getUpdated().isAfter(cachedValue
.getUpdated())){
349 copyProperties(cachedValue
, value
);
350 } catch (IllegalAccessException e
) {
351 /* should never happen */
353 } catch (InvocationTargetException e
) {
354 /* critical! re-throw as runtime exception, which is ok in the context of a vaadin app */
355 throw new RuntimeException(e
);
363 * partially copy of {@link BeanUtilsBean#copyProperties(Object, Object)}
365 private <CDM
extends VersionableEntity
> void copyProperties(CDM dest
, CDM orig
) throws IllegalAccessException
, InvocationTargetException
{
367 PropertyUtilsBean propertyUtils
= BeanUtilsBean
.getInstance().getPropertyUtils();
368 PropertyDescriptor
[] origDescriptors
=
369 propertyUtils
.getPropertyDescriptors(orig
);
370 for (int i
= 0; i
< origDescriptors
.length
; i
++) {
371 String name
= origDescriptors
[i
].getName();
372 if ("class".equals(name
)) {
373 continue; // No point in trying to set an object's class
375 if (propertyUtils
.isReadable(orig
, name
) &&
376 propertyUtils
.isWriteable(dest
, name
)) {
378 if(CdmBase
.class.isAssignableFrom(propertyUtils
.getPropertyType(dest
, name
))){
379 CdmBase origValue
= (CdmBase
)propertyUtils
.getSimpleProperty(orig
, name
);
380 if(!Hibernate
.isInitialized(origValue
)){
381 // ignore uninitialized entities
384 // only copy entities if origValue either is a completely different entity (A)
385 // or if origValue is updated (B).
386 CdmBase destValue
= (CdmBase
)propertyUtils
.getSimpleProperty(dest
, name
);
387 if(destValue
== null && origValue
== null){
391 origValue
!= null && destValue
== null ||
392 origValue
== null && destValue
!= null ||
394 origValue
.getId() != destValue
.getId() ||
395 origValue
.getClass() != destValue
.getClass() ||
396 origValue
.getUuid() != destValue
.getUuid()){
398 BeanUtilsBean
.getInstance().copyProperty(dest
, name
, origValue
);
401 // (B) recurse into findAndUpdate
402 findAndUpdate(origValue
);
405 Object value
= propertyUtils
.getSimpleProperty(orig
, name
);
406 BeanUtilsBean
.getInstance().copyProperty(dest
, name
, value
);
409 } catch (NoSuchMethodException e
) {
418 public <CDM
extends CdmBase
> void add(CDM value
) {
420 analyzeEntity(value
, "");