ca7ac3502b6cc8a8ddc23c3732e045f5393335e8
[taxeditor.git] / eu.etaxonomy.taxeditor.cdmlib / src / main / java / eu / etaxonomy / taxeditor / remoting / cache / EntityCacherDebugResult.java
1 // $Id$
2 /**
3 * Copyright (C) 2015 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
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.
9 */
10 package eu.etaxonomy.taxeditor.remoting.cache;
11
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;
18 import java.util.Map;
19
20 import net.sf.ehcache.Cache;
21 import net.sf.ehcache.CacheManager;
22 import net.sf.ehcache.Element;
23
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;
29
30 import eu.etaxonomy.cdm.api.cache.CdmCacher;
31 import eu.etaxonomy.cdm.model.common.CdmBase;
32
33 /**
34 * @author cmathew
35 * @date 9 Feb 2015
36 *
37 */
38 public class EntityCacherDebugResult {
39
40 private static final Logger logger = Logger.getLogger(EntityCacherDebugResult.class);
41
42 private Map<CdmEntityInfo, CdmEntityInfo> duplicateCdmEntityMap;
43
44 private List<CdmEntityInfo> notInCacheList;
45
46 private CdmTransientEntityCacher cacher;
47
48 private List<CdmEntityInfo> rootElements;
49
50 StringBuilder debugOutput = new StringBuilder();
51
52 public EntityCacherDebugResult() {
53 }
54
55
56 public <T extends CdmBase> EntityCacherDebugResult(CdmTransientEntityCacher cacher, Collection<T> rootEntities) {
57 this.cacher = cacher;
58 init();
59
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);
66 clear();
67 }
68
69 }
70 }
71
72 private void init() {
73 duplicateCdmEntityMap = new HashMap<CdmEntityInfo, CdmEntityInfo>();
74 notInCacheList = new ArrayList<CdmEntityInfo>();
75 rootElements = new ArrayList<CdmEntityInfo>();
76 }
77
78 private void clear() {
79 duplicateCdmEntityMap.clear();
80 notInCacheList.clear();
81 }
82
83 public void addDuplicateEntity(CdmEntityInfo cei, CdmEntityInfo cachedCei) {
84 duplicateCdmEntityMap.put(cei, cachedCei);
85 }
86
87 public void addEntityNotInCache(CdmEntityInfo cei) {
88 notInCacheList.add(cei);
89 }
90
91 public List<CdmEntityInfo> getRootElements() {
92 return rootElements;
93 }
94
95 private void print(Map<CdmEntityInfo, CdmEntityInfo> duplicateCdmEntityMap,
96 List<CdmEntityInfo> notInCacheList,
97 CdmBase rootEntity) {
98 System.out.println(toString(duplicateCdmEntityMap, notInCacheList, rootEntity));
99 }
100
101
102 @Override
103 public String toString() {
104 return debugOutput.toString();
105 }
106
107 private String toString(Map<CdmEntityInfo, CdmEntityInfo> duplicateCdmEntityMap,
108 List<CdmEntityInfo> notInCacheList,
109 CdmBase rootEntity) {
110
111
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.");
118 } else {
119 sb.append("Duplicate CDM Entities,");
120
121 for (Map.Entry<CdmEntityInfo, CdmEntityInfo> entry : duplicateCdmEntityMap.entrySet())
122 {
123 sb.append(System.getProperty("line.separator"));
124 CdmEntityInfo cei = entry.getKey();
125 CdmBase cb = (CdmBase) cei.getObject();
126 Object cbParent = cei.getParent().getObject();
127
128
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());
133 }
134 sb.append(System.getProperty("line.separator"));
135 sb.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(cb));
136 sb.append(System.getProperty("line.separator"));
137
138
139 CdmEntityInfo dupCei = entry.getValue();
140 CdmBase dupCb = (CdmBase) dupCei.getObject();
141 Object dupCbParent = dupCei.getParent().getObject();
142
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());
147 }
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("-----------");
152 }
153 }
154
155 sb.append(System.getProperty("line.separator"));
156 sb.append(System.getProperty("line.separator"));
157
158 if(notInCacheList.isEmpty()) {
159 sb.append("No Entities found which are not in Cache.");
160 } else {
161 sb.append("Not In Cache Entities,");
162
163 for(CdmEntityInfo cei : notInCacheList) {
164 CdmBase cb = (CdmBase) cei.getObject();
165 Object cbParent = cei.getParent().getObject();
166
167 sb.append(System.getProperty("line.separator"));
168
169 String fieldName = "";
170 if(cei.getField() != null) {
171 fieldName = cei.getField().getName();
172 }
173 sb.append(" - " + fieldName + ":" + cb.getUserFriendlyTypeName() + "/" + cb.getId());
174
175 if(cbParent instanceof CdmBase) {
176 sb.append(" of entity " + ((CdmBase)cbParent).getUserFriendlyTypeName());
177 } else {
178 sb.append(" of entity " + cbParent.getClass().getName());
179 }
180 }
181 }
182 sb.append(System.getProperty("line.separator"));
183 return sb.toString();
184 }
185
186 private String getCachesContainingEntity(CdmBase cdmEntity) {
187 Cache defaultCache = CacheManager.create().getCache(CdmCacher.DEFAULT_CACHE_NAME);
188 String caches = "";
189 Element dce = defaultCache.get(cdmEntity.getUuid());
190 if(dce != null && dce.getObjectValue() == cdmEntity) {
191 caches = "{DC}";
192 }
193
194 Object cte = cacher.getFromCache(CdmTransientEntityCacher.generateKey(cdmEntity));
195 if(cte != null && cte == cdmEntity) {
196 caches += "{TC}";
197 }
198 return caches;
199 }
200
201
202 private void debug(CdmBase cdmEntity, boolean recursive) {
203 if(cdmEntity == null) {
204 return;
205 }
206 logger.info("---- starting recursive debug for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
207 List<CdmEntityInfo> alreadyVisitedEntities = new ArrayList<CdmEntityInfo>();
208 CdmEntityInfo cei = new CdmEntityInfo(cdmEntity);
209 debugRecursive(cdmEntity, alreadyVisitedEntities, cei);
210 rootElements.add(cei);
211 alreadyVisitedEntities.clear();
212 logger.info("---- ending recursive debug for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + "\n");
213 }
214
215 private <T extends Object> void debugRecursive(T obj,
216 List<CdmEntityInfo> alreadyVisitedEntities,
217 CdmEntityInfo cei) {
218 if(obj == null) {
219 return;
220 }
221 if(obj instanceof CdmBase) {
222 debugRecursive((CdmBase)obj, alreadyVisitedEntities, cei);
223 } else if (obj instanceof Map) {
224 debug((Map<T,T>)obj, alreadyVisitedEntities, cei);
225 } else if (obj instanceof Collection) {
226 debug((Collection<T>)obj, alreadyVisitedEntities, cei);
227 }
228
229 logger.info("No caching yet for type " + obj.getClass().getName());
230
231
232 }
233
234 private <T extends Object> void debug(Map<T,T> map,
235 List<CdmEntityInfo> alreadyVisitedEntities,
236 CdmEntityInfo cei) {
237 if(map == null || map.isEmpty()) {
238 return;
239 }
240
241 int originalMapSize = map.size();
242
243 Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
244 int i=0;
245 while ( iter.hasNext() ) {
246 Map.Entry<T,T> e = iter.next();
247 CdmEntityInfo childCei = new CdmEntityInfo(e);
248 cei.addChild(childCei);
249 debugRecursive(e.getKey(), alreadyVisitedEntities, childCei);
250 debugRecursive(e.getValue(), alreadyVisitedEntities, childCei);
251 }
252 }
253
254 private <T extends Object> void debug(Collection<T> collection,
255 List<CdmEntityInfo> alreadyVisitedEntities,
256 CdmEntityInfo cei) {
257 int length = collection.size();
258 Object[] result = new Object[length];
259 Iterator<T> collectionItr = collection.iterator();
260
261 while(collectionItr.hasNext()) {
262 Object obj = collectionItr.next();
263 CdmEntityInfo childCei = new CdmEntityInfo(obj);
264 cei.addChild(childCei);
265 debugRecursive(obj, alreadyVisitedEntities, childCei);
266
267 }
268
269 }
270
271 private void debugRecursive(CdmBase cdmEntity,
272 List<CdmEntityInfo> alreadyVisitedEntities,
273 CdmEntityInfo cei) {
274
275 CdmBase cachedCdmEntityInSubGraph = null;
276
277 if(cei.getObject() instanceof CdmBase) {
278 CdmBase cb = (CdmBase)cei.getObject();
279 cachedCdmEntityInSubGraph = cacher.getFromCache(cb);
280 if(cachedCdmEntityInSubGraph != cb) {
281 // found a cdm entity which is not in cache - need to record this
282 //logger.info(" - found entity not in cache " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
283 addEntityNotInCache(cei);
284 }
285 }
286
287
288 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
289 // since there could be new or deleted objects in the cdmEntity sub-graph
290
291 // start by getting the fields from the cdm entity
292 String className = cdmEntity.getClass().getName();
293 CdmModelFieldPropertyFromClass cmgmfc = cacher.getFromCdmlibModelCache(className);
294 if(cmgmfc != null) {
295 alreadyVisitedEntities.add(cei);
296 List<String> fields = cmgmfc.getFields();
297 for(String field : fields) {
298 // retrieve the actual object corresponding to the field.
299 // this object will be either a CdmBase or a Collection / Map
300 // with CdmBase as the generic type
301 String f = field;
302 CdmEntityInfo childCei = getDebugCdmBaseTypeFieldValue(cdmEntity, field, alreadyVisitedEntities, cei);
303 if(!childCei.isProxy()) {
304 Object object = childCei.getObject();
305 if(object != null && object instanceof CdmBase) {
306 CdmBase cdmEntityInSubGraph = (CdmBase)object;
307 if(!containsIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph)) {
308 logger.info("recursive debugging object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
309 debugRecursive(cdmEntityInSubGraph, alreadyVisitedEntities, childCei);
310 } else {
311 logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
312 }
313 }
314 }
315 }
316 } else {
317 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
318 "The cache may be corrupted or not in sync with the latest model version" );
319 }
320
321 }
322
323
324 private CdmEntityInfo getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity,
325 String fieldName,
326 List<CdmEntityInfo> alreadyVisitedEntities,
327 CdmEntityInfo cei) {
328
329 CdmEntityInfo childCei = null;
330 Class<?> clazz = cdmEntity.getClass();
331 try {
332 // this call will search in the provided class as well as
333 // the super classes until it finds the field
334 Field field = ReflectionUtils.findField(clazz, fieldName);
335
336 if(field == null) {
337 throw new CdmClientCacheException("Field '" + fieldName
338 + "' not found when searching in class '" + clazz.getName() + "' and its supercalsses");
339 }
340 field.setAccessible(true);
341 Object o = field.get(cdmEntity);
342
343 CdmBase cdmEntityInSubGraph = null;
344
345 boolean isHibernateProxy = false;
346 boolean isPersistentCollection = false;
347
348 childCei = new CdmEntityInfo(o);
349 cei.addChild(childCei);
350 childCei.setField(field);
351
352 if(o != null) {
353
354 if(o instanceof HibernateProxy) {
355 isHibernateProxy = true;
356 }
357
358 if(o instanceof PersistentCollection) {
359 isPersistentCollection = true;
360 }
361 childCei.setObject(o);
362 childCei.setProxy(isHibernateProxy || isPersistentCollection);
363 if(!isHibernateProxy && !isPersistentCollection) {
364
365 if(CdmBase.class.isAssignableFrom(o.getClass())) {
366 logger.info("found initialised cdm entity '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
367 cdmEntityInSubGraph = (CdmBase)o;
368
369 //logger.info(" - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
370 CdmEntityInfo dupCei = getDuplicate(alreadyVisitedEntities, cdmEntityInSubGraph);
371 if(dupCei != null) {
372 addDuplicateEntity(childCei, dupCei);
373 }
374
375 } else if(o instanceof Map) {
376 debugRecursive((Map)o, alreadyVisitedEntities, childCei);
377 } else if(o instanceof Collection) {
378 debugRecursive((Collection)o, alreadyVisitedEntities, childCei);
379 }
380
381 }
382 }
383 // we return the original cdm entity in the sub graph because we
384 // want to continue to recurse on the input cdm entity graph
385 // and not the one in the cache
386
387 return childCei;
388 } catch (SecurityException e) {
389 throw new CdmClientCacheException(e);
390 } catch (IllegalArgumentException e) {
391 throw new CdmClientCacheException(e);
392 } catch (IllegalAccessException e) {
393 throw new CdmClientCacheException(e);
394 }
395 }
396
397
398 private CdmEntityInfo getDuplicate(List<CdmEntityInfo> alreadyVisitedEntities, Object objectToCompare) {
399 if(objectToCompare != null ) {
400 for(CdmEntityInfo cei: alreadyVisitedEntities) {
401 if(objectToCompare.equals(cei.getObject()) && objectToCompare != cei.getObject()) {
402 return cei;
403 }
404 }
405 }
406 return null;
407 }
408
409 private boolean containsIdenticalCdmEntity(List<CdmEntityInfo> ceiSet, Object objectToCompare) {
410 boolean foundIdentical = false;
411 if(objectToCompare != null) {
412 for(CdmEntityInfo cei : ceiSet) {
413 if(cei.getObject() == objectToCompare) {
414 foundIdentical = true;
415 } else if(objectToCompare.equals(cei.getObject())) {
416 return false;
417 }
418 }
419 }
420 return foundIdentical;
421 }
422
423 public class CdmEntityInfo {
424
425 private Object object;
426 private CdmEntityInfo parent;
427 private List<CdmEntityInfo> children;
428 private Field field;
429 private String label;
430 private boolean isProxy;
431
432 public CdmEntityInfo(Object object) {
433 this.object = object;
434 isProxy = false;
435 children = new ArrayList<CdmEntityInfo>();
436 }
437
438 public CdmEntityInfo getParent() {
439 return parent;
440 }
441
442 public void setParent(CdmEntityInfo parent) {
443 this.parent = parent;
444 }
445
446 public List<CdmEntityInfo> getChildren() {
447 return children;
448 }
449
450 public void setChildren(List<CdmEntityInfo> children) {
451 this.children = children;
452 }
453
454 public void addChild(CdmEntityInfo cei) {
455 this.children.add(cei);
456 cei.setParent(this);
457 }
458
459 public Field getField() {
460 return field;
461 }
462
463 public void setField(Field field) {
464 this.field = field;
465 }
466
467
468 public String getLabel() {
469 String label;
470 String fieldName = "";
471 if(field != null) {
472 fieldName = field.getName();
473 }
474
475 if(object != null) {
476 String className = object.getClass().getName();
477 if(object instanceof HibernateProxy) {
478 LazyInitializer hli = ((HibernateProxy)object).getHibernateLazyInitializer();
479 if(hli.isUninitialized()) {
480 className = "HibernateProxy";
481 } else {
482 className = "InitialisedHibernateProxy";
483 }
484 label = "[" + className + "] " + fieldName;
485 } else if(object instanceof PersistentCollection) {
486 PersistentCollection pc = ((PersistentCollection)object);
487 if(!pc.wasInitialized()) {
488 className = "PersistentCollection";
489 } else {
490 className = "InitialisedPersistentCollection";
491 }
492 label = "[" + className + "] " + fieldName;
493 } else if(object instanceof Collection) {
494 label = "[" + className + "] " + fieldName + " : " + String.valueOf(((Collection)object).size());
495 } else if(object instanceof Map) {
496
497 label = "[" + className + "] " + fieldName + " : " + String.valueOf(((Map)object).size());
498 } else if(object instanceof CdmBase) {
499 label = getCachesContainingEntity((CdmBase)object) + "[" + className + ",id" + ((CdmBase)object).getId() + "] " + fieldName + " : " + object.toString();
500 } else {
501 label = "[" + className + "] " + fieldName + " : " + object.toString();
502 }
503 } else {
504 label = "[NULL] " + fieldName;
505 }
506 return label;
507 }
508
509 public void setLabel(String label) {
510 this.label = label;
511 }
512
513 public Object getObject() {
514 return object;
515 }
516
517 public void setObject(Object object) {
518 this.object = object;
519 }
520
521 public boolean isProxy() {
522 return isProxy;
523 }
524
525 public void setProxy(boolean isProxy) {
526 this.isProxy = isProxy;
527 }
528
529
530
531 }
532
533 }