Project

General

Profile

Download (22 KB) Statistics
| Branch: | Tag: | Revision:
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.cdm.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 org.apache.log4j.Logger;
20
import org.hibernate.LazyInitializationException;
21
import org.hibernate.collection.spi.PersistentCollection;
22
import org.hibernate.proxy.HibernateProxy;
23
import org.hibernate.proxy.LazyInitializer;
24
import org.springframework.util.ReflectionUtils;
25

    
26
import eu.etaxonomy.cdm.api.cache.CdmCacherBase;
27
import eu.etaxonomy.cdm.model.common.CdmBase;
28
import net.sf.ehcache.Cache;
29
import net.sf.ehcache.CacheManager;
30
import net.sf.ehcache.Element;
31

    
32
/**
33
 * @author cmathew
34
 * @since 9 Feb 2015
35
 */
36
public class EntityCacherDebugResult {
37

    
38
    private static final Logger logger = Logger.getLogger(EntityCacherDebugResult.class);
39

    
40
    private Map<CdmEntityInfo, CdmEntityInfo> duplicateCdmEntityMap;
41

    
42
    private List<CdmEntityInfo> notInCacheList;
43

    
44
    private CdmTransientEntityCacher cacher;
45

    
46
    private List<CdmEntityInfo> rootElements;
47

    
48
    StringBuilder debugOutput = new StringBuilder();
49

    
50
    public EntityCacherDebugResult() {
51
    }
52

    
53
    public <T extends CdmBase> EntityCacherDebugResult(CdmTransientEntityCacher cacher, Collection<T> rootEntities) {
54
        this.cacher = cacher;
55
        init();
56

    
57
        if(rootEntities != null && !rootEntities.isEmpty()) {
58
            for(CdmBase rootEntity : rootEntities) {
59
                debug(rootEntity, true);
60
                String out = toString(duplicateCdmEntityMap, notInCacheList, rootEntity);
61
                System.out.println(out);
62
                debugOutput.append(out);
63
                clear();
64
            }
65
        }
66
    }
67

    
68
    private void init() {
69
        duplicateCdmEntityMap = new HashMap<CdmEntityInfo, CdmEntityInfo>();
70
        notInCacheList = new ArrayList<CdmEntityInfo>();
71
        rootElements = new ArrayList<CdmEntityInfo>();
72
    }
73

    
74
    private void clear() {
75
        duplicateCdmEntityMap.clear();
76
        notInCacheList.clear();
77
    }
78

    
79
    public void addDuplicateEntity(CdmEntityInfo cei, CdmEntityInfo cachedCei) {
80
        duplicateCdmEntityMap.put(cei, cachedCei);
81
    }
82

    
83
    public void addEntityNotInCache(CdmEntityInfo cei) {
84
        notInCacheList.add(cei);
85
    }
86

    
87
    public List<CdmEntityInfo> getRootElements() {
88
        return rootElements;
89
    }
90

    
91
    private void print(Map<CdmEntityInfo, CdmEntityInfo> duplicateCdmEntityMap,
92
            List<CdmEntityInfo> notInCacheList,
93
            CdmBase rootEntity) {
94
        System.out.println(toString(duplicateCdmEntityMap, notInCacheList, rootEntity));
95
    }
96

    
97
    @Override
98
    public String toString() {
99
        return debugOutput.toString();
100
    }
101

    
102
    private String toString(Map<CdmEntityInfo, CdmEntityInfo> duplicateCdmEntityMap,
103
            List<CdmEntityInfo> notInCacheList,
104
            CdmBase rootEntity) {
105

    
106
        StringBuilder sb = new StringBuilder();
107
        sb.append(System.getProperty("line.separator"));
108
        sb.append("<<< Root Entity " + rootEntity.getUserFriendlyTypeName() + " with id " + rootEntity.getId() + " >>>");
109
        sb.append(System.getProperty("line.separator"));
110
        if(duplicateCdmEntityMap.isEmpty()) {
111
            sb.append("No Duplicate CDM Entities.");
112
        } else {
113
            sb.append("Duplicate CDM Entities,");
114

    
115
            for (Map.Entry<CdmEntityInfo, CdmEntityInfo> entry : duplicateCdmEntityMap.entrySet())
116
            {
117
                sb.append(System.getProperty("line.separator"));
118
                CdmEntityInfo cei = entry.getKey();
119
                CdmBase cb = (CdmBase) cei.getObject();
120

    
121
                sb.append(" - " + cei.getField().getName() + ":" + cb.getUserFriendlyTypeName() + "/" + cb.getId());
122
                if(cei.getParent() != null) {
123
                    Object cbParent = cei.getParent().getObject();
124
                    sb.append("     in entity " + cbParent.getClass().getCanonicalName());
125
                    if(cbParent instanceof CdmBase) {
126

    
127
                        sb.append(" with id : " + ((CdmBase)cbParent).getId());
128
                    }
129
                }
130
                sb.append(System.getProperty("line.separator"));
131
                sb.append("  -- entity belongs to cache(s) : " + getCachesContainingEntity(cb));
132
                sb.append(System.getProperty("line.separator"));
133

    
134

    
135
                CdmEntityInfo dupCei = entry.getValue();
136
                CdmBase dupCb = (CdmBase) dupCei.getObject();
137

    
138
                String dupCeiFieldName = "";
139
                if(dupCei.getField() != null) {
140
                    dupCeiFieldName = dupCei.getField().getName();
141
                }
142
                sb.append(" - " + dupCeiFieldName + ":" + dupCb.getUserFriendlyTypeName() + "/" + dupCb.getId());
143
                if(dupCei.getParent() != null) {
144
                    Object dupCbParent = dupCei.getParent().getObject();
145
                    sb.append("      in entity " + dupCbParent.getClass().getCanonicalName());
146
                    if(dupCbParent instanceof CdmBase) {
147
                        sb.append(" with id : " + ((CdmBase)dupCbParent).getId());
148
                    }
149
                }
150
                sb.append(System.getProperty("line.separator"));
151
                sb.append("  -- entity belongs to cache(s) : " + getCachesContainingEntity(dupCb));
152
                sb.append(System.getProperty("line.separator"));
153
                sb.append("-----------");
154
            }
155
        }
156

    
157
        sb.append(System.getProperty("line.separator"));
158
        sb.append(System.getProperty("line.separator"));
159

    
160
        if(notInCacheList.isEmpty()) {
161
            sb.append("No Entities found which are not in Cache.");
162
        } else {
163
            sb.append("Not In Cache Entities (");
164
            sb.append(NotInCacheDetail.NOT_FOUND.getLabel() + ": " + NotInCacheDetail.NOT_FOUND.name() + ", ");
165
            sb.append(NotInCacheDetail.COPY_ENTITY.getLabel() + ": " + NotInCacheDetail.COPY_ENTITY.name() + ")");
166

    
167
            for(CdmEntityInfo cei : notInCacheList) {
168
                CdmBase cb = (CdmBase) cei.getObject();
169
                CdmEntityInfo parentCei = cei.getParent();
170
                Object parentEntity = null;
171
                if(parentCei != null){
172
                    parentEntity = parentCei.getObject();
173
                }
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(" - ");
182
                if(cei.getNotInCacheDetail() != null){
183
                    sb.append(cei.getNotInCacheDetail().getLabel());
184
                }
185
                sb.append(fieldName + "[" + cb.getUserFriendlyTypeName() + "#" + cb.getId() + "]");
186

    
187
                String parentsPath = "";
188
                while(parentCei != null){
189
                    parentsPath += ".";
190
                    parentsPath += parentCei.getField() != null? parentCei.getField().getName() : "";
191
                    String id = "";
192
                    if(parentCei.getObject() instanceof CdmBase){
193
                        id = "#" + ((CdmBase)parentCei.getObject()).getId();
194
                    }
195
                    parentsPath += "[" + classLabel(parentCei.getObject()) + id + "]";
196
                    parentCei = parentCei.getParent();
197
                }
198

    
199
                sb.append(parentsPath);
200
            }
201
        }
202
        sb.append(System.getProperty("line.separator"));
203
        return sb.toString();
204
    }
205

    
206
    private String classLabel(Object entity){
207
        if(entity instanceof CdmBase) {
208
            return ((CdmBase)entity).getUserFriendlyTypeName();
209
        } else {
210
            return entity.getClass().getName();
211
        }
212
    }
213

    
214
    private String getCachesContainingEntity(CdmBase cdmEntity) {
215
        Cache defaultCache = CacheManager.create().getCache(CdmCacherBase.DEFAULT_CACHE_NAME);
216
        String caches = "";
217
        Element dce = defaultCache.get(cdmEntity.getUuid());
218
        if(dce != null && dce.getObjectValue() == cdmEntity) {
219
            caches = "{DC}";
220
        }
221

    
222
        Object cte = cacher.getFromCache(CdmTransientEntityCacher.generateKey(cdmEntity));
223
        if(cte != null && cte == cdmEntity) {
224
            caches += "{TC}";
225
        }
226
        return caches;
227
    }
228

    
229
    private void debug(CdmBase cdmEntity, boolean recursive) {
230
        if(cdmEntity == null) {
231
            return;
232
        }
233
        logger.info("---- starting recursive debug for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
234
        List<CdmEntityInfo> alreadyVisitedEntities = new ArrayList<CdmEntityInfo>();
235
        CdmEntityInfo cei = new CdmEntityInfo(ProxyUtils.deproxy(cdmEntity));
236
        debugRecursive(cdmEntity, alreadyVisitedEntities, cei);
237
        rootElements.add(cei);
238
        alreadyVisitedEntities.clear();
239
        logger.info("---- ending recursive debug for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + "\n");
240
    }
241

    
242
    private <T extends Object> void debugRecursive(T obj,
243
            List<CdmEntityInfo> alreadyVisitedEntities,
244
            CdmEntityInfo cei) {
245
        if(obj == null) {
246
            return;
247
        }
248
        if(obj instanceof CdmBase) {
249
            debugRecursive((CdmBase)obj, alreadyVisitedEntities, cei);
250
        } else if (obj instanceof Map) {
251
            debug((Map<T,T>)obj, alreadyVisitedEntities, cei);
252
        } else if (obj instanceof Collection) {
253
            debug((Collection<T>)obj, alreadyVisitedEntities, cei);
254
        }
255
        logger.info("No caching yet for type " + obj.getClass().getName());
256
    }
257

    
258
    private <T extends Object> void debug(Map<T,T> map,
259
            List<CdmEntityInfo> alreadyVisitedEntities,
260
            CdmEntityInfo cei) {
261
        if(map == null || map.isEmpty()) {
262
            return;
263
        }
264

    
265
        Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
266
        while ( iter.hasNext() ) {
267
            Map.Entry<T,T> e = iter.next();
268
            CdmEntityInfo childCei = new CdmEntityInfo(e);
269
            cei.addChild(childCei);
270

    
271
            CdmEntityInfo keyCei = new CdmEntityInfo(ProxyUtils.deproxy(e.getKey()));
272
            childCei.addChild(keyCei);
273
            CdmEntityInfo valueCei = new CdmEntityInfo(ProxyUtils.deproxy(e.getValue()));
274
            childCei.addChild(valueCei);
275

    
276
            debugRecursive(e.getKey(), alreadyVisitedEntities, keyCei);
277
            debugRecursive(e.getValue(), alreadyVisitedEntities, valueCei);
278
        }
279
    }
280

    
281
    private <T extends Object> void debug(Collection<T> collection,
282
            List<CdmEntityInfo> alreadyVisitedEntities,
283
            CdmEntityInfo cei) {
284
        Iterator<T> collectionItr = collection.iterator();
285

    
286
        while(collectionItr.hasNext()) {
287
            Object obj = collectionItr.next();
288
            boolean alreadyVisited = false;
289
            for (CdmEntityInfo entityInfo: alreadyVisitedEntities) {
290
                if(obj.equals(entityInfo.getObject())){
291
                    alreadyVisited = true;
292
                    break;
293
                }
294
            }
295
            if(!alreadyVisited){
296
                CdmEntityInfo childCei = new CdmEntityInfo(ProxyUtils.deproxy(obj));
297
                cei.addChild(childCei);
298
                debugRecursive(obj, alreadyVisitedEntities, childCei);
299
            }
300
        }
301
    }
302

    
303
    private void debugRecursive(CdmBase cdmEntity,
304
            List<CdmEntityInfo> alreadyVisitedEntities,
305
            CdmEntityInfo cei) {
306

    
307
        if(cei.getObject() instanceof CdmBase) {
308
           CdmBase cb =  (CdmBase)cei.getObject();
309
           cb = (CdmBase) ProxyUtils.deproxy(cb);
310
           CdmBase cachedCdmEntityInSubGraph = cacher.getFromCache(cb);
311
           if(cachedCdmEntityInSubGraph != cb) {
312
               cei.setNotInCacheDetail(cachedCdmEntityInSubGraph == null ? NotInCacheDetail.NOT_FOUND : NotInCacheDetail.COPY_ENTITY);
313
               // found a cdm entity which is not in cache - need to record this
314
               //logger.info("  - found entity not in cache " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
315
               addEntityNotInCache(cei);
316
           }
317
        }
318

    
319
        // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
320
        // since there could be new or deleted objects in the cdmEntity sub-graph
321

    
322
        // start by getting the fields from the cdm entity
323
        String className = cdmEntity.getClass().getName();
324
        CdmModelFieldPropertyFromClass cmgmfc = cacher.getFromCdmlibModelCache(className);
325
        if(cmgmfc != null) {
326
            alreadyVisitedEntities.add(cei);
327
            List<String> fields = cmgmfc.getFields();
328
            for(String field : fields) {
329
                // retrieve the actual object corresponding to the field.
330
                // this object will be either a CdmBase or a Collection / Map
331
                // with CdmBase as the generic type
332
                CdmEntityInfo childCei = getDebugCdmBaseTypeFieldValue(cdmEntity, field, alreadyVisitedEntities, cei);
333
                if(!childCei.isProxy()) {
334
                    Object object = childCei.getObject();
335
                    if(object != null && object instanceof CdmBase) {
336
                        CdmBase cdmEntityInSubGraph = (CdmBase)object;
337
                        if(!containsIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph)) {
338
                            logger.info("recursive debugging object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
339
                            debugRecursive(cdmEntityInSubGraph, alreadyVisitedEntities, childCei);
340
                        } else {
341
                            logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
342
                        }
343
                    }
344
                }
345
            }
346
        } else {
347
            throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
348
                    "The cache may be corrupted or not in sync with the latest model version" );
349
        }
350

    
351
    }
352

    
353

    
354
    private CdmEntityInfo getDebugCdmBaseTypeFieldValue(CdmBase cdmEntity,
355
            String fieldName,
356
            List<CdmEntityInfo> alreadyVisitedEntities,
357
            CdmEntityInfo cei) {
358

    
359
        CdmEntityInfo childCei = null;
360
        Class<?> clazz = cdmEntity.getClass();
361
        try {
362
            // this call will search in the provided class as well as
363
            // the super classes until it finds the field
364
            Field field = ReflectionUtils.findField(clazz, fieldName);
365

    
366
            if(field == null) {
367
                throw new CdmClientCacheException("Field '" + fieldName
368
                        + "' not found when searching in class '" + clazz.getName() + "' and its supercalsses");
369
            }
370
            field.setAccessible(true);
371
            Object o = field.get(cdmEntity);
372
            o = ProxyUtils.deproxy(o);
373
            CdmBase cdmEntityInSubGraph = null;
374

    
375
            childCei = new CdmEntityInfo(o);
376
            cei.addChild(childCei);
377
            childCei.setField(field);
378

    
379
            if(o != null) {
380
                boolean isProxy = ProxyUtils.isUninitializedProxy(o);
381

    
382
                childCei.setProxy(isProxy);
383
                if(!isProxy) {
384
                    childCei.setObject(o);
385
                    if(CdmBase.class.isAssignableFrom(o.getClass())) {
386
                        logger.info("found initialised cdm entity '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
387
                        cdmEntityInSubGraph  = (CdmBase)o;
388

    
389
                        //logger.info("  - found duplicate entity at " + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
390
                        CdmEntityInfo dupCei = getDuplicate(alreadyVisitedEntities, cdmEntityInSubGraph);
391
                        if(dupCei != null) {
392
                            addDuplicateEntity(childCei, dupCei);
393
                        }
394

    
395
                    } else if(o instanceof Map) {
396
                        debugRecursive((Map)o, alreadyVisitedEntities, childCei);
397
                    } else if(o instanceof Collection) {
398
                        debugRecursive((Collection)o, alreadyVisitedEntities, childCei);
399
                    }
400

    
401
                }
402
            }
403
            // we return the original cdm entity in the sub graph because we
404
            // want to continue to recurse on the input cdm entity graph
405
            // and not the one in the cache
406
            return childCei;
407
        } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
408
            throw new CdmClientCacheException(e);
409
        }
410
    }
411

    
412
    private CdmEntityInfo getDuplicate(List<CdmEntityInfo> alreadyVisitedEntities, Object objectToCompare) {
413
        if(objectToCompare != null ) {
414
            for(CdmEntityInfo cei: alreadyVisitedEntities) {
415
                if(objectToCompare.equals(cei.getObject()) && objectToCompare != cei.getObject()) {
416
                    return cei;
417
                }
418
            }
419
        }
420
        return null;
421
    }
422

    
423
    private boolean containsIdenticalCdmEntity(List<CdmEntityInfo> ceiSet, Object objectToCompare) {
424
        boolean foundIdentical = false;
425
        if(objectToCompare != null) {
426
            for(CdmEntityInfo cei : ceiSet) {
427
                if(cei.getObject() == objectToCompare) {
428
                    foundIdentical = true;
429
                }
430
//                } else if(objectToCompare.equals(cei.getObject())) {
431
//                    return false;
432
//                }
433
            }
434
        }
435
        return foundIdentical;
436
    }
437

    
438
    public class CdmEntityInfo {
439

    
440
        private Object object;
441
        private CdmEntityInfo parent;
442
        private List<CdmEntityInfo> children;
443
        private Field field;
444
        private String label;
445
        private boolean isProxy;
446
        private NotInCacheDetail notInCacheDetail = null;
447

    
448
        public CdmEntityInfo(Object object) {
449
            this.object = object;
450
            isProxy = false;
451
            children = new ArrayList<CdmEntityInfo>();
452
        }
453

    
454
        public CdmEntityInfo getParent() {
455
            return parent;
456
        }
457
        public void setParent(CdmEntityInfo parent) {
458
            this.parent = parent;
459
        }
460

    
461
        public List<CdmEntityInfo> getChildren() {
462
            return children;
463
        }
464
        public void setChildren(List<CdmEntityInfo> children) {
465
            this.children = children;
466
        }
467

    
468
        public void addChild(CdmEntityInfo cei) {
469
            this.children.add(cei);
470
            cei.setParent(this);
471
        }
472

    
473
        public Field getField() {
474
            return field;
475
        }
476
        public void setField(Field field) {
477
            this.field = field;
478
        }
479

    
480
        public String getLabel() {
481
            String label;
482
            String fieldName = "";
483
            if(field != null) {
484
                fieldName = field.getName();
485
            }
486

    
487
            if(object != null) {
488
                String className = object.getClass().getName();
489
                if(object instanceof HibernateProxy) {
490
                    LazyInitializer hli = ((HibernateProxy)object).getHibernateLazyInitializer();
491
                    if(hli.isUninitialized()) {
492
                        className = "HibernateProxy";
493
                    } else {
494
                        className = "InitialisedHibernateProxy";
495
                    }
496
                    label = "[" + className + "] " + fieldName;
497
                } else if(object instanceof PersistentCollection) {
498
                    PersistentCollection pc = ((PersistentCollection)object);
499
                    if(!pc.wasInitialized()) {
500
                        className = "PersistentCollection";
501
                    } else {
502
                        className = "InitialisedPersistentCollection";
503
                    }
504
                    label = "[" + className + "] " + fieldName;
505
                } else if(object instanceof Collection) {
506
                    label = "[" + className + "] " + fieldName + " : " + String.valueOf(((Collection)object).size());
507
                } else if(object instanceof Map) {
508
                    label = "[" + className + "] " + fieldName + " : " + String.valueOf(((Map)object).size());
509
                } else if(object instanceof CdmBase) {
510
                    String objectLabel = "-- not fully initialized for toString() --";
511
                    try {
512

    
513
                        objectLabel = object.toString();
514
                    } catch(LazyInitializationException e){
515

    
516
                    }
517
                    label = getCachesContainingEntity((CdmBase)object) +  "[" + className + "#" + ((CdmBase)object).getId() + "] " + fieldName + " : " + objectLabel;
518
                } else {
519
                    label = "[" + className + "] " + fieldName + " : " + object.toString();
520
                }
521
            } else {
522
                label = "[NULL] " + fieldName;
523
            }
524
            return label;
525
        }
526

    
527
        public void setLabel(String label) {
528
            this.label = label;
529
        }
530

    
531
        public Object getObject() {
532
            return object;
533
        }
534

    
535
        public void setObject(Object object) {
536
            this.object = object;
537
        }
538

    
539
        public boolean isProxy() {
540
            return isProxy;
541
        }
542

    
543
        public void setProxy(boolean isProxy) {
544
            this.isProxy = isProxy;
545
        }
546

    
547
        public NotInCacheDetail getNotInCacheDetail() {
548
            return notInCacheDetail;
549
        }
550
        public void setNotInCacheDetail(NotInCacheDetail notInCacheDetail) {
551
            this.notInCacheDetail = notInCacheDetail;
552
        }
553
    }
554

    
555
    enum NotInCacheDetail {
556
        NOT_FOUND("*"),
557
        COPY_ENTITY("?");
558

    
559
        private String label;
560

    
561
        private NotInCacheDetail(String label){
562
            this.label = label;
563
        }
564

    
565
        public Object getLabel() {
566
            return label;
567
        }
568
    }
569

    
570
}
(10-10/12)