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
.cdm
.api
.cache
;
12 import java
.lang
.reflect
.Field
;
13 import java
.lang
.reflect
.InvocationTargetException
;
14 import java
.lang
.reflect
.Method
;
15 import java
.util
.ArrayList
;
16 import java
.util
.HashSet
;
17 import java
.util
.Iterator
;
18 import java
.util
.List
;
21 import java
.util
.UUID
;
23 import javassist
.util
.proxy
.ProxyFactory
;
25 import net
.sf
.ehcache
.Cache
;
26 import net
.sf
.ehcache
.Element
;
27 import net
.sf
.ehcache
.config
.CacheConfiguration
;
28 import net
.sf
.ehcache
.store
.MemoryStoreEvictionPolicy
;
30 import org
.apache
.log4j
.Logger
;
31 import org
.hibernate
.collection
.spi
.PersistentCollection
;
32 import org
.hibernate
.proxy
.pojo
.javassist
.JavassistLazyInitializer
;
33 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
34 import org
.springframework
.util
.ReflectionUtils
;
36 import eu
.etaxonomy
.cdm
.api
.service
.ICommonService
;
37 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
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
extends CdmServiceCacher
{
55 private static final Logger logger
= Logger
.getLogger(CdmTransientEntityCacher
.class);
58 private CdmEntityCachingUtils cdmEntityCachingUtils
;
61 private ICommonService commonService
;
63 private String cacheId
;
67 private Cache cdmlibModelCache
;
69 private static boolean isRecursiveEnabled
= true;
72 public CdmTransientEntityCacher(String cacheId
) {
73 this.cacheId
= cacheId
;
75 cache
= new Cache(getEntityCacheConfiguration(cacheId
));
76 getDefaultCacheManager().addCache(cache
);
78 cdmlibModelCache
= CdmRemoteCacheManager
.getInstance().getCdmModelGetMethodsCache();
82 public CdmTransientEntityCacher(Object obj
) {
83 this(obj
.getClass().getName() + String
.valueOf(obj
.hashCode()));
87 * Returns the default cache configuration.
91 private CacheConfiguration
getEntityCacheConfiguration(String cacheId
) {
92 // For a better understanding on how to size caches, refer to
93 // http://ehcache.org/documentation/configuration/cache-size
94 return new CacheConfiguration(cacheId
, 500)
95 .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy
.LFU
)
97 // default ttl and tti set to 2 hours
98 .timeToLiveSeconds(60*60*2)
99 .timeToIdleSeconds(60*60*2);
105 * Returns the cache corresponding to the cache id
110 private Cache
getCache() {
111 return getDefaultCacheManager().getCache(cacheId
);
114 public <T
extends Object
> T
load(T obj
, boolean recursive
) {
115 if(obj
instanceof CdmBase
) {
116 return load(obj
, recursive
);
117 } else if (obj
instanceof Map
) {
118 return load(obj
, recursive
);
124 @SuppressWarnings("unchecked")
125 private <T
extends Object
> T
loadRecursive(T obj
, Set
<CdmBase
> alreadyVisitedEntities
) {
126 if(obj
instanceof CdmBase
) {
127 return loadRecursive(obj
, alreadyVisitedEntities
);
128 } else if (obj
instanceof Map
) {
129 return (T
) load((Map
<T
,T
>)obj
, alreadyVisitedEntities
);
131 logger
.info("No caching yet for type " + obj
.getClass().getName());
136 public <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, boolean recursive
){
137 if(isRecursiveEnabled
&& recursive
) {
138 logger
.info("---- starting recursive load for map");
139 Set
<CdmBase
> alreadyVisitedEntities
= new HashSet
<CdmBase
>();
140 Map
<T
,T
> cachedMap
= load(map
, alreadyVisitedEntities
);
141 alreadyVisitedEntities
.clear();
142 logger
.info("---- ending recursive load for cdm entity map \n");
145 return load(map
, null);
149 private <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, Set
<CdmBase
> alreadyVisitedEntities
){
150 if(map
== null || map
.isEmpty()) {
154 int originalMapSize
= map
.size();
155 Object
[] result
= new Object
[ map
.size() * 2 ];
156 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
158 while ( iter
.hasNext() ) {
159 Map
.Entry
<T
,T
> e
= (Map
.Entry
<T
,T
>) iter
.next();
160 result
[i
++] = e
.getKey();
161 result
[i
++] = e
.getValue();
164 for(i
=0; i
<result
.length
;i
++) {
165 if(alreadyVisitedEntities
== null) {
166 result
[i
] = load(result
[i
], false);
168 result
[i
] = loadRecursive(result
[i
], alreadyVisitedEntities
);
172 for(i
= 0; i
< originalMapSize
; i
+=2 ) {
181 * Puts the (Key,Value) pair of ({@link java.util.UUID}, {@link eu.etaxonomy.cdm.model.common.CdmBase}),
182 * in the cache corresponding to the given cache id
188 public CdmBase
load(CdmBase cdmEntity
, boolean recursive
) {
190 if(isRecursiveEnabled
&& recursive
) {
191 logger
.info("---- starting recursive load for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
192 Set
<CdmBase
> alreadyVisitedEntities
= new HashSet
<CdmBase
>();
193 CdmBase cb
= loadRecursive(cdmEntity
, alreadyVisitedEntities
);
194 alreadyVisitedEntities
.clear();
195 logger
.info("---- ending recursive load for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + "\n");
198 return load(cdmEntity
);
201 // split into two methods
202 // - load(CdmBase cdmBase)
203 // - recursiveLoad(CdmBase cdmbase, List<CdmBase> alreadyVisitedEntities)
204 // : the entity to be visted should added before the recursive call
205 // and deleted after the call to end finally with a list
206 // of size zero at the end
207 // : entity to recurse should be the original entity and
208 // not the cached one
213 private CdmBase
load(CdmBase cdmEntity
) {
214 logger
.info("loading object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
216 // start by looking up the cdm entity in the cache
217 CdmBase cachedCdmEntity
= getFromCache(cdmEntity
);
219 if(cachedCdmEntity
!= null) {
220 // if cdm entity was found in cache then return ...
221 logger
.info(" - object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + " already exists");
222 return cachedCdmEntity
;
224 // ... else save the entity in the cache
225 getCache().put(new Element(generateKey(cdmEntity
), cdmEntity
));
226 logger
.info(" - object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + " put in cache");
231 private CdmBase
loadRecursive(CdmBase cdmEntity
, Set
<CdmBase
> alreadyVisitedEntities
) {
233 CdmBase cachedCdmEntity
= load(cdmEntity
);
235 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
236 // since there could be new initialized object in the cdmEntity sub-graph
238 // start by getting the fields from the cdm entity
239 String className
= cdmEntity
.getClass().getName();
240 CdmModelFieldPropertyFromClass cmgmfc
= getFromCdmlibModelCache(className
);
242 alreadyVisitedEntities
.add(cdmEntity
);
243 List
<String
> fields
= cmgmfc
.getFields();
244 for(String field
: fields
) {
245 // retrieve the actual object corresponding to the field.
246 // this object will be either a CdmBase or a Collection / Map
247 // with CdmBase as the generic type
249 // In the case that the returned is either a Collection or a Map
250 // the individual objects inside these also need to be loaded
251 // by calling the corresponding cachify method in the
252 // CdmEntityCachingUtils
254 CdmBase cdmEntityInSubGraph
= getCdmBaseTypeFieldValue(cdmEntity
, cachedCdmEntity
, field
);
255 if(cdmEntityInSubGraph
!= null && !alreadyVisitedEntities
.contains(cdmEntityInSubGraph
)) {
256 logger
.info("recursive loading object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId());
257 loadRecursive(cdmEntityInSubGraph
, alreadyVisitedEntities
);
261 return cachedCdmEntity
;
265 private CdmBase
getCdmBaseTypeFieldValue(CdmBase cdmEntity
, CdmBase cachedCdmEntity
, String fieldName
) {
268 if(cachedCdmEntity
== null) {
269 throw new CdmClientCacheException("When trying to set filed value, the cached cdm entity cannot be null");
272 Class
<?
> clazz
= cdmEntity
.getClass();
274 // this call will search in the provided class as well as
275 // the super classes until it finds the field
276 Field field
= ReflectionUtils
.findField(clazz
, fieldName
);
277 // logger.info("Retrieving field " + field
278 // + " from cdm entity of type " + c.getName()
279 // + " with id " + cdmEntity.getId());
283 throw new CdmClientCacheException("Field '" + fieldName
284 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
286 field
.setAccessible(true);
287 Object o
= field
.get(cdmEntity
);
289 // logger.info(" - resulting object is null");
290 // } else if(ProxyFactory.isProxyClass(o.getClass())) {
291 // logger.info(" - resulting object is a proxy object");
292 // } else if(o instanceof PersistentCollection) {
293 // logger.info(" - resulting object is a persistent collection object");
295 // logger.info(" - resulting object is of type " + o.getClass().getName());
297 CdmBase cdmEntityInSubGraph
= null;
299 && !ProxyFactory
.isProxyClass(o
.getClass())
300 && !(o
instanceof PersistentCollection
)
301 && CdmBase
.class.isAssignableFrom(o
.getClass())) {
302 // logger.info("retrieving object of type " + o.getClass().getName()
303 // + " with id " + ((CdmBase)o).getId());
305 //FIXME:Remoting add case for collection / map
306 //FIXME:Remoting if cached entity != original entity then
307 // replace original with cache using
308 // field.set and return original entity
309 cdmEntityInSubGraph
= (CdmBase
)o
;
311 CdmBase cachedCdmEntityInSubGraph
= getFromCache(cdmEntityInSubGraph
);
313 if(cachedCdmEntityInSubGraph
!= null) {
314 if(cachedCdmEntityInSubGraph
!= cdmEntityInSubGraph
) {
315 field
.set(cachedCdmEntity
, cachedCdmEntityInSubGraph
);
318 field
.set(cachedCdmEntity
, cdmEntityInSubGraph
);
321 // we return the original cdm entity in the sub graph because we
322 // want to continue to recurse on the input cdm entity graph
323 // and not the one in the cache
324 return cdmEntityInSubGraph
;
325 } catch (SecurityException e
) {
326 throw new CdmClientCacheException(e
);
327 } catch (IllegalArgumentException e
) {
328 throw new CdmClientCacheException(e
);
329 } catch (IllegalAccessException e
) {
330 throw new CdmClientCacheException(e
);
334 public void put(CdmBase cdmEntity
) {
335 CdmEntityCacheKey id
= new CdmEntityCacheKey(cdmEntity
);
336 Element cachedCdmEntityElement
= getCacheElement(id
);
338 if(cachedCdmEntityElement
== null) {
339 cachedCdmEntityElement
= getCacheElement(cdmEntity
.getUuid());
340 if(cachedCdmEntityElement
!= null) {
341 logger
.info("Cdm Entity with id : " + cdmEntity
.getId() + " already exists in permanent cache. Ignoring put.");
346 getCache().put(new Element(id
, cdmEntity
));
350 private Element
getCacheElement(CdmEntityCacheKey key
) {
351 return getCache().get(key
);
354 public CdmModelFieldPropertyFromClass
getFromCdmlibModelCache(String className
) {
355 Element e
= cdmlibModelCache
.get(className
);
359 return (CdmModelFieldPropertyFromClass
) e
.getObjectValue();
363 public CdmBase
getFromCache(CdmEntityCacheKey id
) {
364 Element e
= getCacheElement(id
);
368 return (CdmBase
) e
.getObjectValue();
372 public CdmBase
getFromCache(Class
<?
extends CdmBase
> clazz
, int id
) {
373 CdmEntityCacheKey cacheId
= generateKey(clazz
,id
);
374 return getFromCache(cacheId
);
377 public CdmBase
getFromCache(CdmBase cdmBase
) {
379 CdmEntityCacheKey cacheId
= generateKey(cdmBase
);
381 CdmBase cachedCdmEntity
= getFromCache(cacheId
);
383 if(cachedCdmEntity
== null) {
384 // ... then try the permanent (uuid-based) cache
385 cachedCdmEntity
= getFromCache(cdmBase
.getUuid());
388 return cachedCdmEntity
;
391 public CdmBase
getFromCache(CdmBase cdmBase
, Class
<?
extends CdmBase
> clazz
) {
393 cdmBase
= CdmBase
.deproxy(cdmBase
, clazz
);
395 CdmEntityCacheKey cacheId
= generateKey(cdmBase
);
397 CdmBase cachedCdmEntity
= getFromCache(cacheId
);
399 if(cachedCdmEntity
== null) {
400 // ... then try the permanent (uuid-based) cache
401 cachedCdmEntity
= getFromCache(cdmBase
.getUuid());
404 return cachedCdmEntity
;
407 public List
<CdmBase
> getAllEntities() {
408 List
<CdmBase
> entities
= new ArrayList
<CdmBase
>();
409 Map
<String
, CdmBase
> elementsMap
= getCache().getAllWithLoader(getCache().getKeys(), null);
410 for (Map
.Entry
<String
, CdmBase
> entry
: elementsMap
.entrySet()) {
411 entities
.add(entry
.getValue());
420 * @see eu.etaxonomy.cdm.model.ICdmCacher#exists(java.util.UUID)
423 public boolean exists(UUID uuid
) {
424 return (getCacheElement(uuid
) != null || super.getCacheElement(uuid
) != null);
427 public boolean exists(CdmEntityCacheKey key
) {
428 return (getCacheElement(key
) != null);
433 * @see eu.etaxonomy.cdm.model.ICdmCacher#existsAndIsNotNull(java.util.UUID)
436 public boolean existsAndIsNotNull(UUID uuid
) {
437 return getFromCache(uuid
) != null;
440 public boolean existsAndIsNotNull(CdmEntityCacheKey id
) {
441 return getFromCache(id
) != null;
445 public static CdmEntityCacheKey
generateKey(Class
<?
extends CdmBase
> clazz
, int id
) {
446 return new CdmEntityCacheKey(clazz
, id
);
450 public static CdmEntityCacheKey
generateKey(CdmBase cdmBase
) {
451 Class
<?
extends CdmBase
> entityClass
= cdmBase
.getClass();
452 int id
= cdmBase
.getId();
453 return new CdmEntityCacheKey(entityClass
, id
);
456 public static boolean isRecursiveEnabled() {
457 return isRecursiveEnabled
;
460 public static void setRecursiveEnabled(boolean ire
) {
461 isRecursiveEnabled
= ire
;