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