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