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
.HashSet
;
17 import java
.util
.Iterator
;
18 import java
.util
.List
;
22 import net
.sf
.ehcache
.Cache
;
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
, List
<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 sb
.append(" - " + cei
.getField().getName() + ":" + cb
.getUserFriendlyTypeName() + "/" + cb
.getId());
171 if(cbParent
instanceof CdmBase
) {
172 sb
.append(" of entity " + ((CdmBase
)cbParent
).getUserFriendlyTypeName());
174 sb
.append(" of entity " + cbParent
.getClass().getName());
178 sb
.append(System
.getProperty("line.separator"));
179 return sb
.toString();
182 private String
getCachesContainingEntity(CdmBase cdmEntity
) {
183 Cache defaultCache
= CdmRemoteCacheManager
.getInstance().getDefaultCacheManager().getCache(CdmCacher
.DEFAULT_CACHE_NAME
);
185 Object dce
= defaultCache
.get(cdmEntity
.getUuid());
186 if(dce
!= null && dce
== cdmEntity
) {
187 caches
= CdmCacher
.DEFAULT_CACHE_NAME
;
189 if(!caches
.isEmpty()) {
192 Object cte
= cacher
.getFromCache(CdmTransientEntityCacher
.generateKey(cdmEntity
));
193 if(cte
!= null && cte
== cdmEntity
) {
194 caches
+= cacher
.toString();
200 private void debug(CdmBase cdmEntity
, boolean recursive
) {
201 if(cdmEntity
== null) {
204 logger
.info("---- starting recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
205 Set
<CdmEntityInfo
> alreadyVisitedEntities
= new HashSet
<CdmEntityInfo
>();
206 CdmEntityInfo cei
= new CdmEntityInfo(cdmEntity
);
207 debugRecursive(cdmEntity
, alreadyVisitedEntities
, cei
);
208 rootElements
.add(cei
);
209 alreadyVisitedEntities
.clear();
210 logger
.info("---- ending recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + "\n");
213 private <T
extends Object
> void debugRecursive(T obj
,
214 Set
<CdmEntityInfo
> alreadyVisitedEntities
,
219 if(obj
instanceof CdmBase
) {
220 debugRecursive((CdmBase
)obj
, alreadyVisitedEntities
, cei
);
221 } else if (obj
instanceof Map
) {
222 debug((Map
<T
,T
>)obj
, alreadyVisitedEntities
, cei
);
223 } else if (obj
instanceof Collection
) {
224 debug((Collection
<T
>)obj
, alreadyVisitedEntities
, cei
);
227 logger
.info("No caching yet for type " + obj
.getClass().getName());
232 private <T
extends Object
> void debug(Map
<T
,T
> map
,
233 Set
<CdmEntityInfo
> alreadyVisitedEntities
,
235 if(map
== null || map
.isEmpty()) {
239 int originalMapSize
= map
.size();
241 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
243 while ( iter
.hasNext() ) {
244 Map
.Entry
<T
,T
> e
= iter
.next();
245 CdmEntityInfo childCei
= new CdmEntityInfo(e
);
246 cei
.addChild(childCei
);
247 debugRecursive(e
.getKey(), alreadyVisitedEntities
, childCei
);
248 debugRecursive(e
.getValue(), alreadyVisitedEntities
, childCei
);
252 private <T
extends Object
> void debug(Collection
<T
> collection
,
253 Set
<CdmEntityInfo
> alreadyVisitedEntities
,
255 int length
= collection
.size();
256 Object
[] result
= new Object
[length
];
257 Iterator
<T
> collectionItr
= collection
.iterator();
259 while(collectionItr
.hasNext()) {
260 Object obj
= collectionItr
.next();
261 CdmEntityInfo childCei
= new CdmEntityInfo(obj
);
262 cei
.addChild(childCei
);
263 debugRecursive(obj
, alreadyVisitedEntities
, childCei
);
269 private void debugRecursive(CdmBase cdmEntity
,
270 Set
<CdmEntityInfo
> alreadyVisitedEntities
,
274 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
275 // since there could be new or deleted objects in the cdmEntity sub-graph
277 // start by getting the fields from the cdm entity
278 String className
= cdmEntity
.getClass().getName();
279 CdmModelFieldPropertyFromClass cmgmfc
= cacher
.getFromCdmlibModelCache(className
);
281 alreadyVisitedEntities
.add(cei
);
282 List
<String
> fields
= cmgmfc
.getFields();
283 for(String field
: fields
) {
284 // retrieve the actual object corresponding to the field.
285 // this object will be either a CdmBase or a Collection / Map
286 // with CdmBase as the generic type
288 CdmEntityInfo childCei
= getDebugCdmBaseTypeFieldValue(cdmEntity
, field
, alreadyVisitedEntities
, cei
);
289 if(!childCei
.isProxy()) {
290 Object object
= childCei
.getObject();
291 if(object
!= null && object
instanceof CdmBase
) {
292 CdmBase cdmEntityInSubGraph
= (CdmBase
)object
;
293 if(!containsIdenticalCdmEntity(alreadyVisitedEntities
, cdmEntityInSubGraph
)) {
294 logger
.info("recursive debugging object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId());
295 debugRecursive(cdmEntityInSubGraph
, alreadyVisitedEntities
, childCei
);
297 logger
.info("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + " already visited");
303 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity
.getClass().getName() + " is not found in the cdmlib model cache. " +
304 "The cache may be corrupted or not in sync with the latest model version" );
310 private CdmEntityInfo
getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity
,
312 Set
<CdmEntityInfo
> alreadyVisitedEntities
,
315 CdmEntityInfo childCei
= null;
316 Class
<?
> clazz
= cdmEntity
.getClass();
318 // this call will search in the provided class as well as
319 // the super classes until it finds the field
320 Field field
= ReflectionUtils
.findField(clazz
, fieldName
);
323 throw new CdmClientCacheException("Field '" + fieldName
324 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
326 field
.setAccessible(true);
327 Object o
= field
.get(cdmEntity
);
329 CdmBase cdmEntityInSubGraph
= null;
331 boolean isHibernateProxy
= false;
332 boolean isPersistentCollection
= false;
334 childCei
= new CdmEntityInfo(o
);
335 cei
.addChild(childCei
);
336 childCei
.setField(field
);
340 if(o
instanceof HibernateProxy
) {
341 LazyInitializer hli
= ((HibernateProxy
)o
).getHibernateLazyInitializer();
342 if(!hli
.isUninitialized()) {
343 o
= hli
.getImplementation();
345 isHibernateProxy
= true;
349 if(o
instanceof PersistentCollection
) {
350 PersistentCollection pc
= ((PersistentCollection
)o
);
351 if(pc
.wasInitialized()) {
352 o
= ProxyUtils
.getObject(pc
);
354 isPersistentCollection
= true;
357 childCei
.setObject(o
);
358 childCei
.setProxy(isHibernateProxy
|| isPersistentCollection
);
359 if(!isHibernateProxy
&& !isPersistentCollection
) {
361 if(CdmBase
.class.isAssignableFrom(o
.getClass())) {
362 logger
.info("found initialised cdm entity '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
363 cdmEntityInSubGraph
= (CdmBase
)o
;
365 //logger.info(" - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
366 CdmEntityInfo dupCei
= getDuplicate(alreadyVisitedEntities
, cdmEntityInSubGraph
);
368 addDuplicateEntity(childCei
, dupCei
);
371 CdmBase cachedCdmEntityInSubGraph
= cacher
.getFromCache(cdmEntityInSubGraph
);
372 // the only exception to updating the field to the latest value
373 // is the case where the field has been already initialised, cached and
374 // is not the same as the one in the cache, in which case we set the value
375 // of the field to the one found in the cache
376 if(cachedCdmEntityInSubGraph
== null) {
377 // found a cdm entity which is not in cache - need to record this
378 //logger.info(" - found entity not in cache " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
379 addEntityNotInCache(childCei
);
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(Set
<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(Set
<CdmEntityInfo
> ceiSet
, Object objectToCompare
) {
417 if(objectToCompare
!= null) {
418 for(CdmEntityInfo cei
: ceiSet
) {
419 if(cei
.getObject() == objectToCompare
) {
427 public class CdmEntityInfo
{
429 private Object object
;
430 private CdmEntityInfo parent
;
431 private List
<CdmEntityInfo
> children
;
433 private String label
;
434 private boolean isProxy
;
436 public CdmEntityInfo(Object object
) {
437 this.object
= object
;
439 children
= new ArrayList
<CdmEntityInfo
>();
442 public CdmEntityInfo
getParent() {
446 public void setParent(CdmEntityInfo parent
) {
447 this.parent
= parent
;
450 public List
<CdmEntityInfo
> getChildren() {
454 public void setChildren(List
<CdmEntityInfo
> children
) {
455 this.children
= children
;
458 public void addChild(CdmEntityInfo cei
) {
459 this.children
.add(cei
);
463 public Field
getField() {
467 public void setField(Field field
) {
472 public String
getLabel() {
474 String fieldName
= "";
476 fieldName
= field
.getName();
480 String className
= object
.getClass().getName();
481 if(object
instanceof HibernateProxy
) {
482 LazyInitializer hli
= ((HibernateProxy
)object
).getHibernateLazyInitializer();
483 if(hli
.isUninitialized()) {
484 className
= "HibernateProxy";
486 className
= "InitialisedHibernateProxy";
488 label
= "[" + className
+ "] " + fieldName
;
489 } else if(object
instanceof PersistentCollection
) {
490 PersistentCollection pc
= ((PersistentCollection
)object
);
491 if(!pc
.wasInitialized()) {
492 className
= "PersistentCollection";
494 className
= "InitialisedPersistentCollection";
496 label
= "[" + className
+ "] " + fieldName
;
497 } else if(object
instanceof Collection
) {
498 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Collection
)object
).size());
499 } else if(object
instanceof Map
) {
501 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Map
)object
).size());
502 } else if(object
instanceof CdmBase
) {
503 label
= "[" + className
+ ",id" + ((CdmBase
)object
).getId() + "] " + fieldName
+ " : " + object
.toString();
505 label
= "[" + className
+ "] " + fieldName
+ " : " + object
.toString();
508 label
= "[NULL] " + fieldName
;
513 public void setLabel(String label
) {
517 public Object
getObject() {
521 public void setObject(Object object
) {
522 this.object
= object
;
525 public boolean isProxy() {
529 public void setProxy(boolean isProxy
) {
530 this.isProxy
= isProxy
;