1b1daae0207accd8fa5c5dcf1e0db19aac897d5e
[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.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21
22 import net.sf.ehcache.Cache;
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, List<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 sb.append(" - " + cei.getField().getName() + ":" + cb.getUserFriendlyTypeName() + "/" + cb.getId());
170
171 if(cbParent instanceof CdmBase) {
172 sb.append(" of entity " + ((CdmBase)cbParent).getUserFriendlyTypeName());
173 } else {
174 sb.append(" of entity " + cbParent.getClass().getName());
175 }
176 }
177 }
178 sb.append(System.getProperty("line.separator"));
179 return sb.toString();
180 }
181
182 private String getCachesContainingEntity(CdmBase cdmEntity) {
183 Cache defaultCache = CdmRemoteCacheManager.getInstance().getDefaultCacheManager().getCache(CdmCacher.DEFAULT_CACHE_NAME);
184 String caches = "";
185 Object dce = defaultCache.get(cdmEntity.getUuid());
186 if(dce != null && dce == cdmEntity) {
187 caches = CdmCacher.DEFAULT_CACHE_NAME;
188 }
189 if(!caches.isEmpty()) {
190 caches += ", ";
191 }
192 Object cte = cacher.getFromCache(CdmTransientEntityCacher.generateKey(cdmEntity));
193 if(cte != null && cte == cdmEntity) {
194 caches += cacher.toString();
195 }
196 return caches;
197 }
198
199
200 private void debug(CdmBase cdmEntity, boolean recursive) {
201 if(cdmEntity == null) {
202 return;
203 }
204 logger.info("---- starting recursive debug for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
205 Set<CdmEntityInfo> alreadyVisitedEntities = new HashSet<CdmEntityInfo>();
206 CdmEntityInfo cei = new CdmEntityInfo(cdmEntity);
207 debugRecursive(cdmEntity, alreadyVisitedEntities, cei);
208 rootElements.add(cei);
209 alreadyVisitedEntities.clear();
210 logger.info("---- ending recursive debug for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + "\n");
211 }
212
213 private <T extends Object> void debugRecursive(T obj,
214 Set<CdmEntityInfo> alreadyVisitedEntities,
215 CdmEntityInfo cei) {
216 if(obj == null) {
217 return;
218 }
219 if(obj instanceof CdmBase) {
220 debugRecursive((CdmBase)obj, alreadyVisitedEntities, cei);
221 } else if (obj instanceof Map) {
222 debug((Map<T,T>)obj, alreadyVisitedEntities, cei);
223 } else if (obj instanceof Collection) {
224 debug((Collection<T>)obj, alreadyVisitedEntities, cei);
225 }
226
227 logger.info("No caching yet for type " + obj.getClass().getName());
228
229
230 }
231
232 private <T extends Object> void debug(Map<T,T> map,
233 Set<CdmEntityInfo> alreadyVisitedEntities,
234 CdmEntityInfo cei) {
235 if(map == null || map.isEmpty()) {
236 return;
237 }
238
239 int originalMapSize = map.size();
240
241 Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
242 int i=0;
243 while ( iter.hasNext() ) {
244 Map.Entry<T,T> e = iter.next();
245 CdmEntityInfo childCei = new CdmEntityInfo(e);
246 cei.addChild(childCei);
247 debugRecursive(e.getKey(), alreadyVisitedEntities, childCei);
248 debugRecursive(e.getValue(), alreadyVisitedEntities, childCei);
249 }
250 }
251
252 private <T extends Object> void debug(Collection<T> collection,
253 Set<CdmEntityInfo> alreadyVisitedEntities,
254 CdmEntityInfo cei) {
255 int length = collection.size();
256 Object[] result = new Object[length];
257 Iterator<T> collectionItr = collection.iterator();
258
259 while(collectionItr.hasNext()) {
260 Object obj = collectionItr.next();
261 CdmEntityInfo childCei = new CdmEntityInfo(obj);
262 cei.addChild(childCei);
263 debugRecursive(obj, alreadyVisitedEntities, childCei);
264
265 }
266
267 }
268
269 private void debugRecursive(CdmBase cdmEntity,
270 Set<CdmEntityInfo> alreadyVisitedEntities,
271 CdmEntityInfo cei) {
272
273
274 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
275 // since there could be new or deleted objects in the cdmEntity sub-graph
276
277 // start by getting the fields from the cdm entity
278 String className = cdmEntity.getClass().getName();
279 CdmModelFieldPropertyFromClass cmgmfc = cacher.getFromCdmlibModelCache(className);
280 if(cmgmfc != null) {
281 alreadyVisitedEntities.add(cei);
282 List<String> fields = cmgmfc.getFields();
283 for(String field : fields) {
284 // retrieve the actual object corresponding to the field.
285 // this object will be either a CdmBase or a Collection / Map
286 // with CdmBase as the generic type
287
288 CdmEntityInfo childCei = getDebugCdmBaseTypeFieldValue(cdmEntity, field, alreadyVisitedEntities, cei);
289 if(!childCei.isProxy()) {
290 Object object = childCei.getObject();
291 if(object != null && object instanceof CdmBase) {
292 CdmBase cdmEntityInSubGraph = (CdmBase)object;
293 if(!containsIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph)) {
294 logger.info("recursive debugging object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
295 debugRecursive(cdmEntityInSubGraph, alreadyVisitedEntities, childCei);
296 } else {
297 logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
298 }
299 }
300 }
301 }
302 } else {
303 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
304 "The cache may be corrupted or not in sync with the latest model version" );
305 }
306
307 }
308
309
310 private CdmEntityInfo getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity,
311 String fieldName,
312 Set<CdmEntityInfo> alreadyVisitedEntities,
313 CdmEntityInfo cei) {
314
315 CdmEntityInfo childCei = null;
316 Class<?> clazz = cdmEntity.getClass();
317 try {
318 // this call will search in the provided class as well as
319 // the super classes until it finds the field
320 Field field = ReflectionUtils.findField(clazz, fieldName);
321
322 if(field == null) {
323 throw new CdmClientCacheException("Field '" + fieldName
324 + "' not found when searching in class '" + clazz.getName() + "' and its supercalsses");
325 }
326 field.setAccessible(true);
327 Object o = field.get(cdmEntity);
328
329 CdmBase cdmEntityInSubGraph = null;
330
331 boolean isHibernateProxy = false;
332 boolean isPersistentCollection = false;
333
334 childCei = new CdmEntityInfo(o);
335 cei.addChild(childCei);
336 childCei.setField(field);
337
338 if(o != null) {
339
340 if(o instanceof HibernateProxy) {
341 LazyInitializer hli = ((HibernateProxy)o).getHibernateLazyInitializer();
342 if(!hli.isUninitialized()) {
343 o = hli.getImplementation();
344 } else {
345 isHibernateProxy = true;
346 }
347 }
348
349 if(o instanceof PersistentCollection) {
350 PersistentCollection pc = ((PersistentCollection)o);
351 if(pc.wasInitialized()) {
352 o = ProxyUtils.getObject(pc);
353 } else {
354 isPersistentCollection = true;
355 }
356 }
357 childCei.setObject(o);
358 childCei.setProxy(isHibernateProxy || isPersistentCollection);
359 if(!isHibernateProxy && !isPersistentCollection) {
360
361 if(CdmBase.class.isAssignableFrom(o.getClass())) {
362 logger.info("found initialised cdm entity '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
363 cdmEntityInSubGraph = (CdmBase)o;
364
365 //logger.info(" - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
366 CdmEntityInfo dupCei = getDuplicate(alreadyVisitedEntities, cdmEntityInSubGraph);
367 if(dupCei != null) {
368 addDuplicateEntity(childCei, dupCei);
369 }
370
371 CdmBase cachedCdmEntityInSubGraph = cacher.getFromCache(cdmEntityInSubGraph);
372 // the only exception to updating the field to the latest value
373 // is the case where the field has been already initialised, cached and
374 // is not the same as the one in the cache, in which case we set the value
375 // of the field to the one found in the cache
376 if(cachedCdmEntityInSubGraph == null) {
377 // found a cdm entity which is not in cache - need to record this
378 //logger.info(" - found entity not in cache " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
379 addEntityNotInCache(childCei);
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(Set<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(Set<CdmEntityInfo> ceiSet, Object objectToCompare) {
417 if(objectToCompare != null) {
418 for(CdmEntityInfo cei : ceiSet) {
419 if(cei.getObject() == objectToCompare) {
420 return true;
421 }
422 }
423 }
424 return false;
425 }
426
427 public class CdmEntityInfo {
428
429 private Object object;
430 private CdmEntityInfo parent;
431 private List<CdmEntityInfo> children;
432 private Field field;
433 private String label;
434 private boolean isProxy;
435
436 public CdmEntityInfo(Object object) {
437 this.object = object;
438 isProxy = false;
439 children = new ArrayList<CdmEntityInfo>();
440 }
441
442 public CdmEntityInfo getParent() {
443 return parent;
444 }
445
446 public void setParent(CdmEntityInfo parent) {
447 this.parent = parent;
448 }
449
450 public List<CdmEntityInfo> getChildren() {
451 return children;
452 }
453
454 public void setChildren(List<CdmEntityInfo> children) {
455 this.children = children;
456 }
457
458 public void addChild(CdmEntityInfo cei) {
459 this.children.add(cei);
460 cei.setParent(this);
461 }
462
463 public Field getField() {
464 return field;
465 }
466
467 public void setField(Field field) {
468 this.field = field;
469 }
470
471
472 public String getLabel() {
473 String label;
474 String fieldName = "";
475 if(field != null) {
476 fieldName = field.getName();
477 }
478
479 if(object != null) {
480 String className = object.getClass().getName();
481 if(object instanceof HibernateProxy) {
482 LazyInitializer hli = ((HibernateProxy)object).getHibernateLazyInitializer();
483 if(hli.isUninitialized()) {
484 className = "HibernateProxy";
485 } else {
486 className = "InitialisedHibernateProxy";
487 }
488 label = "[" + className + "] " + fieldName;
489 } else if(object instanceof PersistentCollection) {
490 PersistentCollection pc = ((PersistentCollection)object);
491 if(!pc.wasInitialized()) {
492 className = "PersistentCollection";
493 } else {
494 className = "InitialisedPersistentCollection";
495 }
496 label = "[" + className + "] " + fieldName;
497 } else if(object instanceof Collection) {
498 label = "[" + className + "] " + fieldName + " : " + String.valueOf(((Collection)object).size());
499 } else if(object instanceof Map) {
500
501 label = "[" + className + "] " + fieldName + " : " + String.valueOf(((Map)object).size());
502 } else if(object instanceof CdmBase) {
503 label = "[" + className + ",id" + ((CdmBase)object).getId() + "] " + fieldName + " : " + object.toString();
504 } else {
505 label = "[" + className + "] " + fieldName + " : " + object.toString();
506 }
507 } else {
508 label = "[NULL] " + fieldName;
509 }
510 return label;
511 }
512
513 public void setLabel(String label) {
514 this.label = label;
515 }
516
517 public Object getObject() {
518 return object;
519 }
520
521 public void setObject(Object object) {
522 this.object = object;
523 }
524
525 public boolean isProxy() {
526 return isProxy;
527 }
528
529 public void setProxy(boolean isProxy) {
530 this.isProxy = isProxy;
531 }
532
533
534
535 }
536
537 }