CdmTransientEntityCacher, EntityCacherDebugResult : moved debug methods to debug...
[taxeditor.git] / eu.etaxonomy.taxeditor.cdmlib / src / main / java / eu / etaxonomy / taxeditor / remoting / cache / CdmTransientEntityCacher.java
1 // $Id$
2 /**
3 * Copyright (C) 2014 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.HashSet;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Set;
20
21 import javassist.util.proxy.ProxyFactory;
22 import net.sf.ehcache.Cache;
23 import net.sf.ehcache.Element;
24 import net.sf.ehcache.config.CacheConfiguration;
25 import net.sf.ehcache.config.SizeOfPolicyConfiguration;
26 import net.sf.ehcache.statistics.LiveCacheStatistics;
27 import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
28
29 import org.apache.log4j.Logger;
30 import org.hibernate.collection.spi.PersistentCollection;
31 import org.hibernate.proxy.HibernateProxy;
32 import org.hibernate.proxy.LazyInitializer;
33 import org.springframework.util.ReflectionUtils;
34
35 import eu.etaxonomy.cdm.api.cache.CdmServiceCacher;
36 import eu.etaxonomy.cdm.model.common.CdmBase;
37 import eu.etaxonomy.taxeditor.session.ICdmEntitySessionManager;
38
39 /**
40 *
41 * This cache guarantees that
42 * - all objects put will be ancestors of CdmBase
43 * - all CdmBase objects in the cache will be already de-proxied
44 * - after any CdmBase object is put in the cache,
45 * all non-null / non-proxy CdmBase objects in the sub-graph
46 * will also be present in the cache.
47 *
48 * @author cmathew
49 * @date 14 Oct 2014
50 *
51 */
52
53 public class CdmTransientEntityCacher {
54
55 private static final Logger logger = Logger.getLogger(CdmTransientEntityCacher.class);
56
57
58 private ICdmEntitySessionManager cdmEntitySessionManager;
59
60 private static CdmServiceCacher cdmServiceCacher;
61
62 private String cacheId;
63
64 private Cache cache;
65
66 private Cache cdmlibModelCache;
67
68 private static boolean isRecursiveEnabled = true;
69
70 public static enum CollectionType {
71 SET,
72 LIST,
73 MAP;
74
75 @Override
76 public String toString() {
77 return this.name().toLowerCase();
78 }
79 }
80
81 private CdmTransientEntityCacher() {
82
83 }
84
85 public CdmTransientEntityCacher(String cacheId, ICdmEntitySessionManager cdmEntitySessionManager) {
86 this.cacheId = cacheId;
87
88 cache = new Cache(getEntityCacheConfiguration(cacheId));
89 cdmServiceCacher.getDefaultCacheManager().addCache(cache);
90
91 cdmlibModelCache = CdmRemoteCacheManager.getInstance().getCdmModelGetMethodsCache();
92 this.cdmEntitySessionManager = cdmEntitySessionManager;
93
94
95 }
96
97 public CdmTransientEntityCacher(Object obj, ICdmEntitySessionManager cdmEntitySessionManager) {
98 this(obj.getClass().getName() + String.valueOf(obj.hashCode()), cdmEntitySessionManager);
99 }
100
101 /**
102 * Returns the default cache configuration.
103 *
104 * @return
105 */
106 private CacheConfiguration getEntityCacheConfiguration(String cacheId) {
107 SizeOfPolicyConfiguration sizeOfConfig = new SizeOfPolicyConfiguration();
108 sizeOfConfig.setMaxDepth(10000);
109 sizeOfConfig.setMaxDepthExceededBehavior("abort");
110 // For a better understanding on how to size caches, refer to
111 // http://ehcache.org/documentation/configuration/cache-size
112 return new CacheConfiguration(cacheId, 500)
113 .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
114 //.eternal(true);
115 // default ttl and tti set to 2 hours
116 .timeToLiveSeconds(60*60*2)
117 .timeToIdleSeconds(60*60*2)
118 .statistics(true)
119 .sizeOfPolicy(sizeOfConfig);
120
121
122
123 }
124
125 public static void setDefaultCacher(CdmServiceCacher css) {
126 cdmServiceCacher = css;
127 }
128
129 public LiveCacheStatistics getCacheStatistics() {
130 return cache.getLiveCacheStatistics();
131 }
132
133 /**
134 * Returns the cache corresponding to the cache id
135 *
136 * @param cacheId
137 * @return
138 */
139 private Cache getCache() {
140 return cdmServiceCacher.getDefaultCacheManager().getCache(cacheId);
141 }
142
143 @SuppressWarnings("unchecked")
144 public <T extends Object> T load(T obj, boolean recursive) {
145 if(obj == null) {
146 return null;
147 }
148 if(obj instanceof CdmBase) {
149 return (T) load((CdmBase)obj, recursive);
150 } else if (obj instanceof Map) {
151 return (T) load((Map<T,T>)obj, recursive);
152 } else if (obj instanceof Collection) {
153 return (T) load((Collection<T>)obj, recursive);
154 }
155
156 return obj;
157 }
158
159 @SuppressWarnings("unchecked")
160 private <T extends Object> T loadRecursive(T obj, Set<CdmBase> alreadyVisitedEntities) {
161 if(obj == null) {
162 return null;
163 }
164 if(obj instanceof CdmBase) {
165 return (T) loadRecursive((CdmBase)obj, alreadyVisitedEntities);
166 } else if (obj instanceof Map) {
167 return (T) load((Map<T,T>)obj, alreadyVisitedEntities);
168 } else if (obj instanceof Collection) {
169 return (T) load((Collection<T>)obj, alreadyVisitedEntities);
170 }
171
172
173 logger.info("No caching yet for type " + obj.getClass().getName());
174
175 return obj;
176 }
177
178 public <T extends Object> Map<T,T> load(Map<T,T> map, boolean recursive){
179 if(map == null) {
180 return null;
181 }
182
183 if(isRecursiveEnabled && recursive) {
184 logger.info("---- starting recursive load for cdm entity map");
185 Set<CdmBase> alreadyVisitedEntities = new HashSet<CdmBase>();
186 Map<T,T> cachedMap = load(map, alreadyVisitedEntities);
187 alreadyVisitedEntities.clear();
188 logger.info("---- ending recursive load for cdm entity map \n");
189 return cachedMap;
190 } else {
191 return load(map, null);
192 }
193 }
194
195
196 private <T extends Object> Map<T,T> load(Map<T,T> map, Set<CdmBase> alreadyVisitedEntities){
197 if(map == null || map.isEmpty()) {
198 return map;
199 }
200
201 int originalMapSize = map.size();
202 Object[] result = new Object[ map.size() * 2 ];
203 Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
204 int i=0;
205 while ( iter.hasNext() ) {
206 Map.Entry<T,T> e = iter.next();
207 result[i++] = e.getKey();
208 result[i++] = e.getValue();
209 }
210
211 for(i=0; i<result.length;i++) {
212 if(alreadyVisitedEntities == null) {
213 result[i] = load(result[i], false);
214 } else {
215 result[i] = loadRecursive(result[i], alreadyVisitedEntities);
216 }
217 }
218 map.clear();
219 for(i = 0; i < originalMapSize; i+=2 ) {
220 map.put(
221 (T)result[i],
222 (T)result[i+1]
223 );
224 }
225 return map;
226 }
227
228 public <T extends Object> Collection<T> load(Collection<T> collection, boolean recursive){
229 if(collection == null) {
230 return null;
231 }
232
233 Collection<T> loadedCollection;
234 if(isRecursiveEnabled && recursive) {
235 logger.info("---- starting recursive load for cdm entity collection");
236 Set<CdmBase> alreadyVisitedEntities = new HashSet<CdmBase>();
237 Collection<T> cachedCollection = load(collection, alreadyVisitedEntities);
238 alreadyVisitedEntities.clear();
239 logger.info("---- ending recursive load for cdm entity collection \n");
240 loadedCollection = cachedCollection;
241 } else {
242 loadedCollection = load(collection, null);
243 }
244 return loadedCollection;
245 }
246
247 @SuppressWarnings("unchecked")
248 private <T extends Object> Collection<T> load(Collection<T> collection, Set<CdmBase> alreadyVisitedEntities){
249 int length = collection.size();
250 Object[] result = new Object[length];
251 Iterator<T> collectionItr = collection.iterator();
252 int count = 0;
253 while(collectionItr.hasNext()) {
254 Object obj = collectionItr.next();
255 if(alreadyVisitedEntities == null) {
256 result[count] = load(obj, false);
257 } else {
258 result[count] = loadRecursive(obj, alreadyVisitedEntities);
259 }
260
261 count++;
262 }
263
264 collection.clear();
265
266 for ( int i = 0; i < length; i++ ) {
267 collection.add((T)result[i]);
268 }
269
270 return collection;
271 }
272
273
274 /**
275 * Puts the (Key,Value) pair of ({@link java.util.UUID}, {@link eu.etaxonomy.cdm.model.common.CdmBase}),
276 * in the cache corresponding to the given cache id
277 *
278 * @param cacheId
279 * @param uuid
280 * @param cdmEntity
281 */
282 public CdmBase load(CdmBase cdmEntity, boolean recursive) {
283 if(cdmEntity == null) {
284 return null;
285 }
286
287
288 // start by looking up the cdm entity in the cache
289 CdmBase cachedCdmEntity = getFromCache(cdmEntity);
290
291 if(cachedCdmEntity != null) {
292 // if cdm entity was found in cache then
293 logger.info(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " already exists");
294 // .. return if the cached and input objects are identical, else (this is a newly loaded object so) continue
295 if(cachedCdmEntity == cdmEntity) {
296 return cachedCdmEntity;
297 }
298 }
299
300 CdmBase loadedCdmBase;
301 if(isRecursiveEnabled && recursive) {
302 logger.info("---- starting recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
303 Set<CdmBase> alreadyVisitedEntities = new HashSet<CdmBase>();
304 CdmBase cb = loadRecursive(cdmEntity, alreadyVisitedEntities);
305 alreadyVisitedEntities.clear();
306 logger.info("---- ending recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + "\n");
307 loadedCdmBase = cb;
308 } else {
309 loadedCdmBase = load(cdmEntity);
310 }
311 return loadedCdmBase;
312
313 }
314
315
316 private CdmBase load(CdmBase cdmEntity) {
317 logger.info("loading object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
318
319 // start by looking up the cdm entity in the cache
320 CdmBase cachedCdmEntity = getFromCache(cdmEntity);
321
322 if(cachedCdmEntity != null) {
323 // if cdm entity was found in cache then return ...
324 logger.info(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " already exists");
325 return cachedCdmEntity;
326 } else {
327 // ... else save the entity in the cache
328 getCache().put(new Element(generateKey(cdmEntity), cdmEntity));
329 logger.info(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " put in cache");
330 return cdmEntity;
331 }
332 }
333
334 private CdmBase loadRecursive(CdmBase cdmEntity, Set<CdmBase> alreadyVisitedEntities) {
335
336 CdmBase cachedCdmEntity = load(cdmEntity);
337
338 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
339 // since there could be new or deleted objects in the cdmEntity sub-graph
340
341 // start by getting the fields from the cdm entity
342 String className = cdmEntity.getClass().getName();
343 CdmModelFieldPropertyFromClass cmgmfc = getFromCdmlibModelCache(className);
344 if(cmgmfc != null) {
345 alreadyVisitedEntities.add(cdmEntity);
346 List<String> fields = cmgmfc.getFields();
347 for(String field : fields) {
348 // retrieve the actual object corresponding to the field.
349 // this object will be either a CdmBase or a Collection / Map
350 // with CdmBase as the generic type
351
352 CdmBase cdmEntityInSubGraph = getCdmBaseTypeFieldValue(cdmEntity, cachedCdmEntity, field, alreadyVisitedEntities);
353 if(cdmEntityInSubGraph != null) {
354 if(!alreadyVisitedEntities.contains(cdmEntityInSubGraph)) {
355 logger.info("recursive loading object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
356 loadRecursive(cdmEntityInSubGraph, alreadyVisitedEntities);
357 } else {
358 logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
359 }
360 }
361 }
362 } else {
363 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
364 "The cache may be corrupted or not in sync with the latest model version" );
365 }
366 return cachedCdmEntity;
367 }
368
369
370 private CdmBase getCdmBaseTypeFieldValue(CdmBase cdmEntity,
371 CdmBase cachedCdmEntity,
372 String fieldName,
373 Set<CdmBase> alreadyVisitedEntities) {
374
375 // this method attempts to make sure that for any two objects found in
376 // the object graph, if they are equal then they should also be the same,
377 // which is crucial for the merge to work
378 if(cachedCdmEntity == null) {
379 throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
380 }
381
382 Class<?> clazz = cdmEntity.getClass();
383 try {
384 // this call will search in the provided class as well as
385 // the super classes until it finds the field
386 Field field = ReflectionUtils.findField(clazz, fieldName);
387
388 if(field == null) {
389 throw new CdmClientCacheException("Field '" + fieldName
390 + "' not found when searching in class '" + clazz.getName() + "' and its supercalsses");
391 }
392 field.setAccessible(true);
393 Object o = field.get(cdmEntity);
394
395 if(o != null && o instanceof HibernateProxy) {
396 LazyInitializer hli = ((HibernateProxy)o).getHibernateLazyInitializer();
397 if(!hli.isUninitialized()) {
398 o = ((HibernateProxy) o).getHibernateLazyInitializer().getImplementation();
399 field.set(cdmEntity, o);
400 }
401 }
402
403 if(o != null && o instanceof PersistentCollection) {
404 PersistentCollection pc = ((PersistentCollection)o);
405 if(pc.wasInitialized()) {
406 o = ProxyUtils.getObject(pc);
407 field.set(cdmEntity, o);
408 }
409 }
410
411 //field.set(cdmEntity, o);
412 CdmBase cdmEntityInSubGraph = null;
413 if(o != null
414 && !ProxyFactory.isProxyClass(o.getClass())
415 && !(o instanceof PersistentCollection) ) {
416
417 if(CdmBase.class.isAssignableFrom(o.getClass())) {
418 logger.info("found initialised cdm entity '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
419 cdmEntityInSubGraph = (CdmBase)o;
420 CdmBase cachedCdmEntityInSubGraph = getFromCache(cdmEntityInSubGraph);
421 // making sure that the field in cached cdm entity is always
422 // up-to-date by setting to the value of the cdm entity being loaded
423 // the only execption to this is found below
424 field.set(cachedCdmEntity, o);
425
426 // the only exception to updating the field to the latest value
427 // is the case where the field has been already initialised, cached and
428 // is not the same as the one in the cache, in which case we set the value
429 // of the field to the one found in the cache
430 if(cachedCdmEntityInSubGraph != null) {
431 if(cachedCdmEntityInSubGraph != cdmEntityInSubGraph) {
432 logger.info("setting cached + real value to '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
433 field.set(cachedCdmEntity, cachedCdmEntityInSubGraph);
434 }
435 }
436 } else if(o instanceof Map) {
437 loadRecursive((Map)o, alreadyVisitedEntities);
438 } else if(o instanceof Collection) {
439 loadRecursive((Collection)o, alreadyVisitedEntities);
440 }
441 }
442 // we return the original cdm entity in the sub graph because we
443 // want to continue to recurse on the input cdm entity graph
444 // and not the one in the cache
445 return cdmEntityInSubGraph;
446 } catch (SecurityException e) {
447 throw new CdmClientCacheException(e);
448 } catch (IllegalArgumentException e) {
449 throw new CdmClientCacheException(e);
450 } catch (IllegalAccessException e) {
451 throw new CdmClientCacheException(e);
452 }
453 }
454
455
456 public void put(CdmBase cdmEntity) {
457 CdmEntityCacheKey id = new CdmEntityCacheKey(cdmEntity);
458 Element cachedCdmEntityElement = getCacheElement(id);
459
460 if(cachedCdmEntityElement == null) {
461 cachedCdmEntityElement = cdmServiceCacher.getCacheElement(cdmEntity.getUuid());
462 if(cachedCdmEntityElement != null) {
463 logger.info("Cdm Entity with id : " + cdmEntity.getId() + " already exists in permanent cache. Ignoring put.");
464 return;
465 }
466 }
467
468 getCache().put(new Element(id, cdmEntity));
469 }
470
471
472 private Element getCacheElement(CdmEntityCacheKey key) {
473 return getCache().get(key);
474 }
475
476 public CdmModelFieldPropertyFromClass getFromCdmlibModelCache(String className) {
477 Element e = cdmlibModelCache.get(className);
478 if (e == null) {
479 return null;
480 } else {
481 return (CdmModelFieldPropertyFromClass) e.getObjectValue();
482 }
483 }
484
485 public CdmBase getFromCache(CdmEntityCacheKey id) {
486 Element e = getCacheElement(id);
487 if (e == null) {
488 return null;
489 } else {
490 return (CdmBase) e.getObjectValue();
491 }
492 }
493
494 public CdmBase getFromCache(Class<? extends CdmBase> clazz, int id) {
495 CdmEntityCacheKey cacheId = generateKey(clazz,id);
496 return getFromCache(cacheId);
497 }
498
499 public CdmBase getFromCache(CdmBase cdmBase) {
500
501 CdmEntityCacheKey cacheId = generateKey(cdmBase);
502 // first try this cache
503 CdmBase cachedCdmEntity = getFromCache(cacheId);
504
505 if(cachedCdmEntity == null) {
506 // ... then try the permanent cache
507 cachedCdmEntity = cdmServiceCacher.load(cdmBase.getUuid());
508 }
509
510 return cachedCdmEntity;
511 }
512
513 public CdmBase getFromCache(CdmBase cdmBase, Class<? extends CdmBase> clazz) {
514
515 cdmBase = CdmBase.deproxy(cdmBase, clazz);
516 return getFromCache(cdmBase);
517 }
518
519 public List<CdmBase> getAllEntities() {
520 List<CdmBase> entities = new ArrayList<CdmBase>();
521 Map<String, CdmBase> elementsMap = getCache().getAllWithLoader(getCache().getKeys(), null);
522 for (Map.Entry<String, CdmBase> entry : elementsMap.entrySet()) {
523 entities.add(entry.getValue());
524 }
525 return entities;
526 }
527
528 public boolean exists(CdmEntityCacheKey key) {
529 return (getCacheElement(key) != null);
530 }
531
532 public boolean existsAndIsNotNull(CdmEntityCacheKey id) {
533 return getFromCache(id) != null;
534 }
535
536 public void clear() {
537 cache.removeAll();
538 }
539
540 public void dispose() {
541 cache.removeAll();
542 cache.flush();
543 cdmServiceCacher.getDefaultCacheManager().removeCache(cacheId);
544 }
545
546
547 public static CdmEntityCacheKey generateKey(Class<? extends CdmBase> clazz, int id) {
548 return new CdmEntityCacheKey(clazz, id);
549 }
550
551
552 public static CdmEntityCacheKey generateKey(CdmBase cdmBase) {
553 Class<? extends CdmBase> entityClass = cdmBase.getClass();
554 int id = cdmBase.getId();
555 return new CdmEntityCacheKey(entityClass, id);
556 }
557
558 public static boolean isRecursiveEnabled() {
559 return isRecursiveEnabled;
560 }
561
562 public static void setRecursiveEnabled(boolean ire) {
563 isRecursiveEnabled = ire;
564 }
565
566
567
568 }