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
.lang
.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.
207 * @param mappedEntity
210 protected String
analyzeMore(CdmBase bean
, EntityKey entityKey
, String flags
, CdmBase mappedEntity
) {
215 public void printEntityGraph(PrintStream printStream
){
216 printLegend(printStream
);
217 for(String path
: entityPathList
) {
218 printStream
.println(path
);
222 public void printCopyEntities(PrintStream printStream
){
223 printStream
.println("-------------- Copy Entities --------------");
224 printLegend(printStream
);
225 for(EntityKey key
: copyEntitiyKeys
){
226 for(String path
: entityPathsMap
.get(key
)) {
227 printStream
.println(path
);
235 protected void printLegend(PrintStream printStream
) {
236 printStream
.println(this.getClass().getSimpleName() + " legend: ");
237 printStream
.println(" - '!{objectHash}': detected copy entity, followed by object hash");
240 public class EntityKey
{
245 public EntityKey(Class type
, int id
){
250 public EntityKey(CdmBase entity
){
251 type
= entity
.getClass();
258 public Class
getType() {
273 public int hashCode() {
274 return new HashCodeBuilder(15, 33)
281 public boolean equals(Object obj
) {
282 EntityKey other
= (EntityKey
)obj
;
283 return this.id
== other
.id
&& this.type
== other
.type
;
288 public String
toString() {
289 return type
.getSimpleName() + "#" + getId();
296 * @return the entities in this cache
298 public Set
<CdmBase
> getEntities(){
305 * In case the cached bean is a HibernateProxy it will be unproxied
306 * before returning it.
309 public <CDM
extends CdmBase
> CDM
find(CDM value
) {
311 EntityKey entityKey
= new EntityKey(HibernateProxyHelper
.deproxy(value
));
312 CDM cachedBean
= (CDM
) entityyMap
.get(entityKey
);
313 if(cachedBean
!= null){
314 return (CDM
) HibernateProxyHelper
.deproxy(cachedBean
, CdmBase
.class);
320 private <CDM
extends CdmBase
> CDM
findProxy(CDM value
) {
322 EntityKey entityKey
= new EntityKey(HibernateProxyHelper
.deproxy(value
));
323 return (CDM
) entityyMap
.get(entityKey
);
331 * In case the cached bean is a HibernateProxy it will be unproxied
332 * before returning it.
335 public <CDM
extends CdmBase
> CDM
find(Class
<CDM
> type
, int id
) {
336 EntityKey entityKey
= new EntityKey(type
, id
);
337 CDM cachedBean
= (CDM
) entityyMap
.get(entityKey
);
338 if(cachedBean
!= null){
339 return (CDM
) HibernateProxyHelper
.deproxy(cachedBean
, CdmBase
.class);
345 public <CDM
extends CdmBase
> CDM
findAndUpdate(CDM value
){
346 CDM cachedBean
= findProxy(value
);
347 if(cachedBean
!= null && VersionableEntity
.class.isAssignableFrom(cachedBean
.getClass())){
348 updatedCachedIfEarlier((VersionableEntity
)cachedBean
, (VersionableEntity
)value
);
350 if(cachedBean
!= null){
351 return (CDM
) HibernateProxyHelper
.deproxy(cachedBean
, CdmBase
.class);
361 private <CDM
extends VersionableEntity
> void updatedCachedIfEarlier(CDM cachedValue
, CDM value
) {
362 if(cachedValue
!= null && value
!= null && value
.getUpdated() != null){
363 if(cachedValue
.getUpdated() == null || value
.getUpdated().isAfter(cachedValue
.getUpdated())){
365 copyProperties(cachedValue
, value
);
366 } catch (IllegalAccessException e
) {
367 /* should never happen */
369 } catch (InvocationTargetException e
) {
370 /* critical! re-throw as runtime exception, which is ok in the context of a vaadin app */
371 throw new RuntimeException(e
);
379 * partially copy of {@link BeanUtilsBean#copyProperties(Object, Object)}
381 private <CDM
extends VersionableEntity
> void copyProperties(CDM dest
, CDM orig
) throws IllegalAccessException
, InvocationTargetException
{
383 PropertyUtilsBean propertyUtils
= BeanUtilsBean
.getInstance().getPropertyUtils();
384 PropertyDescriptor
[] origDescriptors
=
385 propertyUtils
.getPropertyDescriptors(orig
);
386 for (int i
= 0; i
< origDescriptors
.length
; i
++) {
387 String name
= origDescriptors
[i
].getName();
388 if ("class".equals(name
)) {
389 continue; // No point in trying to set an object's class
391 if (propertyUtils
.isReadable(orig
, name
) &&
392 propertyUtils
.isWriteable(dest
, name
)) {
394 if(CdmBase
.class.isAssignableFrom(propertyUtils
.getPropertyType(dest
, name
))){
395 CdmBase origValue
= (CdmBase
)propertyUtils
.getSimpleProperty(orig
, name
);
396 if(!Hibernate
.isInitialized(origValue
)){
397 // ignore uninitialized entities
400 // only copy entities if origValue either is a completely different entity (A)
401 // or if origValue is updated (B).
402 CdmBase destValue
= (CdmBase
)propertyUtils
.getSimpleProperty(dest
, name
);
403 if(destValue
== null && origValue
== null){
407 origValue
!= null && destValue
== null ||
408 origValue
== null && destValue
!= null ||
410 origValue
.getId() != destValue
.getId() ||
411 origValue
.getClass() != destValue
.getClass() ||
412 origValue
.getUuid() != destValue
.getUuid()){
414 BeanUtilsBean
.getInstance().copyProperty(dest
, name
, origValue
);
417 // (B) recurse into findAndUpdate
418 findAndUpdate(origValue
);
421 Object value
= propertyUtils
.getSimpleProperty(orig
, name
);
422 BeanUtilsBean
.getInstance().copyProperty(dest
, name
, value
);
425 } catch (NoSuchMethodException e
) {
437 public <CDM
extends CdmBase
> void add(CDM value
) {
439 analyzeEntity(value
, "");