Merge branch 'develop' into LibrAlign
[taxeditor.git] / eu.etaxonomy.taxeditor.cdmlib / src / main / java / eu / etaxonomy / taxeditor / remoting / cache / EntityCacherDebugResult.java
1 /**
2 * Copyright (C) 2015 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.taxeditor.remoting.cache;
10
11 import java.lang.reflect.Field;
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.HashMap;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Map;
18
19 import net.sf.ehcache.Cache;
20 import net.sf.ehcache.CacheManager;
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, Collection<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
126 sb.append(" - " + cei.getField().getName() + ":" + cb.getUserFriendlyTypeName() + "/" + cb.getId());
127 if(cei.getParent() != null) {
128 Object cbParent = cei.getParent().getObject();
129 sb.append(" in entity " + cbParent.getClass().getCanonicalName());
130 if(cbParent instanceof CdmBase) {
131
132 sb.append(" with id : " + ((CdmBase)cbParent).getId());
133 }
134 }
135 sb.append(System.getProperty("line.separator"));
136 sb.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(cb));
137 sb.append(System.getProperty("line.separator"));
138
139
140 CdmEntityInfo dupCei = entry.getValue();
141 CdmBase dupCb = (CdmBase) dupCei.getObject();
142
143 String dupCeiFieldName = "";
144 if(dupCei.getField() != null) {
145 dupCeiFieldName = dupCei.getField().getName();
146 }
147 sb.append(" - " + dupCeiFieldName + ":" + dupCb.getUserFriendlyTypeName() + "/" + dupCb.getId());
148 if(dupCei.getParent() != null) {
149 Object dupCbParent = dupCei.getParent().getObject();
150 sb.append(" in entity " + dupCbParent.getClass().getCanonicalName());
151 if(dupCbParent instanceof CdmBase) {
152 sb.append(" with id : " + ((CdmBase)dupCbParent).getId());
153 }
154 }
155 sb.append(System.getProperty("line.separator"));
156 sb.append(" -- entity belongs to cache(s) : " + getCachesContainingEntity(dupCb));
157 sb.append(System.getProperty("line.separator"));
158 sb.append("-----------");
159 }
160 }
161
162 sb.append(System.getProperty("line.separator"));
163 sb.append(System.getProperty("line.separator"));
164
165 if(notInCacheList.isEmpty()) {
166 sb.append("No Entities found which are not in Cache.");
167 } else {
168 sb.append("Not In Cache Entities,");
169
170 for(CdmEntityInfo cei : notInCacheList) {
171 CdmBase cb = (CdmBase) cei.getObject();
172 Object cbParent = cei.getParent().getObject();
173
174 sb.append(System.getProperty("line.separator"));
175
176 String fieldName = "";
177 if(cei.getField() != null) {
178 fieldName = cei.getField().getName();
179 }
180 sb.append(" - " + fieldName + ":" + cb.getUserFriendlyTypeName() + "/" + cb.getId());
181
182 if(cbParent instanceof CdmBase) {
183 sb.append(" of entity " + ((CdmBase)cbParent).getUserFriendlyTypeName());
184 } else {
185 sb.append(" of entity " + cbParent.getClass().getName());
186 }
187 }
188 }
189 sb.append(System.getProperty("line.separator"));
190 return sb.toString();
191 }
192
193 private String getCachesContainingEntity(CdmBase cdmEntity) {
194 Cache defaultCache = CacheManager.create().getCache(CdmCacher.DEFAULT_CACHE_NAME);
195 String caches = "";
196 Element dce = defaultCache.get(cdmEntity.getUuid());
197 if(dce != null && dce.getObjectValue() == cdmEntity) {
198 caches = "{DC}";
199 }
200
201 Object cte = cacher.getFromCache(CdmTransientEntityCacher.generateKey(cdmEntity));
202 if(cte != null && cte == cdmEntity) {
203 caches += "{TC}";
204 }
205 return caches;
206 }
207
208
209 private void debug(CdmBase cdmEntity, boolean recursive) {
210 if(cdmEntity == null) {
211 return;
212 }
213 logger.info("---- starting recursive debug for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
214 List<CdmEntityInfo> alreadyVisitedEntities = new ArrayList<CdmEntityInfo>();
215 CdmEntityInfo cei = new CdmEntityInfo(ProxyUtils.deproxy(cdmEntity));
216 debugRecursive(cdmEntity, alreadyVisitedEntities, cei);
217 rootElements.add(cei);
218 alreadyVisitedEntities.clear();
219 logger.info("---- ending recursive debug for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + "\n");
220 }
221
222 private <T extends Object> void debugRecursive(T obj,
223 List<CdmEntityInfo> alreadyVisitedEntities,
224 CdmEntityInfo cei) {
225 if(obj == null) {
226 return;
227 }
228 if(obj instanceof CdmBase) {
229 debugRecursive((CdmBase)obj, alreadyVisitedEntities, cei);
230 } else if (obj instanceof Map) {
231 debug((Map<T,T>)obj, alreadyVisitedEntities, cei);
232 } else if (obj instanceof Collection) {
233 debug((Collection<T>)obj, alreadyVisitedEntities, cei);
234 }
235
236 logger.info("No caching yet for type " + obj.getClass().getName());
237
238
239 }
240
241 private <T extends Object> void debug(Map<T,T> map,
242 List<CdmEntityInfo> alreadyVisitedEntities,
243 CdmEntityInfo cei) {
244 if(map == null || map.isEmpty()) {
245 return;
246 }
247
248 Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
249 while ( iter.hasNext() ) {
250 Map.Entry<T,T> e = iter.next();
251 CdmEntityInfo childCei = new CdmEntityInfo(e);
252 cei.addChild(childCei);
253
254 CdmEntityInfo keyCei = new CdmEntityInfo(ProxyUtils.deproxy(e.getKey()));
255 childCei.addChild(keyCei);
256 CdmEntityInfo valueCei = new CdmEntityInfo(ProxyUtils.deproxy(e.getValue()));
257 childCei.addChild(valueCei);
258
259 debugRecursive(e.getKey(), alreadyVisitedEntities, keyCei);
260 debugRecursive(e.getValue(), alreadyVisitedEntities, valueCei);
261 }
262 }
263
264 private <T extends Object> void debug(Collection<T> collection,
265 List<CdmEntityInfo> alreadyVisitedEntities,
266 CdmEntityInfo cei) {
267 Iterator<T> collectionItr = collection.iterator();
268
269 while(collectionItr.hasNext()) {
270 Object obj = collectionItr.next();
271 boolean alreadyVisited = false;
272 for (CdmEntityInfo entityInfo: alreadyVisitedEntities) {
273 if(obj.equals(entityInfo.getObject())){
274 alreadyVisited = true;
275 break;
276 }
277 }
278 if(!alreadyVisited){
279 CdmEntityInfo childCei = new CdmEntityInfo(ProxyUtils.deproxy(obj));
280 cei.addChild(childCei);
281 debugRecursive(obj, alreadyVisitedEntities, childCei);
282 }
283
284 }
285
286 }
287
288 private void debugRecursive(CdmBase cdmEntity,
289 List<CdmEntityInfo> alreadyVisitedEntities,
290 CdmEntityInfo cei) {
291
292 CdmBase cachedCdmEntityInSubGraph = null;
293
294 if(cei.getObject() instanceof CdmBase) {
295 CdmBase cb = (CdmBase)cei.getObject();
296 cachedCdmEntityInSubGraph = cacher.getFromCache(cb);
297 if(cachedCdmEntityInSubGraph != cb) {
298 // found a cdm entity which is not in cache - need to record this
299 //logger.info(" - found entity not in cache " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
300 addEntityNotInCache(cei);
301 }
302 }
303
304
305 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
306 // since there could be new or deleted objects in the cdmEntity sub-graph
307
308 // start by getting the fields from the cdm entity
309 String className = cdmEntity.getClass().getName();
310 CdmModelFieldPropertyFromClass cmgmfc = cacher.getFromCdmlibModelCache(className);
311 if(cmgmfc != null) {
312 alreadyVisitedEntities.add(cei);
313 List<String> fields = cmgmfc.getFields();
314 for(String field : fields) {
315 // retrieve the actual object corresponding to the field.
316 // this object will be either a CdmBase or a Collection / Map
317 // with CdmBase as the generic type
318 CdmEntityInfo childCei = getDebugCdmBaseTypeFieldValue(cdmEntity, field, alreadyVisitedEntities, cei);
319 if(!childCei.isProxy()) {
320 Object object = childCei.getObject();
321 if(object != null && object instanceof CdmBase) {
322 CdmBase cdmEntityInSubGraph = (CdmBase)object;
323 if(!containsIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph)) {
324 logger.info("recursive debugging object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
325 debugRecursive(cdmEntityInSubGraph, alreadyVisitedEntities, childCei);
326 } else {
327 logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
328 }
329 }
330 }
331 }
332 } else {
333 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
334 "The cache may be corrupted or not in sync with the latest model version" );
335 }
336
337 }
338
339
340 private CdmEntityInfo getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity,
341 String fieldName,
342 List<CdmEntityInfo> alreadyVisitedEntities,
343 CdmEntityInfo cei) {
344
345 CdmEntityInfo childCei = null;
346 Class<?> clazz = cdmEntity.getClass();
347 try {
348 // this call will search in the provided class as well as
349 // the super classes until it finds the field
350 Field field = ReflectionUtils.findField(clazz, fieldName);
351
352 if(field == null) {
353 throw new CdmClientCacheException("Field '" + fieldName
354 + "' not found when searching in class '" + clazz.getName() + "' and its supercalsses");
355 }
356 field.setAccessible(true);
357 Object o = field.get(cdmEntity);
358 o = ProxyUtils.deproxy(o);
359 CdmBase cdmEntityInSubGraph = null;
360
361 childCei = new CdmEntityInfo(o);
362 cei.addChild(childCei);
363 childCei.setField(field);
364
365 if(o != null) {
366 boolean isProxy = ProxyUtils.isProxy(o);
367
368 childCei.setProxy(isProxy);
369 if(!isProxy) {
370 childCei.setObject(o);
371 if(CdmBase.class.isAssignableFrom(o.getClass())) {
372 logger.info("found initialised cdm entity '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
373 cdmEntityInSubGraph = (CdmBase)o;
374
375 //logger.info(" - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
376 CdmEntityInfo dupCei = getDuplicate(alreadyVisitedEntities, cdmEntityInSubGraph);
377 if(dupCei != null) {
378 addDuplicateEntity(childCei, dupCei);
379 }
380
381 } else if(o instanceof Map) {
382 debugRecursive((Map)o, alreadyVisitedEntities, childCei);
383 } else if(o instanceof Collection) {
384 debugRecursive((Collection)o, alreadyVisitedEntities, childCei);
385 }
386
387 }
388 }
389 // we return the original cdm entity in the sub graph because we
390 // want to continue to recurse on the input cdm entity graph
391 // and not the one in the cache
392
393 return childCei;
394 } catch (SecurityException e) {
395 throw new CdmClientCacheException(e);
396 } catch (IllegalArgumentException e) {
397 throw new CdmClientCacheException(e);
398 } catch (IllegalAccessException e) {
399 throw new CdmClientCacheException(e);
400 }
401 }
402
403
404 private CdmEntityInfo getDuplicate(List<CdmEntityInfo> alreadyVisitedEntities, Object objectToCompare) {
405 if(objectToCompare != null ) {
406 for(CdmEntityInfo cei: alreadyVisitedEntities) {
407 if(objectToCompare.equals(cei.getObject()) && objectToCompare != cei.getObject()) {
408 return cei;
409 }
410 }
411 }
412 return null;
413 }
414
415 private boolean containsIdenticalCdmEntity(List<CdmEntityInfo> ceiSet, Object objectToCompare) {
416 boolean foundIdentical = false;
417 if(objectToCompare != null) {
418 for(CdmEntityInfo cei : ceiSet) {
419 if(cei.getObject() == objectToCompare) {
420 foundIdentical = true;
421 }
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 }