3 * Copyright (C) 2015 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
.Collection
;
14 import java
.util
.HashSet
;
15 import java
.util
.Iterator
;
16 import java
.util
.List
;
20 import javassist
.util
.proxy
.ProxyFactory
;
21 import net
.sf
.ehcache
.Cache
;
22 import net
.sf
.ehcache
.Element
;
24 import org
.apache
.log4j
.Logger
;
25 import org
.hibernate
.collection
.spi
.PersistentCollection
;
26 import org
.hibernate
.proxy
.HibernateProxy
;
27 import org
.hibernate
.proxy
.LazyInitializer
;
28 import org
.springframework
.util
.ReflectionUtils
;
30 import eu
.etaxonomy
.cdm
.model
.ICdmCacher
;
31 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
38 public class CacheLoader
{
39 private static final Logger logger
= Logger
.getLogger(CacheLoader
.class);
41 private static boolean isRecursiveEnabled
= true;
43 private final ICdmCacher cdmCacher
;
45 private final Cache cdmlibModelCache
;
48 public CacheLoader(ICdmCacher cdmCacher
) {
49 this.cdmCacher
= cdmCacher
;
50 this.cdmlibModelCache
= CdmRemoteCacheManager
.getInstance().getCdmModelGetMethodsCache();
54 public CdmModelFieldPropertyFromClass
getFromCdmlibModelCache(String className
) {
55 Element e
= cdmlibModelCache
.get(className
);
59 return (CdmModelFieldPropertyFromClass
) e
.getObjectValue();
63 @SuppressWarnings("unchecked")
64 public <T
extends Object
> T
load(T obj
, boolean recursive
) {
68 if(obj
instanceof CdmBase
) {
69 return (T
) load((CdmBase
)obj
, recursive
);
70 } else if (obj
instanceof Map
) {
71 return (T
) load((Map
<T
,T
>)obj
, recursive
);
72 } else if (obj
instanceof Collection
) {
73 return (T
) load((Collection
<T
>)obj
, recursive
);
79 @SuppressWarnings("unchecked")
80 private <T
extends Object
> T
loadRecursive(T obj
, Set
<CdmBase
> alreadyVisitedEntities
) {
84 if(obj
instanceof CdmBase
) {
85 return (T
) loadRecursive((CdmBase
)obj
, alreadyVisitedEntities
);
86 } else if (obj
instanceof Map
) {
87 return (T
) load((Map
<T
,T
>)obj
, alreadyVisitedEntities
);
88 } else if (obj
instanceof Collection
) {
89 return (T
) load((Collection
<T
>)obj
, alreadyVisitedEntities
);
93 logger
.info("No caching yet for type " + obj
.getClass().getName());
98 public <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, boolean recursive
){
103 if(isRecursiveEnabled
&& recursive
) {
104 logger
.info("---- starting recursive load for cdm entity map");
105 Set
<CdmBase
> alreadyVisitedEntities
= new HashSet
<CdmBase
>();
106 Map
<T
,T
> cachedMap
= load(map
, alreadyVisitedEntities
);
107 alreadyVisitedEntities
.clear();
108 logger
.info("---- ending recursive load for cdm entity map \n");
111 return load(map
, null);
116 private <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, Set
<CdmBase
> alreadyVisitedEntities
){
117 if(map
== null || map
.isEmpty()) {
121 int originalMapSize
= map
.size();
122 Object
[] result
= new Object
[ map
.size() * 2 ];
123 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
125 while ( iter
.hasNext() ) {
126 Map
.Entry
<T
,T
> e
= iter
.next();
127 result
[i
++] = e
.getKey();
128 result
[i
++] = e
.getValue();
131 for(i
=0; i
<result
.length
;i
++) {
132 if(alreadyVisitedEntities
== null) {
133 result
[i
] = load(result
[i
], false);
135 result
[i
] = loadRecursive(result
[i
], alreadyVisitedEntities
);
139 for(i
= 0; i
< originalMapSize
; i
+=2 ) {
148 public <T
extends Object
> Collection
<T
> load(Collection
<T
> collection
, boolean recursive
){
149 if(collection
== null) {
153 Collection
<T
> loadedCollection
;
154 if(isRecursiveEnabled
&& recursive
) {
155 logger
.info("---- starting recursive load for cdm entity collection");
156 Set
<CdmBase
> alreadyVisitedEntities
= new HashSet
<CdmBase
>();
157 Collection
<T
> cachedCollection
= load(collection
, alreadyVisitedEntities
);
158 alreadyVisitedEntities
.clear();
159 logger
.info("---- ending recursive load for cdm entity collection \n");
160 loadedCollection
= cachedCollection
;
162 loadedCollection
= load(collection
, null);
164 return loadedCollection
;
167 @SuppressWarnings("unchecked")
168 private <T
extends Object
> Collection
<T
> load(Collection
<T
> collection
, Set
<CdmBase
> alreadyVisitedEntities
){
169 int length
= collection
.size();
170 Object
[] result
= new Object
[length
];
171 Iterator
<T
> collectionItr
= collection
.iterator();
173 while(collectionItr
.hasNext()) {
174 Object obj
= collectionItr
.next();
175 if(alreadyVisitedEntities
== null) {
176 result
[count
] = load(obj
, false);
178 result
[count
] = loadRecursive(obj
, alreadyVisitedEntities
);
186 for ( int i
= 0; i
< length
; i
++ ) {
187 collection
.add((T
)result
[i
]);
195 * Puts the (Key,Value) pair of ({@link java.util.UUID}, {@link eu.etaxonomy.cdm.model.common.CdmBase}),
196 * in the cache corresponding to the given cache id
202 public CdmBase
load(CdmBase cdmEntity
, boolean recursive
) {
203 if(cdmEntity
== null) {
208 // start by looking up the cdm entity in the cache
209 CdmBase cachedCdmEntity
= cdmCacher
.getFromCache(cdmEntity
);
211 if(cachedCdmEntity
!= null) {
212 // if cdm entity was found in cache then
213 logger
.info(" - object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + " already exists");
214 // .. return if the cached and input objects are identical, else (this is a newly loaded object so) continue
215 if(cachedCdmEntity
== cdmEntity
) {
216 return cachedCdmEntity
;
218 return cachedCdmEntity
;
221 CdmBase loadedCdmBase
;
222 if(isRecursiveEnabled
&& recursive
) {
223 logger
.info("---- starting recursive load for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
224 Set
<CdmBase
> alreadyVisitedEntities
= new HashSet
<CdmBase
>();
225 CdmBase cb
= loadRecursive(cdmEntity
, alreadyVisitedEntities
);
226 alreadyVisitedEntities
.clear();
227 logger
.info("---- ending recursive load for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + "\n");
230 loadedCdmBase
= load(cdmEntity
);
232 return loadedCdmBase
;
237 private CdmBase
load(CdmBase cdmEntity
) {
238 logger
.info("loading object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
240 // start by looking up the cdm entity in the cache
241 CdmBase cachedCdmEntity
= cdmCacher
.getFromCache(cdmEntity
);
243 if(cachedCdmEntity
!= null) {
244 // if cdm entity was found in cache then return ...
245 logger
.info(" - object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + " already exists");
246 return cachedCdmEntity
;
248 // ... else save the entity in the cache
249 cdmCacher
.put(cdmEntity
);
250 logger
.info(" - object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + " put in cache");
255 private CdmBase
loadRecursive(CdmBase cdmEntity
, Set
<CdmBase
> alreadyVisitedEntities
) {
257 CdmBase cachedCdmEntity
= load(cdmEntity
);
260 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
261 // since there could be new or deleted objects in the cdmEntity sub-graph
263 // start by getting the fields from the cdm entity
264 String className
= cdmEntity
.getClass().getName();
265 CdmModelFieldPropertyFromClass cmgmfc
= getFromCdmlibModelCache(className
);
267 alreadyVisitedEntities
.add(cdmEntity
);
268 List
<String
> fields
= cmgmfc
.getFields();
269 for(String field
: fields
) {
270 // retrieve the actual object corresponding to the field.
271 // this object will be either a CdmBase or a Collection / Map
272 // with CdmBase as the generic type
274 CdmBase cdmEntityInSubGraph
= getCdmBaseTypeFieldValue(cdmEntity
, cachedCdmEntity
, field
, alreadyVisitedEntities
);
275 if(cdmEntityInSubGraph
!= null) {
276 //checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph);
277 if(!alreadyVisitedEntities
.contains(cdmEntityInSubGraph
)) {
278 logger
.info("recursive loading object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId());
279 loadRecursive(cdmEntityInSubGraph
, alreadyVisitedEntities
);
281 logger
.info("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + " already visited");
287 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity
.getClass().getName() + " is not found in the cdmlib model cache. " +
288 "The cache may be corrupted or not in sync with the latest model version" );
290 return cachedCdmEntity
;
294 private CdmBase
getCdmBaseTypeFieldValue(CdmBase cdmEntity
,
295 CdmBase cachedCdmEntity
,
297 Set
<CdmBase
> alreadyVisitedEntities
) {
299 // this method attempts to make sure that for any two objects found in
300 // the object graph, if they are equal then they should also be the same,
301 // which is crucial for the merge to work
302 if(cachedCdmEntity
== null) {
303 throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
306 Class
<?
> clazz
= cdmEntity
.getClass();
308 // this call will search in the provided class as well as
309 // the super classes until it finds the field
310 Field field
= ReflectionUtils
.findField(clazz
, fieldName
);
313 throw new CdmClientCacheException("Field '" + fieldName
314 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
316 field
.setAccessible(true);
317 Object o
= field
.get(cdmEntity
);
319 if(o
!= null && o
instanceof HibernateProxy
) {
320 LazyInitializer hli
= ((HibernateProxy
)o
).getHibernateLazyInitializer();
321 if(!hli
.isUninitialized()) {
322 o
= ((HibernateProxy
) o
).getHibernateLazyInitializer().getImplementation();
323 field
.set(cdmEntity
, o
);
327 if(o
!= null && o
instanceof PersistentCollection
) {
328 PersistentCollection pc
= ((PersistentCollection
)o
);
329 if(pc
.wasInitialized()) {
330 o
= ProxyUtils
.getObject(pc
);
331 field
.set(cdmEntity
, o
);
336 CdmBase cdmEntityInSubGraph
= null;
338 && !ProxyFactory
.isProxyClass(o
.getClass())
339 && !(o
instanceof PersistentCollection
) ) {
341 if(CdmBase
.class.isAssignableFrom(o
.getClass())) {
342 logger
.info("found initialised cdm entity '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
343 cdmEntityInSubGraph
= (CdmBase
)o
;
346 CdmBase cachedCdmEntityInSubGraph
= cdmCacher
.getFromCache(cdmEntityInSubGraph
);
347 // making sure that the field in cached cdm entity is always
348 // up-to-date by setting to the value of the cdm entity being loaded
349 // the only execption to this is found below
350 field
.set(cachedCdmEntity
, o
);
353 // the only exception to updating the field to the latest value
354 // is the case where the field has been already initialised, cached and
355 // is not the same as the one in the cache, in which case we set the value
356 // of the field to the one found in the cache
357 if(cachedCdmEntityInSubGraph
!= null) {
358 if(cachedCdmEntityInSubGraph
!= cdmEntityInSubGraph
) {
359 logger
.info("setting cached + real value to '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
360 field
.set(cachedCdmEntity
, cachedCdmEntityInSubGraph
);
361 field
.set(cdmEntity
, cachedCdmEntityInSubGraph
);
363 cdmEntityInSubGraph
= null;
366 } else if(o
instanceof Map
) {
367 loadRecursive((Map
)o
, alreadyVisitedEntities
);
368 } else if(o
instanceof Collection
) {
369 loadRecursive((Collection
)o
, alreadyVisitedEntities
);
372 // we return the original cdm entity in the sub graph because we
373 // want to continue to recurse on the input cdm entity graph
374 // and not the one in the cache
375 return cdmEntityInSubGraph
;
376 } catch (SecurityException e
) {
377 throw new CdmClientCacheException(e
);
378 } catch (IllegalArgumentException e
) {
379 throw new CdmClientCacheException(e
);
380 } catch (IllegalAccessException e
) {
381 throw new CdmClientCacheException(e
);
385 private boolean checkForIdenticalCdmEntity(Set
<CdmBase
> cbSet
, CdmBase cbToCompare
) {
386 if(cbToCompare
!= null) {
387 for(CdmBase cb
: cbSet
) {
389 if(cb
== cbToCompare
) {
392 if(cb
.equals(cbToCompare
)) {
393 logger
.info("equal but non identical object found of type " + cbToCompare
.getUserFriendlyTypeName() + " with id " + cbToCompare
.getId());
401 public static boolean isRecursiveEnabled() {
402 return isRecursiveEnabled
;
405 public static void setRecursiveEnabled(boolean ire
) {
406 isRecursiveEnabled
= ire
;