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
.Element
;
23 import org
.apache
.log4j
.Logger
;
24 import org
.hibernate
.collection
.spi
.PersistentCollection
;
25 import org
.hibernate
.proxy
.HibernateProxy
;
26 import org
.hibernate
.proxy
.LazyInitializer
;
27 import org
.springframework
.util
.ReflectionUtils
;
29 import eu
.etaxonomy
.cdm
.api
.cache
.CdmCacher
;
30 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
37 public class EntityCacherDebugResult
{
39 private static final Logger logger
= Logger
.getLogger(EntityCacherDebugResult
.class);
41 private Map
<CdmEntityInfo
, CdmEntityInfo
> duplicateCdmEntityMap
;
43 private List
<CdmEntityInfo
> notInCacheList
;
45 private CdmTransientEntityCacher cacher
;
47 private List
<CdmEntityInfo
> rootElements
;
49 StringBuilder debugOutput
= new StringBuilder();
51 public EntityCacherDebugResult() {
55 public <T
extends CdmBase
> EntityCacherDebugResult(CdmTransientEntityCacher cacher
, List
<T
> rootEntities
) {
59 if(rootEntities
!= null && !rootEntities
.isEmpty()) {
60 for(CdmBase rootEntity
: rootEntities
) {
61 debug(rootEntity
, true);
62 String out
= toString(duplicateCdmEntityMap
, notInCacheList
, rootEntity
);
63 System
.out
.println(out
);
64 debugOutput
.append(out
);
72 duplicateCdmEntityMap
= new HashMap
<CdmEntityInfo
, CdmEntityInfo
>();
73 notInCacheList
= new ArrayList
<CdmEntityInfo
>();
74 rootElements
= new ArrayList
<CdmEntityInfo
>();
77 private void clear() {
78 duplicateCdmEntityMap
.clear();
79 notInCacheList
.clear();
82 public void addDuplicateEntity(CdmEntityInfo cei
, CdmEntityInfo cachedCei
) {
83 duplicateCdmEntityMap
.put(cei
, cachedCei
);
86 public void addEntityNotInCache(CdmEntityInfo cei
) {
87 notInCacheList
.add(cei
);
90 public List
<CdmEntityInfo
> getRootElements() {
94 private void print(Map
<CdmEntityInfo
, CdmEntityInfo
> duplicateCdmEntityMap
,
95 List
<CdmEntityInfo
> notInCacheList
,
97 System
.out
.println(toString(duplicateCdmEntityMap
, notInCacheList
, rootEntity
));
102 public String
toString() {
103 return debugOutput
.toString();
106 private String
toString(Map
<CdmEntityInfo
, CdmEntityInfo
> duplicateCdmEntityMap
,
107 List
<CdmEntityInfo
> notInCacheList
,
108 CdmBase rootEntity
) {
111 StringBuilder sb
= new StringBuilder();
112 sb
.append(System
.getProperty("line.separator"));
113 sb
.append("<<< Root Entity " + rootEntity
.getUserFriendlyTypeName() + " with id " + rootEntity
.getId() + " >>>");
114 sb
.append(System
.getProperty("line.separator"));
115 if(duplicateCdmEntityMap
.isEmpty()) {
116 sb
.append("No Duplicate CDM Entities.");
118 sb
.append("Duplicate CDM Entities,");
120 for (Map
.Entry
<CdmEntityInfo
, CdmEntityInfo
> entry
: duplicateCdmEntityMap
.entrySet())
122 sb
.append(System
.getProperty("line.separator"));
123 CdmEntityInfo cei
= entry
.getKey();
124 CdmBase cb
= (CdmBase
) cei
.getObject();
125 Object cbParent
= cei
.getParent().getObject();
128 sb
.append(" - " + cei
.getField().getName() + ":" + cb
.getUserFriendlyTypeName() + "/" + cb
.getId() +
129 " in entity " + cbParent
.getClass().getCanonicalName());
130 if(cbParent
instanceof CdmBase
) {
131 sb
.append(" with id : " + ((CdmBase
)cbParent
).getId());
133 sb
.append(System
.getProperty("line.separator"));
134 sb
.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(cb
));
135 sb
.append(System
.getProperty("line.separator"));
138 CdmEntityInfo dupCei
= entry
.getValue();
139 CdmBase dupCb
= (CdmBase
) dupCei
.getObject();
140 Object dupCbParent
= dupCei
.getParent().getObject();
142 sb
.append(" - " + dupCei
.getField().getName() + ":" + dupCb
.getUserFriendlyTypeName() + "/" + dupCb
.getId() +
143 " in entity " + dupCbParent
.getClass().getCanonicalName());
144 if(dupCbParent
instanceof CdmBase
) {
145 sb
.append(" with id : " + ((CdmBase
)dupCbParent
).getId());
147 sb
.append(System
.getProperty("line.separator"));
148 sb
.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(dupCb
));
149 sb
.append(System
.getProperty("line.separator"));
150 sb
.append("-----------");
154 sb
.append(System
.getProperty("line.separator"));
155 sb
.append(System
.getProperty("line.separator"));
157 if(notInCacheList
.isEmpty()) {
158 sb
.append("No Entities found which are not in Cache.");
160 sb
.append("Not In Cache Entities,");
162 for(CdmEntityInfo cei
: notInCacheList
) {
163 CdmBase cb
= (CdmBase
) cei
.getObject();
164 Object cbParent
= cei
.getParent().getObject();
166 sb
.append(System
.getProperty("line.separator"));
168 String fieldName
= "";
169 if(cei
.getField() != null) {
170 fieldName
= cei
.getField().getName();
172 sb
.append(" - " + fieldName
+ ":" + cb
.getUserFriendlyTypeName() + "/" + cb
.getId());
174 if(cbParent
instanceof CdmBase
) {
175 sb
.append(" of entity " + ((CdmBase
)cbParent
).getUserFriendlyTypeName());
177 sb
.append(" of entity " + cbParent
.getClass().getName());
181 sb
.append(System
.getProperty("line.separator"));
182 return sb
.toString();
185 private String
getCachesContainingEntity(CdmBase cdmEntity
) {
186 Cache defaultCache
= CdmRemoteCacheManager
.getInstance().getDefaultCacheManager().getCache(CdmCacher
.DEFAULT_CACHE_NAME
);
188 Element dce
= defaultCache
.get(cdmEntity
.getUuid());
189 if(dce
!= null && dce
.getObjectValue() == cdmEntity
) {
193 Object cte
= cacher
.getFromCache(CdmTransientEntityCacher
.generateKey(cdmEntity
));
194 if(cte
!= null && cte
== cdmEntity
) {
201 private void debug(CdmBase cdmEntity
, boolean recursive
) {
202 if(cdmEntity
== null) {
205 logger
.info("---- starting recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
206 List
<CdmEntityInfo
> alreadyVisitedEntities
= new ArrayList
<CdmEntityInfo
>();
207 CdmEntityInfo cei
= new CdmEntityInfo(cdmEntity
);
208 debugRecursive(cdmEntity
, alreadyVisitedEntities
, cei
);
209 rootElements
.add(cei
);
210 alreadyVisitedEntities
.clear();
211 logger
.info("---- ending recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + "\n");
214 private <T
extends Object
> void debugRecursive(T obj
,
215 List
<CdmEntityInfo
> alreadyVisitedEntities
,
220 if(obj
instanceof CdmBase
) {
221 debugRecursive((CdmBase
)obj
, alreadyVisitedEntities
, cei
);
222 } else if (obj
instanceof Map
) {
223 debug((Map
<T
,T
>)obj
, alreadyVisitedEntities
, cei
);
224 } else if (obj
instanceof Collection
) {
225 debug((Collection
<T
>)obj
, alreadyVisitedEntities
, cei
);
228 logger
.info("No caching yet for type " + obj
.getClass().getName());
233 private <T
extends Object
> void debug(Map
<T
,T
> map
,
234 List
<CdmEntityInfo
> alreadyVisitedEntities
,
236 if(map
== null || map
.isEmpty()) {
240 int originalMapSize
= map
.size();
242 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
244 while ( iter
.hasNext() ) {
245 Map
.Entry
<T
,T
> e
= iter
.next();
246 CdmEntityInfo childCei
= new CdmEntityInfo(e
);
247 cei
.addChild(childCei
);
248 debugRecursive(e
.getKey(), alreadyVisitedEntities
, childCei
);
249 debugRecursive(e
.getValue(), alreadyVisitedEntities
, childCei
);
253 private <T
extends Object
> void debug(Collection
<T
> collection
,
254 List
<CdmEntityInfo
> alreadyVisitedEntities
,
256 int length
= collection
.size();
257 Object
[] result
= new Object
[length
];
258 Iterator
<T
> collectionItr
= collection
.iterator();
260 while(collectionItr
.hasNext()) {
261 Object obj
= collectionItr
.next();
262 CdmEntityInfo childCei
= new CdmEntityInfo(obj
);
263 cei
.addChild(childCei
);
264 debugRecursive(obj
, alreadyVisitedEntities
, childCei
);
270 private void debugRecursive(CdmBase cdmEntity
,
271 List
<CdmEntityInfo
> alreadyVisitedEntities
,
274 CdmBase cachedCdmEntityInSubGraph
= null;
276 if(cei
.getObject() instanceof CdmBase
) {
277 CdmBase cb
= (CdmBase
)cei
.getObject();
278 cachedCdmEntityInSubGraph
= cacher
.getFromCache(cb
);
279 if(cachedCdmEntityInSubGraph
!= cb
) {
280 // found a cdm entity which is not in cache - need to record this
281 //logger.info(" - found entity not in cache " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
282 addEntityNotInCache(cei
);
287 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
288 // since there could be new or deleted objects in the cdmEntity sub-graph
290 // start by getting the fields from the cdm entity
291 String className
= cdmEntity
.getClass().getName();
292 CdmModelFieldPropertyFromClass cmgmfc
= cacher
.getFromCdmlibModelCache(className
);
294 alreadyVisitedEntities
.add(cei
);
295 List
<String
> fields
= cmgmfc
.getFields();
296 for(String field
: fields
) {
297 // retrieve the actual object corresponding to the field.
298 // this object will be either a CdmBase or a Collection / Map
299 // with CdmBase as the generic type
301 CdmEntityInfo childCei
= getDebugCdmBaseTypeFieldValue(cdmEntity
, field
, alreadyVisitedEntities
, cei
);
302 if(!childCei
.isProxy()) {
303 Object object
= childCei
.getObject();
304 if(object
!= null && object
instanceof CdmBase
) {
305 CdmBase cdmEntityInSubGraph
= (CdmBase
)object
;
306 if(!containsIdenticalCdmEntity(alreadyVisitedEntities
, cdmEntityInSubGraph
)) {
307 logger
.info("recursive debugging object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId());
308 debugRecursive(cdmEntityInSubGraph
, alreadyVisitedEntities
, childCei
);
310 logger
.info("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + " already visited");
316 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity
.getClass().getName() + " is not found in the cdmlib model cache. " +
317 "The cache may be corrupted or not in sync with the latest model version" );
323 private CdmEntityInfo
getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity
,
325 List
<CdmEntityInfo
> alreadyVisitedEntities
,
328 CdmEntityInfo childCei
= null;
329 Class
<?
> clazz
= cdmEntity
.getClass();
331 // this call will search in the provided class as well as
332 // the super classes until it finds the field
333 Field field
= ReflectionUtils
.findField(clazz
, fieldName
);
336 throw new CdmClientCacheException("Field '" + fieldName
337 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
339 field
.setAccessible(true);
340 Object o
= field
.get(cdmEntity
);
342 CdmBase cdmEntityInSubGraph
= null;
344 boolean isHibernateProxy
= false;
345 boolean isPersistentCollection
= false;
347 childCei
= new CdmEntityInfo(o
);
348 cei
.addChild(childCei
);
349 childCei
.setField(field
);
353 if(o
instanceof HibernateProxy
) {
354 isHibernateProxy
= true;
357 if(o
instanceof PersistentCollection
) {
358 isPersistentCollection
= true;
360 childCei
.setObject(o
);
361 childCei
.setProxy(isHibernateProxy
|| isPersistentCollection
);
362 if(!isHibernateProxy
&& !isPersistentCollection
) {
364 if(CdmBase
.class.isAssignableFrom(o
.getClass())) {
365 logger
.info("found initialised cdm entity '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
366 cdmEntityInSubGraph
= (CdmBase
)o
;
368 //logger.info(" - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
369 CdmEntityInfo dupCei
= getDuplicate(alreadyVisitedEntities
, cdmEntityInSubGraph
);
371 addDuplicateEntity(childCei
, dupCei
);
374 } else if(o
instanceof Map
) {
375 debugRecursive((Map
)o
, alreadyVisitedEntities
, childCei
);
376 } else if(o
instanceof Collection
) {
377 debugRecursive((Collection
)o
, alreadyVisitedEntities
, childCei
);
382 // we return the original cdm entity in the sub graph because we
383 // want to continue to recurse on the input cdm entity graph
384 // and not the one in the cache
387 } catch (SecurityException e
) {
388 throw new CdmClientCacheException(e
);
389 } catch (IllegalArgumentException e
) {
390 throw new CdmClientCacheException(e
);
391 } catch (IllegalAccessException e
) {
392 throw new CdmClientCacheException(e
);
397 private CdmEntityInfo
getDuplicate(List
<CdmEntityInfo
> alreadyVisitedEntities
, Object objectToCompare
) {
398 if(objectToCompare
!= null ) {
399 for(CdmEntityInfo cei
: alreadyVisitedEntities
) {
400 if(objectToCompare
.equals(cei
.getObject()) && objectToCompare
!= cei
.getObject()) {
408 private boolean containsIdenticalCdmEntity(List
<CdmEntityInfo
> ceiSet
, Object objectToCompare
) {
409 boolean foundIdentical
= false;
410 if(objectToCompare
!= null) {
411 for(CdmEntityInfo cei
: ceiSet
) {
412 if(cei
.getObject() == objectToCompare
) {
413 foundIdentical
= true;
414 } else if(objectToCompare
.equals(cei
.getObject())) {
419 return foundIdentical
;
422 public class CdmEntityInfo
{
424 private Object object
;
425 private CdmEntityInfo parent
;
426 private List
<CdmEntityInfo
> children
;
428 private String label
;
429 private boolean isProxy
;
431 public CdmEntityInfo(Object object
) {
432 this.object
= object
;
434 children
= new ArrayList
<CdmEntityInfo
>();
437 public CdmEntityInfo
getParent() {
441 public void setParent(CdmEntityInfo parent
) {
442 this.parent
= parent
;
445 public List
<CdmEntityInfo
> getChildren() {
449 public void setChildren(List
<CdmEntityInfo
> children
) {
450 this.children
= children
;
453 public void addChild(CdmEntityInfo cei
) {
454 this.children
.add(cei
);
458 public Field
getField() {
462 public void setField(Field field
) {
467 public String
getLabel() {
469 String fieldName
= "";
471 fieldName
= field
.getName();
475 String className
= object
.getClass().getName();
476 if(object
instanceof HibernateProxy
) {
477 LazyInitializer hli
= ((HibernateProxy
)object
).getHibernateLazyInitializer();
478 if(hli
.isUninitialized()) {
479 className
= "HibernateProxy";
481 className
= "InitialisedHibernateProxy";
483 label
= "[" + className
+ "] " + fieldName
;
484 } else if(object
instanceof PersistentCollection
) {
485 PersistentCollection pc
= ((PersistentCollection
)object
);
486 if(!pc
.wasInitialized()) {
487 className
= "PersistentCollection";
489 className
= "InitialisedPersistentCollection";
491 label
= "[" + className
+ "] " + fieldName
;
492 } else if(object
instanceof Collection
) {
493 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Collection
)object
).size());
494 } else if(object
instanceof Map
) {
496 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Map
)object
).size());
497 } else if(object
instanceof CdmBase
) {
498 label
= getCachesContainingEntity((CdmBase
)object
) + "[" + className
+ ",id" + ((CdmBase
)object
).getId() + "] " + fieldName
+ " : " + object
.toString();
500 label
= "[" + className
+ "] " + fieldName
+ " : " + object
.toString();
503 label
= "[NULL] " + fieldName
;
508 public void setLabel(String label
) {
512 public Object
getObject() {
516 public void setObject(Object object
) {
517 this.object
= object
;
520 public boolean isProxy() {
524 public void setProxy(boolean isProxy
) {
525 this.isProxy
= isProxy
;