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
.store
.MemoryStoreEvictionPolicy
;
27 import org
.apache
.log4j
.Logger
;
28 import org
.hibernate
.collection
.spi
.PersistentCollection
;
29 import org
.springframework
.util
.ReflectionUtils
;
31 import eu
.etaxonomy
.cdm
.api
.cache
.CdmServiceCacher
;
32 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
33 import eu
.etaxonomy
.taxeditor
.session
.ICdmEntitySessionManager
;
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.
49 public class CdmTransientEntityCacher
{
51 private static final Logger logger
= Logger
.getLogger(CdmTransientEntityCacher
.class);
54 private ICdmEntitySessionManager cdmEntitySessionManager
;
56 private static CdmServiceCacher cdmServiceCacher
;
58 private String cacheId
;
62 private Cache cdmlibModelCache
;
64 private static boolean isRecursiveEnabled
= true;
66 public static enum CollectionType
{
72 public String
toString() {
73 return this.name().toLowerCase();
77 private CdmTransientEntityCacher() {
81 public CdmTransientEntityCacher(String cacheId
, ICdmEntitySessionManager cdmEntitySessionManager
) {
82 this.cacheId
= cacheId
;
84 cache
= new Cache(getEntityCacheConfiguration(cacheId
));
85 cdmServiceCacher
.getDefaultCacheManager().addCache(cache
);
87 cdmlibModelCache
= CdmRemoteCacheManager
.getInstance().getCdmModelGetMethodsCache();
88 this.cdmEntitySessionManager
= cdmEntitySessionManager
;
91 public CdmTransientEntityCacher(Object obj
, ICdmEntitySessionManager cdmEntitySessionManager
) {
92 this(obj
.getClass().getName() + String
.valueOf(obj
.hashCode()), cdmEntitySessionManager
);
96 * Returns the default cache configuration.
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
)
106 // default ttl and tti set to 2 hours
107 .timeToLiveSeconds(60*60*2)
108 .timeToIdleSeconds(60*60*2);
112 public static void setDefaultCacher(CdmServiceCacher css
) {
113 cdmServiceCacher
= css
;
117 * Returns the cache corresponding to the cache id
122 private Cache
getCache() {
123 return cdmServiceCacher
.getDefaultCacheManager().getCache(cacheId
);
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
);
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
);
149 logger
.info("No caching yet for type " + obj
.getClass().getName());
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");
163 return load(map
, null);
168 private <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, Set
<CdmBase
> alreadyVisitedEntities
){
169 if(map
== null || map
.isEmpty()) {
173 int originalMapSize
= map
.size();
174 Object
[] result
= new Object
[ map
.size() * 2 ];
175 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
177 while ( iter
.hasNext() ) {
178 Map
.Entry
<T
,T
> e
= iter
.next();
179 result
[i
++] = e
.getKey();
180 result
[i
++] = e
.getValue();
183 for(i
=0; i
<result
.length
;i
++) {
184 if(alreadyVisitedEntities
== null) {
185 result
[i
] = load(result
[i
], false);
187 result
[i
] = loadRecursive(result
[i
], alreadyVisitedEntities
);
191 for(i
= 0; i
< originalMapSize
; i
+=2 ) {
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
;
210 loadedCollection
= load(collection
, null);
212 return loadedCollection
;
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();
221 while(collectionItr
.hasNext()) {
222 if(alreadyVisitedEntities
== null) {
223 result
[count
] = load(collectionItr
.next(), false);
225 result
[count
] = loadRecursive(collectionItr
.next(), alreadyVisitedEntities
);
232 for ( int i
= 0; i
< length
; i
++ ) {
233 collection
.add((T
)result
[i
]);
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
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");
258 loadedCdmBase
= load(cdmEntity
);
260 return loadedCdmBase
;
265 private CdmBase
load(CdmBase cdmEntity
) {
266 logger
.info("loading object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
268 // start by looking up the cdm entity in the cache
269 CdmBase cachedCdmEntity
= getFromCache(cdmEntity
);
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
;
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");
283 private CdmBase
loadRecursive(CdmBase cdmEntity
, Set
<CdmBase
> alreadyVisitedEntities
) {
285 CdmBase cachedCdmEntity
= load(cdmEntity
);
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
290 // start by getting the fields from the cdm entity
291 String className
= cdmEntity
.getClass().getName();
292 CdmModelFieldPropertyFromClass cmgmfc
= getFromCdmlibModelCache(className
);
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
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
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
);
312 logger
.info("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + "already visited");
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" );
320 return cachedCdmEntity
;
324 private CdmBase
getCdmBaseTypeFieldValue(CdmBase cdmEntity
,
325 CdmBase cachedCdmEntity
,
327 Set
<CdmBase
> alreadyVisitedEntities
) {
330 if(cachedCdmEntity
== null) {
331 throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
334 Class
<?
> clazz
= cdmEntity
.getClass();
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
);
341 throw new CdmClientCacheException("Field '" + fieldName
342 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
344 field
.setAccessible(true);
345 Object o
= field
.get(cdmEntity
);
347 CdmBase cdmEntityInSubGraph
= null;
349 && !ProxyFactory
.isProxyClass(o
.getClass())
350 && !(o
instanceof PersistentCollection
) ) {
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
);
358 if(cachedCdmEntityInSubGraph
!= null) {
359 if(cachedCdmEntityInSubGraph
!= cdmEntityInSubGraph
) {
360 field
.set(cachedCdmEntity
, cachedCdmEntityInSubGraph
);
363 field
.set(cachedCdmEntity
, cdmEntityInSubGraph
);
365 } else if(o
instanceof Map
) {
366 loadRecursive((Map
)o
, alreadyVisitedEntities
);
367 } else if(o
instanceof Collection
) {
368 loadRecursive((Collection
)o
, alreadyVisitedEntities
);
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
);
384 public void put(CdmBase cdmEntity
) {
385 CdmEntityCacheKey id
= new CdmEntityCacheKey(cdmEntity
);
386 Element cachedCdmEntityElement
= getCacheElement(id
);
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.");
396 getCache().put(new Element(id
, cdmEntity
));
400 private Element
getCacheElement(CdmEntityCacheKey key
) {
401 return getCache().get(key
);
404 public CdmModelFieldPropertyFromClass
getFromCdmlibModelCache(String className
) {
405 Element e
= cdmlibModelCache
.get(className
);
409 return (CdmModelFieldPropertyFromClass
) e
.getObjectValue();
413 public CdmBase
getFromCache(CdmEntityCacheKey id
) {
414 Element e
= getCacheElement(id
);
418 return (CdmBase
) e
.getObjectValue();
422 public CdmBase
getFromCache(Class
<?
extends CdmBase
> clazz
, int id
) {
423 CdmEntityCacheKey cacheId
= generateKey(clazz
,id
);
424 return getFromCache(cacheId
);
427 public CdmBase
getFromCache(CdmBase cdmBase
) {
429 CdmEntityCacheKey cacheId
= generateKey(cdmBase
);
430 CdmBase cachedCdmEntity
= getFromCache(cacheId
);
432 if(cachedCdmEntity
== null) {
433 // ... then try the permanent (uuid-based) cache
434 cachedCdmEntity
= cdmServiceCacher
.getFromCache(cdmBase
.getUuid());
437 return cachedCdmEntity
;
440 public CdmBase
getFromCache(CdmBase cdmBase
, Class
<?
extends CdmBase
> clazz
) {
442 cdmBase
= CdmBase
.deproxy(cdmBase
, clazz
);
443 CdmEntityCacheKey cacheId
= generateKey(cdmBase
);
445 CdmBase cachedCdmEntity
= getFromCache(cacheId
);
447 if(cachedCdmEntity
== null) {
448 // ... then try the permanent (uuid-based) cache
449 cachedCdmEntity
= cdmServiceCacher
.getFromCache(cdmBase
.getUuid());
452 return cachedCdmEntity
;
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());
464 public boolean exists(CdmEntityCacheKey key
) {
465 return (getCacheElement(key
) != null);
468 public boolean existsAndIsNotNull(CdmEntityCacheKey id
) {
469 return getFromCache(id
) != null;
472 public void dispose() {
478 public static CdmEntityCacheKey
generateKey(Class
<?
extends CdmBase
> clazz
, int id
) {
479 return new CdmEntityCacheKey(clazz
, id
);
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
);
489 public static boolean isRecursiveEnabled() {
490 return isRecursiveEnabled
;
493 public static void setRecursiveEnabled(boolean ire
) {
494 isRecursiveEnabled
= ire
;