3 * Copyright (C) 2015 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.taxeditor
.remoting
.cache
;
12 import java
.lang
.reflect
.Field
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Collection
;
15 import java
.util
.HashMap
;
16 import java
.util
.Iterator
;
17 import java
.util
.List
;
20 import net
.sf
.ehcache
.Cache
;
21 import net
.sf
.ehcache
.CacheManager
;
22 import net
.sf
.ehcache
.Element
;
24 import org
.apache
.log4j
.Logger
;
25 import org
.hibernate
.collection
.spi
.PersistentCollection
;
26 import org
.hibernate
.proxy
.HibernateProxy
;
27 import org
.hibernate
.proxy
.LazyInitializer
;
28 import org
.springframework
.util
.ReflectionUtils
;
30 import eu
.etaxonomy
.cdm
.api
.cache
.CdmCacher
;
31 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
38 public class EntityCacherDebugResult
{
40 private static final Logger logger
= Logger
.getLogger(EntityCacherDebugResult
.class);
42 private Map
<CdmEntityInfo
, CdmEntityInfo
> duplicateCdmEntityMap
;
44 private List
<CdmEntityInfo
> notInCacheList
;
46 private CdmTransientEntityCacher cacher
;
48 private List
<CdmEntityInfo
> rootElements
;
50 StringBuilder debugOutput
= new StringBuilder();
52 public EntityCacherDebugResult() {
56 public <T
extends CdmBase
> EntityCacherDebugResult(CdmTransientEntityCacher cacher
, Collection
<T
> rootEntities
) {
60 if(rootEntities
!= null && !rootEntities
.isEmpty()) {
61 for(CdmBase rootEntity
: rootEntities
) {
62 debug(rootEntity
, true);
63 String out
= toString(duplicateCdmEntityMap
, notInCacheList
, rootEntity
);
64 System
.out
.println(out
);
65 debugOutput
.append(out
);
73 duplicateCdmEntityMap
= new HashMap
<CdmEntityInfo
, CdmEntityInfo
>();
74 notInCacheList
= new ArrayList
<CdmEntityInfo
>();
75 rootElements
= new ArrayList
<CdmEntityInfo
>();
78 private void clear() {
79 duplicateCdmEntityMap
.clear();
80 notInCacheList
.clear();
83 public void addDuplicateEntity(CdmEntityInfo cei
, CdmEntityInfo cachedCei
) {
84 duplicateCdmEntityMap
.put(cei
, cachedCei
);
87 public void addEntityNotInCache(CdmEntityInfo cei
) {
88 notInCacheList
.add(cei
);
91 public List
<CdmEntityInfo
> getRootElements() {
95 private void print(Map
<CdmEntityInfo
, CdmEntityInfo
> duplicateCdmEntityMap
,
96 List
<CdmEntityInfo
> notInCacheList
,
98 System
.out
.println(toString(duplicateCdmEntityMap
, notInCacheList
, rootEntity
));
103 public String
toString() {
104 return debugOutput
.toString();
107 private String
toString(Map
<CdmEntityInfo
, CdmEntityInfo
> duplicateCdmEntityMap
,
108 List
<CdmEntityInfo
> notInCacheList
,
109 CdmBase rootEntity
) {
112 StringBuilder sb
= new StringBuilder();
113 sb
.append(System
.getProperty("line.separator"));
114 sb
.append("<<< Root Entity " + rootEntity
.getUserFriendlyTypeName() + " with id " + rootEntity
.getId() + " >>>");
115 sb
.append(System
.getProperty("line.separator"));
116 if(duplicateCdmEntityMap
.isEmpty()) {
117 sb
.append("No Duplicate CDM Entities.");
119 sb
.append("Duplicate CDM Entities,");
121 for (Map
.Entry
<CdmEntityInfo
, CdmEntityInfo
> entry
: duplicateCdmEntityMap
.entrySet())
123 sb
.append(System
.getProperty("line.separator"));
124 CdmEntityInfo cei
= entry
.getKey();
125 CdmBase cb
= (CdmBase
) cei
.getObject();
126 Object cbParent
= cei
.getParent().getObject();
129 sb
.append(" - " + cei
.getField().getName() + ":" + cb
.getUserFriendlyTypeName() + "/" + cb
.getId() +
130 " in entity " + cbParent
.getClass().getCanonicalName());
131 if(cbParent
instanceof CdmBase
) {
132 sb
.append(" with id : " + ((CdmBase
)cbParent
).getId());
134 sb
.append(System
.getProperty("line.separator"));
135 sb
.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(cb
));
136 sb
.append(System
.getProperty("line.separator"));
139 CdmEntityInfo dupCei
= entry
.getValue();
140 CdmBase dupCb
= (CdmBase
) dupCei
.getObject();
141 Object dupCbParent
= dupCei
.getParent().getObject();
143 sb
.append(" - " + dupCei
.getField().getName() + ":" + dupCb
.getUserFriendlyTypeName() + "/" + dupCb
.getId() +
144 " in entity " + dupCbParent
.getClass().getCanonicalName());
145 if(dupCbParent
instanceof CdmBase
) {
146 sb
.append(" with id : " + ((CdmBase
)dupCbParent
).getId());
148 sb
.append(System
.getProperty("line.separator"));
149 sb
.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(dupCb
));
150 sb
.append(System
.getProperty("line.separator"));
151 sb
.append("-----------");
155 sb
.append(System
.getProperty("line.separator"));
156 sb
.append(System
.getProperty("line.separator"));
158 if(notInCacheList
.isEmpty()) {
159 sb
.append("No Entities found which are not in Cache.");
161 sb
.append("Not In Cache Entities,");
163 for(CdmEntityInfo cei
: notInCacheList
) {
164 CdmBase cb
= (CdmBase
) cei
.getObject();
165 Object cbParent
= cei
.getParent().getObject();
167 sb
.append(System
.getProperty("line.separator"));
169 String fieldName
= "";
170 if(cei
.getField() != null) {
171 fieldName
= cei
.getField().getName();
173 sb
.append(" - " + fieldName
+ ":" + cb
.getUserFriendlyTypeName() + "/" + cb
.getId());
175 if(cbParent
instanceof CdmBase
) {
176 sb
.append(" of entity " + ((CdmBase
)cbParent
).getUserFriendlyTypeName());
178 sb
.append(" of entity " + cbParent
.getClass().getName());
182 sb
.append(System
.getProperty("line.separator"));
183 return sb
.toString();
186 private String
getCachesContainingEntity(CdmBase cdmEntity
) {
187 Cache defaultCache
= CacheManager
.create().getCache(CdmCacher
.DEFAULT_CACHE_NAME
);
189 Element dce
= defaultCache
.get(cdmEntity
.getUuid());
190 if(dce
!= null && dce
.getObjectValue() == cdmEntity
) {
194 Object cte
= cacher
.getFromCache(CdmTransientEntityCacher
.generateKey(cdmEntity
));
195 if(cte
!= null && cte
== cdmEntity
) {
202 private void debug(CdmBase cdmEntity
, boolean recursive
) {
203 if(cdmEntity
== null) {
206 logger
.info("---- starting recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
207 List
<CdmEntityInfo
> alreadyVisitedEntities
= new ArrayList
<CdmEntityInfo
>();
208 CdmEntityInfo cei
= new CdmEntityInfo(cdmEntity
);
209 debugRecursive(cdmEntity
, alreadyVisitedEntities
, cei
);
210 rootElements
.add(cei
);
211 alreadyVisitedEntities
.clear();
212 logger
.info("---- ending recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + "\n");
215 private <T
extends Object
> void debugRecursive(T obj
,
216 List
<CdmEntityInfo
> alreadyVisitedEntities
,
221 if(obj
instanceof CdmBase
) {
222 debugRecursive((CdmBase
)obj
, alreadyVisitedEntities
, cei
);
223 } else if (obj
instanceof Map
) {
224 debug((Map
<T
,T
>)obj
, alreadyVisitedEntities
, cei
);
225 } else if (obj
instanceof Collection
) {
226 debug((Collection
<T
>)obj
, alreadyVisitedEntities
, cei
);
229 logger
.info("No caching yet for type " + obj
.getClass().getName());
234 private <T
extends Object
> void debug(Map
<T
,T
> map
,
235 List
<CdmEntityInfo
> alreadyVisitedEntities
,
237 if(map
== null || map
.isEmpty()) {
241 int originalMapSize
= map
.size();
243 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
245 while ( iter
.hasNext() ) {
246 Map
.Entry
<T
,T
> e
= iter
.next();
247 CdmEntityInfo childCei
= new CdmEntityInfo(e
);
248 cei
.addChild(childCei
);
249 debugRecursive(e
.getKey(), alreadyVisitedEntities
, childCei
);
250 debugRecursive(e
.getValue(), alreadyVisitedEntities
, childCei
);
254 private <T
extends Object
> void debug(Collection
<T
> collection
,
255 List
<CdmEntityInfo
> alreadyVisitedEntities
,
257 int length
= collection
.size();
258 Object
[] result
= new Object
[length
];
259 Iterator
<T
> collectionItr
= collection
.iterator();
261 while(collectionItr
.hasNext()) {
262 Object obj
= collectionItr
.next();
263 CdmEntityInfo childCei
= new CdmEntityInfo(obj
);
264 cei
.addChild(childCei
);
265 debugRecursive(obj
, alreadyVisitedEntities
, childCei
);
271 private void debugRecursive(CdmBase cdmEntity
,
272 List
<CdmEntityInfo
> alreadyVisitedEntities
,
275 CdmBase cachedCdmEntityInSubGraph
= null;
277 if(cei
.getObject() instanceof CdmBase
) {
278 CdmBase cb
= (CdmBase
)cei
.getObject();
279 cachedCdmEntityInSubGraph
= cacher
.getFromCache(cb
);
280 if(cachedCdmEntityInSubGraph
!= cb
) {
281 // found a cdm entity which is not in cache - need to record this
282 //logger.info(" - found entity not in cache " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
283 addEntityNotInCache(cei
);
288 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
289 // since there could be new or deleted objects in the cdmEntity sub-graph
291 // start by getting the fields from the cdm entity
292 String className
= cdmEntity
.getClass().getName();
293 CdmModelFieldPropertyFromClass cmgmfc
= cacher
.getFromCdmlibModelCache(className
);
295 alreadyVisitedEntities
.add(cei
);
296 List
<String
> fields
= cmgmfc
.getFields();
297 for(String field
: fields
) {
298 // retrieve the actual object corresponding to the field.
299 // this object will be either a CdmBase or a Collection / Map
300 // with CdmBase as the generic type
302 CdmEntityInfo childCei
= getDebugCdmBaseTypeFieldValue(cdmEntity
, field
, alreadyVisitedEntities
, cei
);
303 if(!childCei
.isProxy()) {
304 Object object
= childCei
.getObject();
305 if(object
!= null && object
instanceof CdmBase
) {
306 CdmBase cdmEntityInSubGraph
= (CdmBase
)object
;
307 if(!containsIdenticalCdmEntity(alreadyVisitedEntities
, cdmEntityInSubGraph
)) {
308 logger
.info("recursive debugging object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId());
309 debugRecursive(cdmEntityInSubGraph
, alreadyVisitedEntities
, childCei
);
311 logger
.info("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + " already visited");
317 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity
.getClass().getName() + " is not found in the cdmlib model cache. " +
318 "The cache may be corrupted or not in sync with the latest model version" );
324 private CdmEntityInfo
getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity
,
326 List
<CdmEntityInfo
> alreadyVisitedEntities
,
329 CdmEntityInfo childCei
= null;
330 Class
<?
> clazz
= cdmEntity
.getClass();
332 // this call will search in the provided class as well as
333 // the super classes until it finds the field
334 Field field
= ReflectionUtils
.findField(clazz
, fieldName
);
337 throw new CdmClientCacheException("Field '" + fieldName
338 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
340 field
.setAccessible(true);
341 Object o
= field
.get(cdmEntity
);
343 CdmBase cdmEntityInSubGraph
= null;
345 boolean isHibernateProxy
= false;
346 boolean isPersistentCollection
= false;
348 childCei
= new CdmEntityInfo(o
);
349 cei
.addChild(childCei
);
350 childCei
.setField(field
);
354 if(o
instanceof HibernateProxy
) {
355 isHibernateProxy
= true;
358 if(o
instanceof PersistentCollection
) {
359 isPersistentCollection
= true;
361 childCei
.setObject(o
);
362 childCei
.setProxy(isHibernateProxy
|| isPersistentCollection
);
363 if(!isHibernateProxy
&& !isPersistentCollection
) {
365 if(CdmBase
.class.isAssignableFrom(o
.getClass())) {
366 logger
.info("found initialised cdm entity '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
367 cdmEntityInSubGraph
= (CdmBase
)o
;
369 //logger.info(" - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
370 CdmEntityInfo dupCei
= getDuplicate(alreadyVisitedEntities
, cdmEntityInSubGraph
);
372 addDuplicateEntity(childCei
, dupCei
);
375 } else if(o
instanceof Map
) {
376 debugRecursive((Map
)o
, alreadyVisitedEntities
, childCei
);
377 } else if(o
instanceof Collection
) {
378 debugRecursive((Collection
)o
, alreadyVisitedEntities
, childCei
);
383 // we return the original cdm entity in the sub graph because we
384 // want to continue to recurse on the input cdm entity graph
385 // and not the one in the cache
388 } catch (SecurityException e
) {
389 throw new CdmClientCacheException(e
);
390 } catch (IllegalArgumentException e
) {
391 throw new CdmClientCacheException(e
);
392 } catch (IllegalAccessException e
) {
393 throw new CdmClientCacheException(e
);
398 private CdmEntityInfo
getDuplicate(List
<CdmEntityInfo
> alreadyVisitedEntities
, Object objectToCompare
) {
399 if(objectToCompare
!= null ) {
400 for(CdmEntityInfo cei
: alreadyVisitedEntities
) {
401 if(objectToCompare
.equals(cei
.getObject()) && objectToCompare
!= cei
.getObject()) {
409 private boolean containsIdenticalCdmEntity(List
<CdmEntityInfo
> ceiSet
, Object objectToCompare
) {
410 boolean foundIdentical
= false;
411 if(objectToCompare
!= null) {
412 for(CdmEntityInfo cei
: ceiSet
) {
413 if(cei
.getObject() == objectToCompare
) {
414 foundIdentical
= true;
415 } else if(objectToCompare
.equals(cei
.getObject())) {
420 return foundIdentical
;
423 public class CdmEntityInfo
{
425 private Object object
;
426 private CdmEntityInfo parent
;
427 private List
<CdmEntityInfo
> children
;
429 private String label
;
430 private boolean isProxy
;
432 public CdmEntityInfo(Object object
) {
433 this.object
= object
;
435 children
= new ArrayList
<CdmEntityInfo
>();
438 public CdmEntityInfo
getParent() {
442 public void setParent(CdmEntityInfo parent
) {
443 this.parent
= parent
;
446 public List
<CdmEntityInfo
> getChildren() {
450 public void setChildren(List
<CdmEntityInfo
> children
) {
451 this.children
= children
;
454 public void addChild(CdmEntityInfo cei
) {
455 this.children
.add(cei
);
459 public Field
getField() {
463 public void setField(Field field
) {
468 public String
getLabel() {
470 String fieldName
= "";
472 fieldName
= field
.getName();
476 String className
= object
.getClass().getName();
477 if(object
instanceof HibernateProxy
) {
478 LazyInitializer hli
= ((HibernateProxy
)object
).getHibernateLazyInitializer();
479 if(hli
.isUninitialized()) {
480 className
= "HibernateProxy";
482 className
= "InitialisedHibernateProxy";
484 label
= "[" + className
+ "] " + fieldName
;
485 } else if(object
instanceof PersistentCollection
) {
486 PersistentCollection pc
= ((PersistentCollection
)object
);
487 if(!pc
.wasInitialized()) {
488 className
= "PersistentCollection";
490 className
= "InitialisedPersistentCollection";
492 label
= "[" + className
+ "] " + fieldName
;
493 } else if(object
instanceof Collection
) {
494 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Collection
)object
).size());
495 } else if(object
instanceof Map
) {
497 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Map
)object
).size());
498 } else if(object
instanceof CdmBase
) {
499 label
= getCachesContainingEntity((CdmBase
)object
) + "[" + className
+ ",id" + ((CdmBase
)object
).getId() + "] " + fieldName
+ " : " + object
.toString();
501 label
= "[" + className
+ "] " + fieldName
+ " : " + object
.toString();
504 label
= "[NULL] " + fieldName
;
509 public void setLabel(String label
) {
513 public Object
getObject() {
517 public void setObject(Object object
) {
518 this.object
= object
;
521 public boolean isProxy() {
525 public void setProxy(boolean isProxy
) {
526 this.isProxy
= isProxy
;