Merge branch 'develop' into remoting-4.0
[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
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) {
132
133 sb.append(" with id : " + ((CdmBase)cbParent).getId());
134 }
135 }
136 sb.append(System.getProperty("line.separator"));
137 sb.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(cb));
138 sb.append(System.getProperty("line.separator"));
139
140
141 CdmEntityInfo dupCei = entry.getValue();
142 CdmBase dupCb = (CdmBase) dupCei.getObject();
143
144 String dupCeiFieldName = "";
145 if(dupCei.getField() != null) {
146 dupCeiFieldName = dupCei.getField().getName();
147 }
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());
154 }
155 }
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("-----------");
160 }
161 }
162
163 sb.append(System.getProperty("line.separator"));
164 sb.append(System.getProperty("line.separator"));
165
166 if(notInCacheList.isEmpty()) {
167 sb.append("No Entities found which are not in Cache.");
168 } else {
169 sb.append("Not In Cache Entities,");
170
171 for(CdmEntityInfo cei : notInCacheList) {
172 CdmBase cb = (CdmBase) cei.getObject();
173 Object cbParent = cei.getParent().getObject();
174
175 sb.append(System.getProperty("line.separator"));
176
177 String fieldName = "";
178 if(cei.getField() != null) {
179 fieldName = cei.getField().getName();
180 }
181 sb.append(" - " + fieldName + ":" + cb.getUserFriendlyTypeName() + "/" + cb.getId());
182
183 if(cbParent instanceof CdmBase) {
184 sb.append(" of entity " + ((CdmBase)cbParent).getUserFriendlyTypeName());
185 } else {
186 sb.append(" of entity " + cbParent.getClass().getName());
187 }
188 }
189 }
190 sb.append(System.getProperty("line.separator"));
191 return sb.toString();
192 }
193
194 private String getCachesContainingEntity(CdmBase cdmEntity) {
195 Cache defaultCache = CacheManager.create().getCache(CdmCacher.DEFAULT_CACHE_NAME);
196 String caches = "";
197 Element dce = defaultCache.get(cdmEntity.getUuid());
198 if(dce != null && dce.getObjectValue() == cdmEntity) {
199 caches = "{DC}";
200 }
201
202 Object cte = cacher.getFromCache(CdmTransientEntityCacher.generateKey(cdmEntity));
203 if(cte != null && cte == cdmEntity) {
204 caches += "{TC}";
205 }
206 return caches;
207 }
208
209
210 private void debug(CdmBase cdmEntity, boolean recursive) {
211 if(cdmEntity == null) {
212 return;
213 }
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");
221 }
222
223 private <T extends Object> void debugRecursive(T obj,
224 List<CdmEntityInfo> alreadyVisitedEntities,
225 CdmEntityInfo cei) {
226 if(obj == null) {
227 return;
228 }
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);
235 }
236
237 logger.info("No caching yet for type " + obj.getClass().getName());
238
239
240 }
241
242 private <T extends Object> void debug(Map<T,T> map,
243 List<CdmEntityInfo> alreadyVisitedEntities,
244 CdmEntityInfo cei) {
245 if(map == null || map.isEmpty()) {
246 return;
247 }
248
249 int originalMapSize = map.size();
250
251 Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
252 int i=0;
253 while ( iter.hasNext() ) {
254 Map.Entry<T,T> e = iter.next();
255 CdmEntityInfo childCei = new CdmEntityInfo(e);
256 cei.addChild(childCei);
257
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);
262
263 debugRecursive(e.getKey(), alreadyVisitedEntities, keyCei);
264 debugRecursive(e.getValue(), alreadyVisitedEntities, valueCei);
265 }
266 }
267
268 private <T extends Object> void debug(Collection<T> collection,
269 List<CdmEntityInfo> alreadyVisitedEntities,
270 CdmEntityInfo cei) {
271 int length = collection.size();
272 Object[] result = new Object[length];
273 Iterator<T> collectionItr = collection.iterator();
274
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);
280
281 }
282
283 }
284
285 private void debugRecursive(CdmBase cdmEntity,
286 List<CdmEntityInfo> alreadyVisitedEntities,
287 CdmEntityInfo cei) {
288
289 CdmBase cachedCdmEntityInSubGraph = null;
290
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);
298 }
299 }
300
301
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
304
305 // start by getting the fields from the cdm entity
306 String className = cdmEntity.getClass().getName();
307 CdmModelFieldPropertyFromClass cmgmfc = cacher.getFromCdmlibModelCache(className);
308 if(cmgmfc != null) {
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
315 String f = field;
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);
324 } else {
325 logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
326 }
327 }
328 }
329 }
330 } else {
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" );
333 }
334
335 }
336
337
338 private CdmEntityInfo getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity,
339 String fieldName,
340 List<CdmEntityInfo> alreadyVisitedEntities,
341 CdmEntityInfo cei) {
342
343 CdmEntityInfo childCei = null;
344 Class<?> clazz = cdmEntity.getClass();
345 try {
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);
349
350 if(field == null) {
351 throw new CdmClientCacheException("Field '" + fieldName
352 + "' not found when searching in class '" + clazz.getName() + "' and its supercalsses");
353 }
354 field.setAccessible(true);
355 Object o = field.get(cdmEntity);
356 o = ProxyUtils.deproxy(o);
357 CdmBase cdmEntityInSubGraph = null;
358
359 boolean isHibernateProxy = false;
360 boolean isPersistentCollection = false;
361
362 childCei = new CdmEntityInfo(o);
363 cei.addChild(childCei);
364 childCei.setField(field);
365
366 if(o != null) {
367 boolean isProxy = ProxyUtils.isProxy(o);
368
369 childCei.setProxy(isProxy);
370 if(!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;
375
376 //logger.info(" - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
377 CdmEntityInfo dupCei = getDuplicate(alreadyVisitedEntities, cdmEntityInSubGraph);
378 if(dupCei != null) {
379 addDuplicateEntity(childCei, dupCei);
380 }
381
382 } else if(o instanceof Map) {
383 debugRecursive((Map)o, alreadyVisitedEntities, childCei);
384 } else if(o instanceof Collection) {
385 debugRecursive((Collection)o, alreadyVisitedEntities, childCei);
386 }
387
388 }
389 }
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
393
394 return childCei;
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);
401 }
402 }
403
404
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()) {
409 return cei;
410 }
411 }
412 }
413 return null;
414 }
415
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;
422 } else if(objectToCompare.equals(cei.getObject())) {
423 return false;
424 }
425 }
426 }
427 return foundIdentical;
428 }
429
430 public class CdmEntityInfo {
431
432 private Object object;
433 private CdmEntityInfo parent;
434 private List<CdmEntityInfo> children;
435 private Field field;
436 private String label;
437 private boolean isProxy;
438
439 public CdmEntityInfo(Object object) {
440 this.object = object;
441 isProxy = false;
442 children = new ArrayList<CdmEntityInfo>();
443 }
444
445 public CdmEntityInfo getParent() {
446 return parent;
447 }
448
449 public void setParent(CdmEntityInfo parent) {
450 this.parent = parent;
451 }
452
453 public List<CdmEntityInfo> getChildren() {
454 return children;
455 }
456
457 public void setChildren(List<CdmEntityInfo> children) {
458 this.children = children;
459 }
460
461 public void addChild(CdmEntityInfo cei) {
462 this.children.add(cei);
463 cei.setParent(this);
464 }
465
466 public Field getField() {
467 return field;
468 }
469
470 public void setField(Field field) {
471 this.field = field;
472 }
473
474
475 public String getLabel() {
476 String label;
477 String fieldName = "";
478 if(field != null) {
479 fieldName = field.getName();
480 }
481
482 if(object != null) {
483 String className = object.getClass().getName();
484 if(object instanceof HibernateProxy) {
485 LazyInitializer hli = ((HibernateProxy)object).getHibernateLazyInitializer();
486 if(hli.isUninitialized()) {
487 className = "HibernateProxy";
488 } else {
489 className = "InitialisedHibernateProxy";
490 }
491 label = "[" + className + "] " + fieldName;
492 } else if(object instanceof PersistentCollection) {
493 PersistentCollection pc = ((PersistentCollection)object);
494 if(!pc.wasInitialized()) {
495 className = "PersistentCollection";
496 } else {
497 className = "InitialisedPersistentCollection";
498 }
499 label = "[" + className + "] " + fieldName;
500 } else if(object instanceof Collection) {
501 label = "[" + className + "] " + fieldName + " : " + String.valueOf(((Collection)object).size());
502 } else if(object instanceof Map) {
503 label = "[" + className + "] " + fieldName + " : " + String.valueOf(((Map)object).size());
504 } else if(object instanceof CdmBase) {
505 label = getCachesContainingEntity((CdmBase)object) + "[" + className + ",id" + ((CdmBase)object).getId() + "] " + fieldName + " : " + object.toString();
506 } else {
507 label = "[" + className + "] " + fieldName + " : " + object.toString();
508 }
509 } else {
510 label = "[NULL] " + fieldName;
511 }
512 return label;
513 }
514
515 public void setLabel(String label) {
516 this.label = label;
517 }
518
519 public Object getObject() {
520 return object;
521 }
522
523 public void setObject(Object object) {
524 this.object = object;
525 }
526
527 public boolean isProxy() {
528 return isProxy;
529 }
530
531 public void setProxy(boolean isProxy) {
532 this.isProxy = isProxy;
533 }
534
535
536
537 }
538
539 }