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 org
.apache
.log4j
.Logger
;
23 import org
.hibernate
.collection
.spi
.PersistentCollection
;
24 import org
.hibernate
.proxy
.HibernateProxy
;
25 import org
.hibernate
.proxy
.LazyInitializer
;
26 import org
.springframework
.util
.ReflectionUtils
;
28 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
35 public class EntityCacherDebugResult
{
37 private static final Logger logger
= Logger
.getLogger(EntityCacherDebugResult
.class);
39 private Map
<CdmEntityInfo
, CdmEntityInfo
> duplicateCdmEntityMap
;
41 private List
<CdmEntityInfo
> notInCacheList
;
43 private CdmTransientEntityCacher cacher
;
45 private List
<CdmEntityInfo
> rootElements
;
47 StringBuilder debugOutput
= new StringBuilder();
49 public EntityCacherDebugResult() {
53 public <T
extends CdmBase
> EntityCacherDebugResult(CdmTransientEntityCacher cacher
, List
<T
> rootEntities
) {
57 if(rootEntities
!= null && !rootEntities
.isEmpty()) {
58 for(CdmBase rootEntity
: rootEntities
) {
59 debug(rootEntity
, true);
60 String out
= toString(duplicateCdmEntityMap
, notInCacheList
, rootEntity
);
61 System
.out
.println(out
);
62 debugOutput
.append(out
);
70 duplicateCdmEntityMap
= new HashMap
<CdmEntityInfo
, CdmEntityInfo
>();
71 notInCacheList
= new ArrayList
<CdmEntityInfo
>();
72 rootElements
= new ArrayList
<CdmEntityInfo
>();
75 private void clear() {
76 duplicateCdmEntityMap
.clear();
77 notInCacheList
.clear();
80 public void addDuplicateEntity(CdmEntityInfo cei
, CdmEntityInfo cachedCei
) {
81 duplicateCdmEntityMap
.put(cei
, cachedCei
);
84 public void addEntityNotInCache(CdmEntityInfo cei
) {
85 notInCacheList
.add(cei
);
88 public List
<CdmEntityInfo
> getRootElements() {
92 private void print(Map
<CdmEntityInfo
, CdmEntityInfo
> duplicateCdmEntityMap
,
93 List
<CdmEntityInfo
> notInCacheList
,
95 System
.out
.println(toString(duplicateCdmEntityMap
, notInCacheList
, rootEntity
));
100 public String
toString() {
101 return debugOutput
.toString();
104 private String
toString(Map
<CdmEntityInfo
, CdmEntityInfo
> duplicateCdmEntityMap
,
105 List
<CdmEntityInfo
> notInCacheList
,
106 CdmBase rootEntity
) {
109 StringBuilder sb
= new StringBuilder();
110 sb
.append(System
.getProperty("line.separator"));
111 sb
.append("<<< Root Entity " + rootEntity
.getUserFriendlyTypeName() + " with id " + rootEntity
.getId() + " >>>");
112 sb
.append(System
.getProperty("line.separator"));
113 if(duplicateCdmEntityMap
.isEmpty()) {
114 sb
.append("No Duplicate CDM Entities.");
116 sb
.append("Duplicate CDM Entities,");
118 for (Map
.Entry
<CdmEntityInfo
, CdmEntityInfo
> entry
: duplicateCdmEntityMap
.entrySet())
120 sb
.append(System
.getProperty("line.separator"));
121 CdmEntityInfo cei
= entry
.getKey();
122 CdmBase cb
= (CdmBase
) cei
.getObject();
123 Object cbParent
= cei
.getParent().getObject();
125 CdmEntityInfo dupCei
= entry
.getValue();
126 CdmBase dupCb
= (CdmBase
) dupCei
.getObject();
127 Object dupCbParent
= dupCei
.getParent().getObject();
129 sb
.append(" - entity : " + cb
.getUserFriendlyTypeName() + "/" + cb
.getId() +
130 " as member " + cei
.getField().getName() +
131 " of entity " + cbParent
.getClass().getCanonicalName());
132 sb
.append(System
.getProperty("line.separator"));
133 sb
.append(" - duplicate entity : " + dupCb
.getUserFriendlyTypeName() + "/" + dupCb
.getId() +
134 " as field " + dupCei
.getField().getName() +
135 " of entity " + dupCbParent
.getClass().getCanonicalName());
136 sb
.append(System
.getProperty("line.separator"));
137 sb
.append("-----------");
141 sb
.append(System
.getProperty("line.separator"));
142 sb
.append(System
.getProperty("line.separator"));
144 if(notInCacheList
.isEmpty()) {
145 sb
.append("No Entities found which are not in Cache.");
147 sb
.append("Not In Cache Entities,");
149 for(CdmEntityInfo cei
: notInCacheList
) {
150 CdmBase cb
= (CdmBase
) cei
.getObject();
151 Object cbParent
= cei
.getParent().getObject();
153 sb
.append(System
.getProperty("line.separator"));
155 sb
.append(" - entity : " + cb
.getUserFriendlyTypeName() + "/" + cb
.getId() +
156 " as field " + cei
.getField().getName() +
157 " of entity" + cbParent
.getClass().getCanonicalName());
160 sb
.append(System
.getProperty("line.separator"));
161 return sb
.toString();
165 private void debug(CdmBase cdmEntity
, boolean recursive
) {
166 if(cdmEntity
== null) {
169 logger
.info("---- starting recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
170 Set
<CdmEntityInfo
> alreadyVisitedEntities
= new HashSet
<CdmEntityInfo
>();
171 CdmEntityInfo cei
= new CdmEntityInfo(cdmEntity
);
172 debugRecursive(cdmEntity
, alreadyVisitedEntities
, cei
);
173 rootElements
.add(cei
);
174 alreadyVisitedEntities
.clear();
175 logger
.info("---- ending recursive debug for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + "\n");
178 private <T
extends Object
> void debugRecursive(T obj
,
179 Set
<CdmEntityInfo
> alreadyVisitedEntities
,
184 if(obj
instanceof CdmBase
) {
185 debugRecursive((CdmBase
)obj
, alreadyVisitedEntities
, cei
);
186 } else if (obj
instanceof Map
) {
187 debug((Map
<T
,T
>)obj
, alreadyVisitedEntities
, cei
);
188 } else if (obj
instanceof Collection
) {
189 debug((Collection
<T
>)obj
, alreadyVisitedEntities
, cei
);
192 logger
.info("No caching yet for type " + obj
.getClass().getName());
197 private <T
extends Object
> void debug(Map
<T
,T
> map
,
198 Set
<CdmEntityInfo
> alreadyVisitedEntities
,
200 if(map
== null || map
.isEmpty()) {
204 int originalMapSize
= map
.size();
206 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
208 while ( iter
.hasNext() ) {
209 Map
.Entry
<T
,T
> e
= iter
.next();
210 CdmEntityInfo childCei
= new CdmEntityInfo(e
);
211 cei
.addChild(childCei
);
212 debugRecursive(e
.getKey(), alreadyVisitedEntities
, childCei
);
213 debugRecursive(e
.getValue(), alreadyVisitedEntities
, childCei
);
217 private <T
extends Object
> void debug(Collection
<T
> collection
,
218 Set
<CdmEntityInfo
> alreadyVisitedEntities
,
220 int length
= collection
.size();
221 Object
[] result
= new Object
[length
];
222 Iterator
<T
> collectionItr
= collection
.iterator();
224 while(collectionItr
.hasNext()) {
225 Object obj
= collectionItr
.next();
226 CdmEntityInfo childCei
= new CdmEntityInfo(obj
);
227 cei
.addChild(childCei
);
228 debugRecursive(obj
, alreadyVisitedEntities
, childCei
);
234 private void debugRecursive(CdmBase cdmEntity
,
235 Set
<CdmEntityInfo
> alreadyVisitedEntities
,
239 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
240 // since there could be new or deleted objects in the cdmEntity sub-graph
242 // start by getting the fields from the cdm entity
243 String className
= cdmEntity
.getClass().getName();
244 CdmModelFieldPropertyFromClass cmgmfc
= cacher
.getFromCdmlibModelCache(className
);
246 alreadyVisitedEntities
.add(cei
);
247 List
<String
> fields
= cmgmfc
.getFields();
248 for(String field
: fields
) {
249 // retrieve the actual object corresponding to the field.
250 // this object will be either a CdmBase or a Collection / Map
251 // with CdmBase as the generic type
253 CdmEntityInfo childCei
= getDebugCdmBaseTypeFieldValue(cdmEntity
, field
, alreadyVisitedEntities
, cei
);
254 if(!childCei
.isProxy()) {
255 Object object
= childCei
.getObject();
256 if(object
!= null && object
instanceof CdmBase
) {
257 CdmBase cdmEntityInSubGraph
= (CdmBase
)object
;
258 if(!containsIdenticalCdmEntity(alreadyVisitedEntities
, cdmEntityInSubGraph
)) {
259 logger
.info("recursive debugging object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId());
260 debugRecursive(cdmEntityInSubGraph
, alreadyVisitedEntities
, childCei
);
262 logger
.info("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + " already visited");
268 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity
.getClass().getName() + " is not found in the cdmlib model cache. " +
269 "The cache may be corrupted or not in sync with the latest model version" );
275 private CdmEntityInfo
getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity
,
277 Set
<CdmEntityInfo
> alreadyVisitedEntities
,
280 CdmEntityInfo childCei
= null;
281 Class
<?
> clazz
= cdmEntity
.getClass();
283 // this call will search in the provided class as well as
284 // the super classes until it finds the field
285 Field field
= ReflectionUtils
.findField(clazz
, fieldName
);
288 throw new CdmClientCacheException("Field '" + fieldName
289 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
291 field
.setAccessible(true);
292 Object o
= field
.get(cdmEntity
);
294 CdmBase cdmEntityInSubGraph
= null;
296 boolean isHibernateProxy
= false;
297 boolean isPersistentCollection
= false;
299 childCei
= new CdmEntityInfo(o
);
300 cei
.addChild(childCei
);
301 childCei
.setField(field
);
305 if(o
instanceof HibernateProxy
) {
306 LazyInitializer hli
= ((HibernateProxy
)o
).getHibernateLazyInitializer();
307 if(!hli
.isUninitialized()) {
308 o
= hli
.getImplementation();
310 isHibernateProxy
= true;
314 if(o
instanceof PersistentCollection
) {
315 PersistentCollection pc
= ((PersistentCollection
)o
);
316 if(pc
.wasInitialized()) {
317 o
= ProxyUtils
.getObject(pc
);
319 isPersistentCollection
= true;
322 childCei
.setObject(o
);
323 childCei
.setProxy(isHibernateProxy
|| isPersistentCollection
);
324 if(!isHibernateProxy
&& !isPersistentCollection
) {
326 if(CdmBase
.class.isAssignableFrom(o
.getClass())) {
327 logger
.info("found initialised cdm entity '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
328 cdmEntityInSubGraph
= (CdmBase
)o
;
330 //logger.info(" - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
331 CdmEntityInfo dupCei
= getDuplicate(alreadyVisitedEntities
, cdmEntityInSubGraph
);
333 addDuplicateEntity(childCei
, dupCei
);
336 CdmBase cachedCdmEntityInSubGraph
= cacher
.getFromCache(cdmEntityInSubGraph
);
337 // the only exception to updating the field to the latest value
338 // is the case where the field has been already initialised, cached and
339 // is not the same as the one in the cache, in which case we set the value
340 // of the field to the one found in the cache
341 if(cachedCdmEntityInSubGraph
== null) {
342 // found a cdm entity which is not in cache - need to record this
343 //logger.info(" - found entity not in cache " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
344 addEntityNotInCache(childCei
);
347 } else if(o
instanceof Map
) {
348 debugRecursive((Map
)o
, alreadyVisitedEntities
, childCei
);
349 } else if(o
instanceof Collection
) {
350 debugRecursive((Collection
)o
, alreadyVisitedEntities
, childCei
);
355 // we return the original cdm entity in the sub graph because we
356 // want to continue to recurse on the input cdm entity graph
357 // and not the one in the cache
360 } catch (SecurityException e
) {
361 throw new CdmClientCacheException(e
);
362 } catch (IllegalArgumentException e
) {
363 throw new CdmClientCacheException(e
);
364 } catch (IllegalAccessException e
) {
365 throw new CdmClientCacheException(e
);
370 private CdmEntityInfo
getDuplicate(Set
<CdmEntityInfo
> alreadyVisitedEntities
, Object objectToCompare
) {
371 if(objectToCompare
!= null) {
372 for(CdmEntityInfo cei
: alreadyVisitedEntities
) {
373 if(objectToCompare
.equals(cei
.getObject()) && objectToCompare
!= cei
.getObject()) {
381 private boolean containsIdenticalCdmEntity(Set
<CdmEntityInfo
> ceiSet
, Object objectToCompare
) {
382 if(objectToCompare
!= null) {
383 for(CdmEntityInfo cei
: ceiSet
) {
384 if(cei
.getObject() == objectToCompare
) {
392 public class CdmEntityInfo
{
394 private Object object
;
395 private CdmEntityInfo parent
;
396 private List
<CdmEntityInfo
> children
;
398 private String label
;
399 private boolean isProxy
;
401 public CdmEntityInfo(Object object
) {
402 this.object
= object
;
404 children
= new ArrayList
<CdmEntityInfo
>();
407 public CdmEntityInfo
getParent() {
411 public void setParent(CdmEntityInfo parent
) {
412 this.parent
= parent
;
415 public List
<CdmEntityInfo
> getChildren() {
419 public void setChildren(List
<CdmEntityInfo
> children
) {
420 this.children
= children
;
423 public void addChild(CdmEntityInfo cei
) {
424 this.children
.add(cei
);
428 public Field
getField() {
432 public void setField(Field field
) {
437 public String
getLabel() {
439 String fieldName
= "";
441 fieldName
= field
.getName();
444 if(object
instanceof HibernateProxy
) {
445 LazyInitializer hli
= ((HibernateProxy
)object
).getHibernateLazyInitializer();
446 if(hli
.isUninitialized()) {
447 label
= "[HibernateProxy] " + fieldName
;
449 label
= fieldName
+ "Object is a HibernateProxy, but initialised";
451 } else if(object
instanceof PersistentCollection
) {
452 PersistentCollection pc
= ((PersistentCollection
)object
);
453 if(!pc
.wasInitialized()) {
454 label
= "[PersistentCollection] " + fieldName
;
456 label
= fieldName
+ "Object is a PersistentCollection, but initialised";
458 } else if(object
instanceof Collection
) {
459 String className
= object
.getClass().getName();
460 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Collection
)object
).size());
461 } else if(object
instanceof Map
) {
462 String className
= object
.getClass().getName();
463 label
= "[" + className
+ "] " + fieldName
+ " : " + String
.valueOf(((Map
)object
).size());
465 String className
= object
.getClass().getName();
466 label
= "[" + className
+ "] " + fieldName
+ " : " + object
.toString();
469 label
= "[NULL] " + fieldName
;
474 public void setLabel(String label
) {
478 public Object
getObject() {
482 public void setObject(Object object
) {
483 this.object
= object
;
486 public boolean isProxy() {
490 public void setProxy(boolean isProxy
) {
491 this.isProxy
= isProxy
;