2 * Copyright (C) 2015 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
9 package eu
.etaxonomy
.cdm
.cache
;
11 import java
.lang
.reflect
.Field
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Collection
;
14 import java
.util
.Iterator
;
15 import java
.util
.List
;
18 import org
.apache
.log4j
.Logger
;
19 import org
.springframework
.util
.ReflectionUtils
;
21 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
22 import eu
.etaxonomy
.cdm
.model
.ICdmCacher
;
23 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
24 import eu
.etaxonomy
.cdm
.persistence
.dto
.MergeResult
;
25 import net
.sf
.ehcache
.Cache
;
26 import net
.sf
.ehcache
.Element
;
32 public class CacheLoader
{
34 private static final Logger logger
= Logger
.getLogger(CacheLoader
.class);
36 private static boolean isRecursiveEnabled
= true;
38 protected final ICdmCacher cdmCacher
;
40 private final Cache cdmlibModelCache
;
42 public CacheLoader(ICdmCacher cdmCacher
) {
43 this.cdmCacher
= cdmCacher
;
44 this.cdmlibModelCache
= CdmRemoteCacheManager
.getInstance().getCdmModelGetMethodsCache();
47 public CdmModelFieldPropertyFromClass
getFromCdmlibModelCache(String className
) {
48 Element e
= cdmlibModelCache
.get(className
);
52 return (CdmModelFieldPropertyFromClass
) e
.getObjectValue();
56 @SuppressWarnings("unchecked")
57 public <T
extends Object
> T
load(T obj
, boolean recursive
, boolean update
) {
61 if(obj
instanceof CdmBase
) {
62 return (T
) load((CdmBase
)obj
, recursive
, update
);
63 } else if (obj
instanceof Map
) {
64 return (T
) load((Map
<T
,T
>)obj
, recursive
, update
);
65 } else if (obj
instanceof Collection
) {
66 return (T
) load((Collection
<T
>)obj
, recursive
, update
);
67 } else if(obj
instanceof Pager
) {
68 load(((Pager
<?
>)obj
).getRecords(), recursive
, update
);
70 } else if(obj
instanceof MergeResult
) {
71 return (T
) load((MergeResult
<CdmBase
>)obj
, recursive
, update
);
77 @SuppressWarnings("unchecked")
78 private <T
extends Object
> T
loadRecursive(T obj
, List
<Object
> alreadyVisitedEntities
, boolean update
) {
82 if(obj
instanceof CdmBase
) {
83 return (T
) loadRecursive((CdmBase
)obj
, alreadyVisitedEntities
, update
);
84 } else if (obj
instanceof Map
) {
85 return (T
) load((Map
<T
,T
>)obj
, alreadyVisitedEntities
, update
);
86 } else if (obj
instanceof Collection
) {
87 return (T
) load((Collection
<T
>)obj
, alreadyVisitedEntities
, update
);
88 } else if (obj
instanceof MergeResult
) {
89 return (T
) loadRecursive((MergeResult
<CdmBase
>)obj
, alreadyVisitedEntities
, update
);
92 if (logger
.isInfoEnabled()){logger
.info("No caching yet for type " + obj
.getClass().getName());}
97 public <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, boolean recursive
, boolean update
){
99 if(isRecursiveEnabled
&& recursive
) {
100 if (logger
.isDebugEnabled()){logger
.debug("---- starting recursive load for cdm entity map");}
101 List
<Object
> alreadyVisitedEntities
= new ArrayList
<>();
102 Map
<T
,T
> cachedMap
= load(map
, alreadyVisitedEntities
, update
);
103 alreadyVisitedEntities
.clear();
104 if (logger
.isDebugEnabled()){logger
.debug("---- ending recursive load for cdm entity map \n");}
107 return load(map
, null, update
);
111 private <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, List
<Object
> alreadyVisitedEntities
, boolean update
){
113 if(map
== null || map
.isEmpty()) {
117 Object
[] result
= new Object
[ map
.size() * 2 ];
118 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
120 // to avoid ConcurrentModificationException
121 if (alreadyVisitedEntities
!= null){
122 alreadyVisitedEntities
.add(map
);
124 while ( iter
.hasNext() ) {
125 Map
.Entry
<T
,T
> e
= iter
.next();
126 result
[i
++] = e
.getKey();
127 result
[i
++] = e
.getValue();
130 for(i
=0; i
<result
.length
;i
++) {
131 if(alreadyVisitedEntities
== null) {
132 result
[i
] = load(result
[i
], false, update
);
134 result
[i
] = loadRecursive(result
[i
], alreadyVisitedEntities
, update
);
138 for(i
= 0; i
< result
.length
; i
+=2 ) {
147 public <T
extends Object
> Collection
<T
> load(Collection
<T
> collection
, boolean recursive
, boolean update
){
149 Collection
<T
> loadedCollection
;
150 if(isRecursiveEnabled
&& recursive
) {
151 if (logger
.isDebugEnabled()){logger
.debug("---- starting recursive load for cdm entity collection");}
152 List
<Object
> alreadyVisitedEntities
= new ArrayList
<>();
153 Collection
<T
> cachedCollection
= load(collection
, alreadyVisitedEntities
, update
);
154 alreadyVisitedEntities
.clear();
155 if (logger
.isDebugEnabled()){logger
.debug("---- ending recursive load for cdm entity collection \n");}
156 loadedCollection
= cachedCollection
;
158 loadedCollection
= load(collection
, null, update
);
160 return loadedCollection
;
163 @SuppressWarnings("unchecked")
164 private <T
extends Object
> Collection
<T
> load(Collection
<T
> collection
, List
<Object
> alreadyVisitedEntities
, boolean update
) {
166 if(collection
== null || collection
.isEmpty()) {
169 int length
= collection
.size();
170 Object
[] result
= new Object
[length
];
171 Iterator
<T
> collectionItr
= collection
.iterator();
173 // to avoid ConcurrentModificationException
174 if (alreadyVisitedEntities
!= null){
175 alreadyVisitedEntities
.add(collection
);
177 while(collectionItr
.hasNext()) {
178 Object obj
= collectionItr
.next();
179 if(alreadyVisitedEntities
== null) {
180 result
[count
] = load(obj
, false, update
);
182 result
[count
] = loadRecursive(obj
, alreadyVisitedEntities
, update
);
190 for ( int i
= 0; i
< length
; i
++ ) {
191 collection
.add((T
)result
[i
]);
197 public MergeResult
<CdmBase
> load(MergeResult
<CdmBase
> mergeResult
, boolean recursive
, boolean update
) {
198 CdmBase cdmBase
= load(mergeResult
.getMergedEntity(), recursive
, update
);
199 load(mergeResult
.getNewEntities(), recursive
, update
);
200 return new MergeResult
<>(cdmBase
, mergeResult
.getNewEntities());
203 public MergeResult
<CdmBase
> loadRecursive(MergeResult
<CdmBase
> mergeResult
, List
<Object
> alreadyVisitedEntities
, boolean update
) {
204 CdmBase cdmBase
= loadRecursive(mergeResult
.getMergedEntity(), alreadyVisitedEntities
, update
);
205 loadRecursive(mergeResult
.getNewEntities(), alreadyVisitedEntities
, update
);
206 return new MergeResult
<>(cdmBase
, mergeResult
.getNewEntities());
210 * Loads the {@link eu.etaxonomy.cdm.model.common.CdmBase cdmEntity}) in the
213 * <b>WARNING: Recursive updating of the cached entity will not take place
214 * in case there is a cached entity which is the same/identical object as
215 * <code>cdmEntity</code>.</b>
217 * For in depth details on the mechanism see
218 * {@link #loadRecursive(CdmBase, List, boolean)} and
219 * {@link #getCdmBaseTypeFieldValue(CdmBase, CdmBase, String, List, boolean)}
222 * the entity to be put into the cache
224 * if <code>true</code>, the cache loader will load the whole
225 * entity graph recursively into the cache
227 * all fields of the cached entity will be overwritten by setting
228 * them to the value of the cdm entity being loaded
230 public <T
extends CdmBase
> T
load(T cdmEntity
, boolean recursive
, boolean update
) {
231 if(cdmEntity
== null) {
235 // start by looking up the cdm entity in the cache
236 T cachedCdmEntity
= cdmCacher
.getFromCache(cdmEntity
);
238 if(cachedCdmEntity
!= null) {
239 // if cdm entity was found in cache then
240 logger
.debug(" - object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + " already exists");
241 // .. return if the cached and input objects are identical, else (this is a newly loaded object so) continue
242 if(cachedCdmEntity
== cdmEntity
) {
243 return cachedCdmEntity
;
248 if(isRecursiveEnabled
&& recursive
) {
249 logger
.debug("---- starting recursive load for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
250 List
<Object
> alreadyVisitedEntities
= new ArrayList
<>();
251 T cb
= loadRecursive(cdmEntity
, alreadyVisitedEntities
, update
);
252 alreadyVisitedEntities
.clear();
253 logger
.debug("---- ending recursive load for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + "\n");
256 loadedCdmBase
= put(cdmEntity
);
258 return loadedCdmBase
;
262 protected <T
extends CdmBase
> T
put(T cdmEntity
) {
263 logger
.debug("loading object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
264 cdmCacher
.putToCache((CdmBase
)ProxyUtils
.deproxy(cdmEntity
));
265 return cdmCacher
.getFromCache(cdmEntity
);
269 * Load the <code>cdmEntity</code> graph recursively into the cache and
270 * updates entities which are already in the cache depending on the value of
271 * <code>update</code>, for more in depth details on this mechanism see
272 * {@link #getCdmBaseTypeFieldValue(CdmBase, CdmBase, String, List, boolean)}.
275 * the entity to be loaded into the cache
276 * @param alreadyVisitedEntities
277 * protocol list of entities already visited during loading an
278 * entity graph recursively into the cache.
281 * all fields of the cached entity will be overwritten by setting
282 * them to the value of the cdm entity being loaded
284 * The cached object which is identical with the input entity in case
285 * the object did not yet exist in the cache
287 private <T
extends CdmBase
> T
loadRecursive(T cdmEntity
, List
<Object
> alreadyVisitedEntities
, boolean update
) {
289 T cachedCdmEntity
= put(cdmEntity
);
291 // we want to recurse through the cdmEntity (and not the cachedCdmEntity)
292 // since there could be new or deleted objects in the cdmEntity sub-graph
294 // start by getting the fields from the cdm entity
295 //TODO improve generics for deproxyOrNull, probably need to split the method
296 @SuppressWarnings("unchecked")
297 T deproxiedEntity
= (T
)ProxyUtils
.deproxyOrNull(cdmEntity
);
298 if(deproxiedEntity
!= null){
299 String className
= deproxiedEntity
.getClass().getName();
300 CdmModelFieldPropertyFromClass cmfpfc
= getFromCdmlibModelCache(className
);
302 alreadyVisitedEntities
.add(cdmEntity
);
303 List
<String
> fields
= cmfpfc
.getFields();
304 for(String field
: fields
) {
305 // retrieve the actual object corresponding to the field.
306 // this object will be either a CdmBase or a Collection / Map
307 // with CdmBase as the generic type
309 CdmBase cdmEntityInSubGraph
= getCdmBaseTypeFieldValue(deproxiedEntity
, cachedCdmEntity
, field
, alreadyVisitedEntities
, update
);
310 if(cdmEntityInSubGraph
!= null) {
311 //checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph);
312 if(!checkForIdenticalCdmEntity(alreadyVisitedEntities
, cdmEntityInSubGraph
)) {
313 logger
.debug("recursive loading object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId());
314 loadRecursive(cdmEntityInSubGraph
, alreadyVisitedEntities
, update
);
316 logger
.debug("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + " already visited");
321 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity
.getClass().getName() + " is not found in the cdmlib model cache. " +
322 "The cache may be corrupted or not in sync with the latest model version" );
324 } else { //deproxiedEntity == null
325 logger
.debug("ignoring uninitlialized proxy " + cdmEntity
.getClass() + "#" + cdmEntity
.getId());
328 return cachedCdmEntity
;
332 * All fields of the <code>cdmEntity</code> containing proxy objects will be
333 * set to the un-proxied field value. If <code>update</code> is enabled the
334 * value of the cached entity will be overwritten by the value of the
335 * <code>cdmEntity</code>. In case the cached field value contains a proxy
336 * object the value will always be overwritten (Q: This might only occur in
337 * case of uninitialized proxies, since initialized proxies are expected to
338 * be replaced by the target entity.)
341 * the entity to be loaded into the cache
342 * @param cachedCdmEntity
343 * the entity which resides in the cache
345 * the field name to operate on
346 * @param alreadyVisitedEntities
347 * protocol list of entities already visited during loading an
348 * entity graph recursively into the cache.
350 * all fields of the cached entity will be overwritten by setting
351 * them to the value of the cdm entity being loaded
354 private CdmBase
getCdmBaseTypeFieldValue(CdmBase cdmEntity
,
355 CdmBase cachedCdmEntity
,
357 List
<Object
> alreadyVisitedEntities
,
360 // this method attempts to make sure that for any two objects found in
361 // the object graph, if they are equal then they should also be the same,
362 // which is crucial for the merge to work
363 if(cachedCdmEntity
== null) {
364 throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
367 Class
<?
> clazz
= cdmEntity
.getClass();
369 // this call will search in the provided class as well as
370 // the super classes until it finds the field
371 Field field
= ReflectionUtils
.findField(clazz
, fieldName
);
374 throw new CdmClientCacheException("Field '" + fieldName
375 + "' not found when searching in class '" + clazz
.getName() + "' and its superclasses");
377 field
.setAccessible(true);
378 Object obj
= field
.get(cdmEntity
);
379 // resetting the value in cdm entity to the deproxied object
380 obj
= ProxyUtils
.deproxy(obj
);
381 field
.set(cdmEntity
, obj
);
382 Object cachedObj
= field
.get(cachedCdmEntity
);
383 CdmBase cdmEntityInSubGraph
= null;
385 if(!ProxyUtils
.isUninitializedProxy(obj
) && (update
|| ProxyUtils
.isUninitializedProxy(cachedObj
))) {
386 // if we are in update mode we have to make the field of the cached entity
387 // up-to-date by setting it to the value of the cdm entity being loaded
389 // if the cdm entity is a proxy then we always update to make sure that
390 // newly created entities are always up-to-date
392 // NOTE : the field is overridden in the case of the exception
394 field
.set(cachedCdmEntity
, obj
);
397 if(obj
!= null && !ProxyUtils
.isUninitializedProxy(obj
)) {
398 if(CdmBase
.class.isAssignableFrom(obj
.getClass())) {
399 logger
.debug("found initialised cdm entity '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
401 cdmEntityInSubGraph
= (CdmBase
)obj
;
402 CdmBase cachedCdmEntityInSubGraph
= cdmCacher
.getFromCache(cdmEntityInSubGraph
);
404 if(cachedCdmEntityInSubGraph
!= null) {
405 if(cachedCdmEntityInSubGraph
!= cdmEntityInSubGraph
) {
406 // exception : is the case where
407 // the field has been already initialized, cached and
408 // is not the same as the one in the cache, in which case we set the value
409 // of the field to the one found in the cache
410 logger
.debug("setting cached + real value to '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
411 field
.set(cachedCdmEntity
, cachedCdmEntityInSubGraph
);
412 field
.set(cdmEntity
, cachedCdmEntityInSubGraph
);
414 // since the field value object in cdmEntity
415 // is the same as the field value object in cachedCdmEntity
416 // we are sure that the subgraph is also correctly loaded,
417 // so we can exit the recursion
421 } else if(obj
instanceof Map
&& !checkForIdenticalCdmEntity(alreadyVisitedEntities
, obj
)) {
422 loadRecursive((Map
<?
,?
>)obj
, alreadyVisitedEntities
, update
);
423 } else if(obj
instanceof Collection
&& !checkForIdenticalCdmEntity(alreadyVisitedEntities
, obj
)) {
424 loadRecursive((Collection
<?
>)obj
, alreadyVisitedEntities
, update
);
427 // we return the original cdm entity in the sub graph because we
428 // want to continue to recurse on the input cdm entity graph
429 // and not the one in the cache
430 return cdmEntityInSubGraph
;
431 } catch (SecurityException
| IllegalArgumentException
| IllegalAccessException e
) {
432 throw new CdmClientCacheException(e
);
436 private boolean checkForIdenticalCdmEntity(List
<Object
> objList
, Object objToCompare
) {
437 if(objToCompare
!= null) {
438 for(Object obj
: objList
) {
439 if(obj
== objToCompare
) {
447 public static boolean isRecursiveEnabled() {
448 return isRecursiveEnabled
;
451 public static void setRecursiveEnabled(boolean recursiveEnabled
) {
452 isRecursiveEnabled
= recursiveEnabled
;