3 * Copyright (C) 2014 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.taxeditor
.remoting
.cache
;
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
;
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
;
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
;
35 import eu
.etaxonomy
.cdm
.api
.cache
.CdmServiceCacher
;
36 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
37 import eu
.etaxonomy
.taxeditor
.session
.ICdmEntitySessionManager
;
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.
53 public class CdmTransientEntityCacher
{
55 private static final Logger logger
= Logger
.getLogger(CdmTransientEntityCacher
.class);
58 private ICdmEntitySessionManager cdmEntitySessionManager
;
60 private static CdmServiceCacher cdmServiceCacher
;
62 private String cacheId
;
66 private Cache cdmlibModelCache
;
68 private static boolean isRecursiveEnabled
= true;
70 public static enum CollectionType
{
76 public String
toString() {
77 return this.name().toLowerCase();
81 private CdmTransientEntityCacher() {
85 public CdmTransientEntityCacher(String cacheId
, ICdmEntitySessionManager cdmEntitySessionManager
) {
86 this.cacheId
= cacheId
;
88 cache
= new Cache(getEntityCacheConfiguration(cacheId
));
89 cdmServiceCacher
.getDefaultCacheManager().addCache(cache
);
91 cdmlibModelCache
= CdmRemoteCacheManager
.getInstance().getCdmModelGetMethodsCache();
92 this.cdmEntitySessionManager
= cdmEntitySessionManager
;
97 public CdmTransientEntityCacher(Object obj
, ICdmEntitySessionManager cdmEntitySessionManager
) {
98 this(obj
.getClass().getName() + String
.valueOf(obj
.hashCode()), cdmEntitySessionManager
);
102 * Returns the default cache configuration.
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
)
115 // default ttl and tti set to 2 hours
116 .timeToLiveSeconds(60*60*2)
117 .timeToIdleSeconds(60*60*2)
119 .sizeOfPolicy(sizeOfConfig
);
125 public static void setDefaultCacher(CdmServiceCacher css
) {
126 cdmServiceCacher
= css
;
129 public LiveCacheStatistics
getCacheStatistics() {
130 return cache
.getLiveCacheStatistics();
134 * Returns the cache corresponding to the cache id
139 private Cache
getCache() {
140 return cdmServiceCacher
.getDefaultCacheManager().getCache(cacheId
);
143 @SuppressWarnings("unchecked")
144 public <T
extends Object
> T
load(T obj
, boolean recursive
) {
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
);
159 @SuppressWarnings("unchecked")
160 private <T
extends Object
> T
loadRecursive(T obj
, Set
<CdmBase
> alreadyVisitedEntities
) {
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
);
173 logger
.info("No caching yet for type " + obj
.getClass().getName());
178 public <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, boolean recursive
){
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");
191 return load(map
, null);
196 private <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, Set
<CdmBase
> alreadyVisitedEntities
){
197 if(map
== null || map
.isEmpty()) {
201 int originalMapSize
= map
.size();
202 Object
[] result
= new Object
[ map
.size() * 2 ];
203 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
205 while ( iter
.hasNext() ) {
206 Map
.Entry
<T
,T
> e
= iter
.next();
207 result
[i
++] = e
.getKey();
208 result
[i
++] = e
.getValue();
211 for(i
=0; i
<result
.length
;i
++) {
212 if(alreadyVisitedEntities
== null) {
213 result
[i
] = load(result
[i
], false);
215 result
[i
] = loadRecursive(result
[i
], alreadyVisitedEntities
);
219 for(i
= 0; i
< originalMapSize
; i
+=2 ) {
228 public <T
extends Object
> Collection
<T
> load(Collection
<T
> collection
, boolean recursive
){
229 if(collection
== null) {
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
;
242 loadedCollection
= load(collection
, null);
244 return loadedCollection
;
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();
253 while(collectionItr
.hasNext()) {
254 Object obj
= collectionItr
.next();
255 if(alreadyVisitedEntities
== null) {
256 result
[count
] = load(obj
, false);
258 result
[count
] = loadRecursive(obj
, alreadyVisitedEntities
);
266 for ( int i
= 0; i
< length
; i
++ ) {
267 collection
.add((T
)result
[i
]);
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
282 public CdmBase
load(CdmBase cdmEntity
, boolean recursive
) {
283 if(cdmEntity
== null) {
288 // start by looking up the cdm entity in the cache
289 CdmBase cachedCdmEntity
= getFromCache(cdmEntity
);
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
;
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");
309 loadedCdmBase
= load(cdmEntity
);
311 return loadedCdmBase
;
316 private CdmBase
load(CdmBase cdmEntity
) {
317 logger
.info("loading object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
319 // start by looking up the cdm entity in the cache
320 CdmBase cachedCdmEntity
= getFromCache(cdmEntity
);
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
;
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");
334 private CdmBase
loadRecursive(CdmBase cdmEntity
, Set
<CdmBase
> alreadyVisitedEntities
) {
336 CdmBase cachedCdmEntity
= load(cdmEntity
);
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
341 // start by getting the fields from the cdm entity
342 String className
= cdmEntity
.getClass().getName();
343 CdmModelFieldPropertyFromClass cmgmfc
= getFromCdmlibModelCache(className
);
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
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
);
358 logger
.info("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + " already visited");
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" );
366 return cachedCdmEntity
;
370 private CdmBase
getCdmBaseTypeFieldValue(CdmBase cdmEntity
,
371 CdmBase cachedCdmEntity
,
373 Set
<CdmBase
> alreadyVisitedEntities
) {
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");
382 Class
<?
> clazz
= cdmEntity
.getClass();
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
);
389 throw new CdmClientCacheException("Field '" + fieldName
390 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
392 field
.setAccessible(true);
393 Object o
= field
.get(cdmEntity
);
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
);
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
);
411 //field.set(cdmEntity, o);
412 CdmBase cdmEntityInSubGraph
= null;
414 && !ProxyFactory
.isProxyClass(o
.getClass())
415 && !(o
instanceof PersistentCollection
) ) {
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
);
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
);
436 } else if(o
instanceof Map
) {
437 loadRecursive((Map
)o
, alreadyVisitedEntities
);
438 } else if(o
instanceof Collection
) {
439 loadRecursive((Collection
)o
, alreadyVisitedEntities
);
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
);
456 public void put(CdmBase cdmEntity
) {
457 CdmEntityCacheKey id
= new CdmEntityCacheKey(cdmEntity
);
458 Element cachedCdmEntityElement
= getCacheElement(id
);
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.");
468 getCache().put(new Element(id
, cdmEntity
));
472 private Element
getCacheElement(CdmEntityCacheKey key
) {
473 return getCache().get(key
);
476 public CdmModelFieldPropertyFromClass
getFromCdmlibModelCache(String className
) {
477 Element e
= cdmlibModelCache
.get(className
);
481 return (CdmModelFieldPropertyFromClass
) e
.getObjectValue();
485 public CdmBase
getFromCache(CdmEntityCacheKey id
) {
486 Element e
= getCacheElement(id
);
490 return (CdmBase
) e
.getObjectValue();
494 public CdmBase
getFromCache(Class
<?
extends CdmBase
> clazz
, int id
) {
495 CdmEntityCacheKey cacheId
= generateKey(clazz
,id
);
496 return getFromCache(cacheId
);
499 public CdmBase
getFromCache(CdmBase cdmBase
) {
501 CdmEntityCacheKey cacheId
= generateKey(cdmBase
);
502 // first try this cache
503 CdmBase cachedCdmEntity
= getFromCache(cacheId
);
505 if(cachedCdmEntity
== null) {
506 // ... then try the permanent cache
507 cachedCdmEntity
= cdmServiceCacher
.load(cdmBase
.getUuid());
510 return cachedCdmEntity
;
513 public CdmBase
getFromCache(CdmBase cdmBase
, Class
<?
extends CdmBase
> clazz
) {
515 cdmBase
= CdmBase
.deproxy(cdmBase
, clazz
);
516 return getFromCache(cdmBase
);
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());
528 public boolean exists(CdmEntityCacheKey key
) {
529 return (getCacheElement(key
) != null);
532 public boolean existsAndIsNotNull(CdmEntityCacheKey id
) {
533 return getFromCache(id
) != null;
536 public void clear() {
540 public void dispose() {
543 cdmServiceCacher
.getDefaultCacheManager().removeCache(cacheId
);
547 public static CdmEntityCacheKey
generateKey(Class
<?
extends CdmBase
> clazz
, int id
) {
548 return new CdmEntityCacheKey(clazz
, id
);
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
);
558 public static boolean isRecursiveEnabled() {
559 return isRecursiveEnabled
;
562 public static void setRecursiveEnabled(boolean ire
) {
563 isRecursiveEnabled
= ire
;