2 * Copyright (C) 2015 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
.taxeditor
.remoting
.cache
;
11 import java
.lang
.reflect
.Field
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Collection
;
14 import java
.util
.HashMap
;
15 import java
.util
.Iterator
;
16 import java
.util
.List
;
19 import net
.sf
.ehcache
.Cache
;
20 import net
.sf
.ehcache
.CacheManager
;
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
, Collection
<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();
126 sb
.append(" - " + cei
.getField().getName() + ":" + cb
.getUserFriendlyTypeName() + "/" + cb
.getId());
127 if(cei
.getParent() != null) {
128 Object cbParent
= cei
.getParent().getObject();
129 sb
.append(" in entity " + cbParent
.getClass().getCanonicalName());
130 if(cbParent
instanceof CdmBase
) {
132 sb
.append(" with id : " + ((CdmBase
)cbParent
).getId());
135 sb
.append(System
.getProperty("line.separator"));
136 sb
.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(cb
));
137 sb
.append(System
.getProperty("line.separator"));
140 CdmEntityInfo dupCei
= entry
.getValue();
141 CdmBase dupCb
= (CdmBase
) dupCei
.getObject();
143 String dupCeiFieldName
= "";
144 if(dupCei
.getField() != null) {
145 dupCeiFieldName
= dupCei
.getField().getName();
147 sb
.append(" - " + dupCeiFieldName
+ ":" + dupCb
.getUserFriendlyTypeName() + "/" + dupCb
.getId());
148 if(dupCei
.getParent() != null) {
149 Object dupCbParent
= dupCei
.getParent().getObject();
150 sb
.append(" in entity " + dupCbParent
.getClass().getCanonicalName());
151 if(dupCbParent
instanceof CdmBase
) {
152 sb
.append(" with id : " + ((CdmBase
)dupCbParent
).getId());
155 sb
.append(System
.getProperty("line.separator"));
156 sb
.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(dupCb
));
157 sb
.append(System
.getProperty("line.separator"));
158 sb
.append("-----------");
162 sb
.append(System
.getProperty("line.separator"));
163 sb
.append(System
.getProperty("line.separator"));
165 if(notInCacheList
.isEmpty()) {
166 sb
.append("No Entities found which are not in Cache.");
168 sb
.append("Not In Cache Entities,");
170 for(CdmEntityInfo cei
: notInCacheList
) {
171 CdmBase cb
= (CdmBase
) cei
.getObject();
172 Object cbParent
= cei
.getParent().getObject();
174 sb
.append(System
.getProperty("line.separator"));
176 String fieldName
= "";
177 if(cei
.getField() != null) {
178 fieldName
= cei
.getField().getName();
180 sb
.append(" - " + fieldName
+ ":" + cb
.getUserFriendlyTypeName() + "/" + cb
.getId());
182 if(cbParent
instanceof CdmBase
) {
183 sb
.append(" of entity " + ((CdmBase
)cbParent
).getUserFriendlyTypeName());
185 sb
.append(" of entity " + cbParent
.getClass().getName());
189 sb
.append(System
.getProperty("line.separator"));
190 return sb
.toString();
193 private String
getCachesContainingEntity(CdmBase cdmEntity
) {
194 Cache defaultCache
= CacheManager
.create().getCache(CdmCacher
.DEFAULT_CACHE_NAME
);
196 Element dce
= defaultCache
.get(cdmEntity
.getUuid());
197 if(dce
!= null && dce
.getObjectValue() == cdmEntity
) {
201 Object cte
= cacher
.getFromCache(CdmTransientEntityCacher
.generateKey(cdmEntity
));
202 if(cte
!= null && cte
== cdmEntity
) {
209 private void debug(CdmBase cdmEntity
, boolean recursive
) {
210 if(cdmEntity
== null) {
213 logger
.info("---- starting recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
214 List
<CdmEntityInfo
> alreadyVisitedEntities
= new ArrayList
<CdmEntityInfo
>();
215 CdmEntityInfo cei
= new CdmEntityInfo(ProxyUtils
.deproxy(cdmEntity
));
216 debugRecursive(cdmEntity
, alreadyVisitedEntities
, cei
);
217 rootElements
.add(cei
);
218 alreadyVisitedEntities
.clear();
219 logger
.info("---- ending recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + "\n");
222 private <T
extends Object
> void debugRecursive(T obj
,
223 List
<CdmEntityInfo
> alreadyVisitedEntities
,
228 if(obj
instanceof CdmBase
) {
229 debugRecursive((CdmBase
)obj
, alreadyVisitedEntities
, cei
);
230 } else if (obj
instanceof Map
) {
231 debug((Map
<T
,T
>)obj
, alreadyVisitedEntities
, cei
);
232 } else if (obj
instanceof Collection
) {
233 debug((Collection
<T
>)obj
, alreadyVisitedEntities
, cei
);
236 logger
.info("No caching yet for type " + obj
.getClass().getName());
241 private <T
extends Object
> void debug(Map
<T
,T
> map
,
242 List
<CdmEntityInfo
> alreadyVisitedEntities
,
244 if(map
== null || map
.isEmpty()) {
248 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
249 while ( iter
.hasNext() ) {
250 Map
.Entry
<T
,T
> e
= iter
.next();
251 CdmEntityInfo childCei
= new CdmEntityInfo(e
);
252 cei
.addChild(childCei
);
254 CdmEntityInfo keyCei
= new CdmEntityInfo(ProxyUtils
.deproxy(e
.getKey()));
255 childCei
.addChild(keyCei
);
256 CdmEntityInfo valueCei
= new CdmEntityInfo(ProxyUtils
.deproxy(e
.getValue()));
257 childCei
.addChild(valueCei
);
259 debugRecursive(e
.getKey(), alreadyVisitedEntities
, keyCei
);
260 debugRecursive(e
.getValue(), alreadyVisitedEntities
, valueCei
);
264 private <T
extends Object
> void debug(Collection
<T
> collection
,
265 List
<CdmEntityInfo
> alreadyVisitedEntities
,
267 Iterator
<T
> collectionItr
= collection
.iterator();
269 while(collectionItr
.hasNext()) {
270 Object obj
= collectionItr
.next();
271 boolean alreadyVisited
= false;
272 for (CdmEntityInfo entityInfo
: alreadyVisitedEntities
) {
273 if(obj
.equals(entityInfo
.getObject())){
274 alreadyVisited
= true;
279 CdmEntityInfo childCei
= new CdmEntityInfo(ProxyUtils
.deproxy(obj
));
280 cei
.addChild(childCei
);
281 debugRecursive(obj
, alreadyVisitedEntities
, childCei
);
288 private void debugRecursive(CdmBase cdmEntity
,
289 List
<CdmEntityInfo
> alreadyVisitedEntities
,
292 CdmBase cachedCdmEntityInSubGraph
= null;
294 if(cei
.getObject() instanceof CdmBase
) {
295 CdmBase cb
= (CdmBase
)cei
.getObject();
296 cachedCdmEntityInSubGraph
= cacher
.getFromCache(cb
);
297 if(cachedCdmEntityInSubGraph
!= cb
) {
298 // found a cdm entity which is not in cache - need to record this
299 //logger.info(" - found entity not in cache " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
300 addEntityNotInCache(cei
);
305 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
306 // since there could be new or deleted objects in the cdmEntity sub-graph
308 // start by getting the fields from the cdm entity
309 String className
= cdmEntity
.getClass().getName();
310 CdmModelFieldPropertyFromClass cmgmfc
= cacher
.getFromCdmlibModelCache(className
);
312 alreadyVisitedEntities
.add(cei
);
313 List
<String
> fields
= cmgmfc
.getFields();
314 for(String field
: fields
) {
315 // retrieve the actual object corresponding to the field.
316 // this object will be either a CdmBase or a Collection / Map
317 // with CdmBase as the generic type
318 CdmEntityInfo childCei
= getDebugCdmBaseTypeFieldValue(cdmEntity
, field
, alreadyVisitedEntities
, cei
);
319 if(!childCei
.isProxy()) {
320 Object object
= childCei
.getObject();
321 if(object
!= null && object
instanceof CdmBase
) {
322 CdmBase cdmEntityInSubGraph
= (CdmBase
)object
;
323 if(!containsIdenticalCdmEntity(alreadyVisitedEntities
, cdmEntityInSubGraph
)) {
324 logger
.info("recursive debugging object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId());
325 debugRecursive(cdmEntityInSubGraph
, alreadyVisitedEntities
, childCei
);
327 logger
.info("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + " already visited");
333 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity
.getClass().getName() + " is not found in the cdmlib model cache. " +
334 "The cache may be corrupted or not in sync with the latest model version" );
340 private CdmEntityInfo
getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity
,
342 List
<CdmEntityInfo
> alreadyVisitedEntities
,
345 CdmEntityInfo childCei
= null;
346 Class
<?
> clazz
= cdmEntity
.getClass();
348 // this call will search in the provided class as well as
349 // the super classes until it finds the field
350 Field field
= ReflectionUtils
.findField(clazz
, fieldName
);
353 throw new CdmClientCacheException("Field '" + fieldName
354 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
356 field
.setAccessible(true);
357 Object o
= field
.get(cdmEntity
);
358 o
= ProxyUtils
.deproxy(o
);
359 CdmBase cdmEntityInSubGraph
= null;
361 childCei
= new CdmEntityInfo(o
);
362 cei
.addChild(childCei
);
363 childCei
.setField(field
);
366 boolean isProxy
= ProxyUtils
.isProxy(o
);
368 childCei
.setProxy(isProxy
);
370 childCei
.setObject(o
);
371 if(CdmBase
.class.isAssignableFrom(o
.getClass())) {
372 logger
.info("found initialised cdm entity '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
373 cdmEntityInSubGraph
= (CdmBase
)o
;
375 //logger.info(" - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
376 CdmEntityInfo dupCei
= getDuplicate(alreadyVisitedEntities
, cdmEntityInSubGraph
);
378 addDuplicateEntity(childCei
, dupCei
);
381 } else if(o
instanceof Map
) {
382 debugRecursive((Map
)o
, alreadyVisitedEntities
, childCei
);
383 } else if(o
instanceof Collection
) {
384 debugRecursive((Collection
)o
, alreadyVisitedEntities
, childCei
);
389 // we return the original cdm entity in the sub graph because we
390 // want to continue to recurse on the input cdm entity graph
391 // and not the one in the cache
394 } catch (SecurityException e
) {
395 throw new CdmClientCacheException(e
);
396 } catch (IllegalArgumentException e
) {
397 throw new CdmClientCacheException(e
);
398 } catch (IllegalAccessException e
) {
399 throw new CdmClientCacheException(e
);
404 private CdmEntityInfo
getDuplicate(List
<CdmEntityInfo
> alreadyVisitedEntities
, Object objectToCompare
) {
405 if(objectToCompare
!= null ) {
406 for(CdmEntityInfo cei
: alreadyVisitedEntities
) {
407 if(objectToCompare
.equals(cei
.getObject()) && objectToCompare
!= cei
.getObject()) {
415 private boolean containsIdenticalCdmEntity(List
<CdmEntityInfo
> ceiSet
, Object objectToCompare
) {
416 boolean foundIdentical
= false;
417 if(objectToCompare
!= null) {
418 for(CdmEntityInfo cei
: ceiSet
) {
419 if(cei
.getObject() == objectToCompare
) {
420 foundIdentical
= true;
422 // } else if(objectToCompare.equals(cei.getObject())) {
427 return foundIdentical
;
430 public class CdmEntityInfo
{
432 private Object object
;
433 private CdmEntityInfo parent
;
434 private List
<CdmEntityInfo
> children
;
436 private String label
;
437 private boolean isProxy
;
439 public CdmEntityInfo(Object object
) {
440 this.object
= object
;
442 children
= new ArrayList
<CdmEntityInfo
>();
445 public CdmEntityInfo
getParent() {
449 public void setParent(CdmEntityInfo parent
) {
450 this.parent
= parent
;
453 public List
<CdmEntityInfo
> getChildren() {
457 public void setChildren(List
<CdmEntityInfo
> children
) {
458 this.children
= children
;
461 public void addChild(CdmEntityInfo cei
) {
462 this.children
.add(cei
);
466 public Field
getField() {
470 public void setField(Field field
) {
475 public String
getLabel() {
477 String fieldName
= "";
479 fieldName
= field
.getName();
483 String className
= object
.getClass().getName();
484 if(object
instanceof HibernateProxy
) {
485 LazyInitializer hli
= ((HibernateProxy
)object
).getHibernateLazyInitializer();
486 if(hli
.isUninitialized()) {
487 className
= "HibernateProxy";
489 className
= "InitialisedHibernateProxy";
491 label
= "[" + className
+ "] " + fieldName
;
492 } else if(object
instanceof PersistentCollection
) {
493 PersistentCollection pc
= ((PersistentCollection
)object
);
494 if(!pc
.wasInitialized()) {
495 className
= "PersistentCollection";
497 className
= "InitialisedPersistentCollection";
499 label
= "[" + className
+ "] " + fieldName
;
500 } else if(object
instanceof Collection
) {
501 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Collection
)object
).size());
502 } else if(object
instanceof Map
) {
503 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Map
)object
).size());
504 } else if(object
instanceof CdmBase
) {
505 label
= getCachesContainingEntity((CdmBase
)object
) + "[" + className
+ ",id" + ((CdmBase
)object
).getId() + "] " + fieldName
+ " : " + object
.toString();
507 label
= "[" + className
+ "] " + fieldName
+ " : " + object
.toString();
510 label
= "[NULL] " + fieldName
;
515 public void setLabel(String label
) {
519 public Object
getObject() {
523 public void setObject(Object object
) {
524 this.object
= object
;
527 public boolean isProxy() {
531 public void setProxy(boolean isProxy
) {
532 this.isProxy
= isProxy
;