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();
127 sb
.append(" - " + cei
.getField().getName() + ":" + cb
.getUserFriendlyTypeName() + "/" + cb
.getId());
128 if(cei
.getParent() != null) {
129 Object cbParent
= cei
.getParent().getObject();
130 sb
.append(" in entity " + cbParent
.getClass().getCanonicalName());
131 if(cbParent
instanceof CdmBase
) {
133 sb
.append(" with id : " + ((CdmBase
)cbParent
).getId());
136 sb
.append(System
.getProperty("line.separator"));
137 sb
.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(cb
));
138 sb
.append(System
.getProperty("line.separator"));
141 CdmEntityInfo dupCei
= entry
.getValue();
142 CdmBase dupCb
= (CdmBase
) dupCei
.getObject();
144 String dupCeiFieldName
= "";
145 if(dupCei
.getField() != null) {
146 dupCeiFieldName
= dupCei
.getField().getName();
148 sb
.append(" - " + dupCeiFieldName
+ ":" + dupCb
.getUserFriendlyTypeName() + "/" + dupCb
.getId());
149 if(dupCei
.getParent() != null) {
150 Object dupCbParent
= dupCei
.getParent().getObject();
151 sb
.append(" in entity " + dupCbParent
.getClass().getCanonicalName());
152 if(dupCbParent
instanceof CdmBase
) {
153 sb
.append(" with id : " + ((CdmBase
)dupCbParent
).getId());
156 sb
.append(System
.getProperty("line.separator"));
157 sb
.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(dupCb
));
158 sb
.append(System
.getProperty("line.separator"));
159 sb
.append("-----------");
163 sb
.append(System
.getProperty("line.separator"));
164 sb
.append(System
.getProperty("line.separator"));
166 if(notInCacheList
.isEmpty()) {
167 sb
.append("No Entities found which are not in Cache.");
169 sb
.append("Not In Cache Entities,");
171 for(CdmEntityInfo cei
: notInCacheList
) {
172 CdmBase cb
= (CdmBase
) cei
.getObject();
173 Object cbParent
= cei
.getParent().getObject();
175 sb
.append(System
.getProperty("line.separator"));
177 String fieldName
= "";
178 if(cei
.getField() != null) {
179 fieldName
= cei
.getField().getName();
181 sb
.append(" - " + fieldName
+ ":" + cb
.getUserFriendlyTypeName() + "/" + cb
.getId());
183 if(cbParent
instanceof CdmBase
) {
184 sb
.append(" of entity " + ((CdmBase
)cbParent
).getUserFriendlyTypeName());
186 sb
.append(" of entity " + cbParent
.getClass().getName());
190 sb
.append(System
.getProperty("line.separator"));
191 return sb
.toString();
194 private String
getCachesContainingEntity(CdmBase cdmEntity
) {
195 Cache defaultCache
= CacheManager
.create().getCache(CdmCacher
.DEFAULT_CACHE_NAME
);
197 Element dce
= defaultCache
.get(cdmEntity
.getUuid());
198 if(dce
!= null && dce
.getObjectValue() == cdmEntity
) {
202 Object cte
= cacher
.getFromCache(CdmTransientEntityCacher
.generateKey(cdmEntity
));
203 if(cte
!= null && cte
== cdmEntity
) {
210 private void debug(CdmBase cdmEntity
, boolean recursive
) {
211 if(cdmEntity
== null) {
214 logger
.info("---- starting recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
215 List
<CdmEntityInfo
> alreadyVisitedEntities
= new ArrayList
<CdmEntityInfo
>();
216 CdmEntityInfo cei
= new CdmEntityInfo(ProxyUtils
.deproxy(cdmEntity
));
217 debugRecursive(cdmEntity
, alreadyVisitedEntities
, cei
);
218 rootElements
.add(cei
);
219 alreadyVisitedEntities
.clear();
220 logger
.info("---- ending recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + "\n");
223 private <T
extends Object
> void debugRecursive(T obj
,
224 List
<CdmEntityInfo
> alreadyVisitedEntities
,
229 if(obj
instanceof CdmBase
) {
230 debugRecursive((CdmBase
)obj
, alreadyVisitedEntities
, cei
);
231 } else if (obj
instanceof Map
) {
232 debug((Map
<T
,T
>)obj
, alreadyVisitedEntities
, cei
);
233 } else if (obj
instanceof Collection
) {
234 debug((Collection
<T
>)obj
, alreadyVisitedEntities
, cei
);
237 logger
.info("No caching yet for type " + obj
.getClass().getName());
242 private <T
extends Object
> void debug(Map
<T
,T
> map
,
243 List
<CdmEntityInfo
> alreadyVisitedEntities
,
245 if(map
== null || map
.isEmpty()) {
249 int originalMapSize
= map
.size();
251 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
253 while ( iter
.hasNext() ) {
254 Map
.Entry
<T
,T
> e
= iter
.next();
255 CdmEntityInfo childCei
= new CdmEntityInfo(e
);
256 cei
.addChild(childCei
);
258 CdmEntityInfo keyCei
= new CdmEntityInfo(ProxyUtils
.deproxy(e
.getKey()));
259 childCei
.addChild(keyCei
);
260 CdmEntityInfo valueCei
= new CdmEntityInfo(ProxyUtils
.deproxy(e
.getValue()));
261 childCei
.addChild(valueCei
);
263 debugRecursive(e
.getKey(), alreadyVisitedEntities
, keyCei
);
264 debugRecursive(e
.getValue(), alreadyVisitedEntities
, valueCei
);
268 private <T
extends Object
> void debug(Collection
<T
> collection
,
269 List
<CdmEntityInfo
> alreadyVisitedEntities
,
271 int length
= collection
.size();
272 Object
[] result
= new Object
[length
];
273 Iterator
<T
> collectionItr
= collection
.iterator();
275 while(collectionItr
.hasNext()) {
276 Object obj
= collectionItr
.next();
277 CdmEntityInfo childCei
= new CdmEntityInfo(ProxyUtils
.deproxy(obj
));
278 cei
.addChild(childCei
);
279 debugRecursive(obj
, alreadyVisitedEntities
, childCei
);
285 private void debugRecursive(CdmBase cdmEntity
,
286 List
<CdmEntityInfo
> alreadyVisitedEntities
,
289 CdmBase cachedCdmEntityInSubGraph
= null;
291 if(cei
.getObject() instanceof CdmBase
) {
292 CdmBase cb
= (CdmBase
)cei
.getObject();
293 cachedCdmEntityInSubGraph
= cacher
.getFromCache(cb
);
294 if(cachedCdmEntityInSubGraph
!= cb
) {
295 // found a cdm entity which is not in cache - need to record this
296 //logger.info(" - found entity not in cache " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
297 addEntityNotInCache(cei
);
302 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
303 // since there could be new or deleted objects in the cdmEntity sub-graph
305 // start by getting the fields from the cdm entity
306 String className
= cdmEntity
.getClass().getName();
307 CdmModelFieldPropertyFromClass cmgmfc
= cacher
.getFromCdmlibModelCache(className
);
309 alreadyVisitedEntities
.add(cei
);
310 List
<String
> fields
= cmgmfc
.getFields();
311 for(String field
: fields
) {
312 // retrieve the actual object corresponding to the field.
313 // this object will be either a CdmBase or a Collection / Map
314 // with CdmBase as the generic type
316 CdmEntityInfo childCei
= getDebugCdmBaseTypeFieldValue(cdmEntity
, field
, alreadyVisitedEntities
, cei
);
317 if(!childCei
.isProxy()) {
318 Object object
= childCei
.getObject();
319 if(object
!= null && object
instanceof CdmBase
) {
320 CdmBase cdmEntityInSubGraph
= (CdmBase
)object
;
321 if(!containsIdenticalCdmEntity(alreadyVisitedEntities
, cdmEntityInSubGraph
)) {
322 logger
.info("recursive debugging object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId());
323 debugRecursive(cdmEntityInSubGraph
, alreadyVisitedEntities
, childCei
);
325 logger
.info("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + " already visited");
331 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity
.getClass().getName() + " is not found in the cdmlib model cache. " +
332 "The cache may be corrupted or not in sync with the latest model version" );
338 private CdmEntityInfo
getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity
,
340 List
<CdmEntityInfo
> alreadyVisitedEntities
,
343 CdmEntityInfo childCei
= null;
344 Class
<?
> clazz
= cdmEntity
.getClass();
346 // this call will search in the provided class as well as
347 // the super classes until it finds the field
348 Field field
= ReflectionUtils
.findField(clazz
, fieldName
);
351 throw new CdmClientCacheException("Field '" + fieldName
352 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
354 field
.setAccessible(true);
355 Object o
= field
.get(cdmEntity
);
356 o
= ProxyUtils
.deproxy(o
);
357 CdmBase cdmEntityInSubGraph
= null;
359 boolean isHibernateProxy
= false;
360 boolean isPersistentCollection
= false;
362 childCei
= new CdmEntityInfo(o
);
363 cei
.addChild(childCei
);
364 childCei
.setField(field
);
367 boolean isProxy
= ProxyUtils
.isProxy(o
);
369 childCei
.setProxy(isProxy
);
371 childCei
.setObject(o
);
372 if(CdmBase
.class.isAssignableFrom(o
.getClass())) {
373 logger
.info("found initialised cdm entity '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
374 cdmEntityInSubGraph
= (CdmBase
)o
;
376 //logger.info(" - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
377 CdmEntityInfo dupCei
= getDuplicate(alreadyVisitedEntities
, cdmEntityInSubGraph
);
379 addDuplicateEntity(childCei
, dupCei
);
382 } else if(o
instanceof Map
) {
383 debugRecursive((Map
)o
, alreadyVisitedEntities
, childCei
);
384 } else if(o
instanceof Collection
) {
385 debugRecursive((Collection
)o
, alreadyVisitedEntities
, childCei
);
390 // we return the original cdm entity in the sub graph because we
391 // want to continue to recurse on the input cdm entity graph
392 // and not the one in the cache
395 } catch (SecurityException e
) {
396 throw new CdmClientCacheException(e
);
397 } catch (IllegalArgumentException e
) {
398 throw new CdmClientCacheException(e
);
399 } catch (IllegalAccessException e
) {
400 throw new CdmClientCacheException(e
);
405 private CdmEntityInfo
getDuplicate(List
<CdmEntityInfo
> alreadyVisitedEntities
, Object objectToCompare
) {
406 if(objectToCompare
!= null ) {
407 for(CdmEntityInfo cei
: alreadyVisitedEntities
) {
408 if(objectToCompare
.equals(cei
.getObject()) && objectToCompare
!= cei
.getObject()) {
416 private boolean containsIdenticalCdmEntity(List
<CdmEntityInfo
> ceiSet
, Object objectToCompare
) {
417 boolean foundIdentical
= false;
418 if(objectToCompare
!= null) {
419 for(CdmEntityInfo cei
: ceiSet
) {
420 if(cei
.getObject() == objectToCompare
) {
421 foundIdentical
= true;
423 // } else if(objectToCompare.equals(cei.getObject())) {
428 return foundIdentical
;
431 public class CdmEntityInfo
{
433 private Object object
;
434 private CdmEntityInfo parent
;
435 private List
<CdmEntityInfo
> children
;
437 private String label
;
438 private boolean isProxy
;
440 public CdmEntityInfo(Object object
) {
441 this.object
= object
;
443 children
= new ArrayList
<CdmEntityInfo
>();
446 public CdmEntityInfo
getParent() {
450 public void setParent(CdmEntityInfo parent
) {
451 this.parent
= parent
;
454 public List
<CdmEntityInfo
> getChildren() {
458 public void setChildren(List
<CdmEntityInfo
> children
) {
459 this.children
= children
;
462 public void addChild(CdmEntityInfo cei
) {
463 this.children
.add(cei
);
467 public Field
getField() {
471 public void setField(Field field
) {
476 public String
getLabel() {
478 String fieldName
= "";
480 fieldName
= field
.getName();
484 String className
= object
.getClass().getName();
485 if(object
instanceof HibernateProxy
) {
486 LazyInitializer hli
= ((HibernateProxy
)object
).getHibernateLazyInitializer();
487 if(hli
.isUninitialized()) {
488 className
= "HibernateProxy";
490 className
= "InitialisedHibernateProxy";
492 label
= "[" + className
+ "] " + fieldName
;
493 } else if(object
instanceof PersistentCollection
) {
494 PersistentCollection pc
= ((PersistentCollection
)object
);
495 if(!pc
.wasInitialized()) {
496 className
= "PersistentCollection";
498 className
= "InitialisedPersistentCollection";
500 label
= "[" + className
+ "] " + fieldName
;
501 } else if(object
instanceof Collection
) {
502 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Collection
)object
).size());
503 } else if(object
instanceof Map
) {
504 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Map
)object
).size());
505 } else if(object
instanceof CdmBase
) {
506 label
= getCachesContainingEntity((CdmBase
)object
) + "[" + className
+ ",id" + ((CdmBase
)object
).getId() + "] " + fieldName
+ " : " + object
.toString();
508 label
= "[" + className
+ "] " + fieldName
+ " : " + object
.toString();
511 label
= "[NULL] " + fieldName
;
516 public void setLabel(String label
) {
520 public Object
getObject() {
524 public void setObject(Object object
) {
525 this.object
= object
;
528 public boolean isProxy() {
532 public void setProxy(boolean isProxy
) {
533 this.isProxy
= isProxy
;