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
.ArrayList
;
14 import java
.util
.Collection
;
15 import java
.util
.Iterator
;
16 import java
.util
.List
;
19 import net
.sf
.ehcache
.Cache
;
20 import net
.sf
.ehcache
.Element
;
22 import org
.apache
.log4j
.Logger
;
23 import org
.springframework
.util
.ReflectionUtils
;
25 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
26 import eu
.etaxonomy
.cdm
.model
.ICdmCacher
;
27 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
28 import eu
.etaxonomy
.cdm
.persistence
.dto
.MergeResult
;
35 public class CacheLoader
{
36 private static final Logger logger
= Logger
.getLogger(CacheLoader
.class);
38 private static boolean isRecursiveEnabled
= true;
40 protected final ICdmCacher cdmCacher
;
42 private final Cache cdmlibModelCache
;
46 public CacheLoader(ICdmCacher cdmCacher
) {
47 this.cdmCacher
= cdmCacher
;
48 this.cdmlibModelCache
= CdmRemoteCacheManager
.getInstance().getCdmModelGetMethodsCache();
53 public CdmModelFieldPropertyFromClass
getFromCdmlibModelCache(String className
) {
54 Element e
= cdmlibModelCache
.get(className
);
58 return (CdmModelFieldPropertyFromClass
) e
.getObjectValue();
62 @SuppressWarnings("unchecked")
63 public <T
extends Object
> T
load(T obj
, boolean recursive
, boolean update
) {
67 if(obj
instanceof CdmBase
) {
68 return (T
) load((CdmBase
)obj
, recursive
, update
);
69 } else if (obj
instanceof Map
) {
70 return (T
) load((Map
<T
,T
>)obj
, recursive
, update
);
71 } else if (obj
instanceof Collection
) {
72 return (T
) load((Collection
<T
>)obj
, recursive
, update
);
73 } else if(obj
instanceof Pager
) {
74 load(((Pager
)obj
).getRecords(), recursive
, update
);
76 } else if(obj
instanceof MergeResult
) {
77 return (T
) load((MergeResult
<CdmBase
>)obj
, recursive
, update
);
83 @SuppressWarnings("unchecked")
84 private <T
extends Object
> T
loadRecursive(T obj
, List
<Object
> alreadyVisitedEntities
, boolean update
) {
88 if(obj
instanceof CdmBase
) {
89 return (T
) loadRecursive((CdmBase
)obj
, alreadyVisitedEntities
, update
);
90 } else if (obj
instanceof Map
) {
91 return (T
) load((Map
<T
,T
>)obj
, alreadyVisitedEntities
, update
);
92 } else if (obj
instanceof Collection
) {
93 return (T
) load((Collection
<T
>)obj
, alreadyVisitedEntities
, update
);
94 } else if (obj
instanceof MergeResult
) {
95 return (T
) loadRecursive((MergeResult
)obj
, alreadyVisitedEntities
, update
);
99 logger
.info("No caching yet for type " + obj
.getClass().getName());
104 public <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, boolean recursive
, boolean update
){
107 if(isRecursiveEnabled
&& recursive
) {
108 logger
.info("---- starting recursive load for cdm entity map");
109 List
<Object
> alreadyVisitedEntities
= new ArrayList
<Object
>();
110 Map
<T
,T
> cachedMap
= load(map
, alreadyVisitedEntities
, update
);
111 alreadyVisitedEntities
.clear();
112 logger
.info("---- ending recursive load for cdm entity map \n");
115 return load(map
, null, update
);
120 private <T
extends Object
> Map
<T
,T
> load(Map
<T
,T
> map
, List
<Object
> alreadyVisitedEntities
, boolean update
){
121 //map = (Map<T,T>)deproxy(map);
123 if(map
== null || map
.isEmpty()) {
127 int originalMapSize
= map
.size();
128 Object
[] result
= new Object
[ map
.size() * 2 ];
129 Iterator
<Map
.Entry
<T
,T
>> iter
= map
.entrySet().iterator();
131 // to avoid ConcurrentModificationException
132 alreadyVisitedEntities
.add(map
);
133 while ( iter
.hasNext() ) {
134 Map
.Entry
<T
,T
> e
= iter
.next();
135 result
[i
++] = e
.getKey();
136 result
[i
++] = e
.getValue();
139 for(i
=0; i
<result
.length
;i
++) {
140 if(alreadyVisitedEntities
== null) {
141 result
[i
] = load(result
[i
], false, update
);
143 result
[i
] = loadRecursive(result
[i
], alreadyVisitedEntities
, update
);
147 for(i
= 0; i
< originalMapSize
; i
+=2 ) {
156 public <T
extends Object
> Collection
<T
> load(Collection
<T
> collection
, boolean recursive
, boolean update
){
158 Collection
<T
> loadedCollection
;
159 if(isRecursiveEnabled
&& recursive
) {
160 logger
.info("---- starting recursive load for cdm entity collection");
161 List
<Object
> alreadyVisitedEntities
= new ArrayList
<Object
>();
162 Collection
<T
> cachedCollection
= load(collection
, alreadyVisitedEntities
, update
);
163 alreadyVisitedEntities
.clear();
164 logger
.info("---- ending recursive load for cdm entity collection \n");
165 loadedCollection
= cachedCollection
;
167 loadedCollection
= load(collection
, null, update
);
169 return loadedCollection
;
172 @SuppressWarnings("unchecked")
173 private <T
extends Object
> Collection
<T
> load(Collection
<T
> collection
, List
<Object
> alreadyVisitedEntities
, boolean update
) {
177 if(collection
== null || collection
.isEmpty()) {
180 int length
= collection
.size();
181 Object
[] result
= new Object
[length
];
182 Iterator
<T
> collectionItr
= collection
.iterator();
184 // to avoid ConcurrentModificationException
185 alreadyVisitedEntities
.add(collection
);
186 while(collectionItr
.hasNext()) {
187 Object obj
= collectionItr
.next();
188 if(alreadyVisitedEntities
== null) {
189 result
[count
] = load(obj
, false, update
);
191 result
[count
] = loadRecursive(obj
, alreadyVisitedEntities
, update
);
199 for ( int i
= 0; i
< length
; i
++ ) {
200 collection
.add((T
)result
[i
]);
207 public MergeResult
<CdmBase
> load(MergeResult
<CdmBase
> mergeResult
, boolean recursive
, boolean update
) {
208 CdmBase cdmBase
= load(mergeResult
.getMergedEntity(), recursive
, update
);
209 load(mergeResult
.getNewEntities(), recursive
, update
);
210 return new MergeResult(cdmBase
, mergeResult
.getNewEntities());
213 public MergeResult
<CdmBase
> loadRecursive(MergeResult
<CdmBase
> mergeResult
,List
<Object
> alreadyVisitedEntities
, boolean update
) {
214 CdmBase cdmBase
= loadRecursive(mergeResult
.getMergedEntity(), alreadyVisitedEntities
, update
);
215 loadRecursive(mergeResult
.getNewEntities(), alreadyVisitedEntities
, update
);
216 return new MergeResult(cdmBase
, mergeResult
.getNewEntities());
220 * Puts the (Key,Value) pair of ({@link java.util.UUID}, {@link eu.etaxonomy.cdm.model.common.CdmBase}),
221 * in the cache corresponding to the given cache id
227 public CdmBase
load(CdmBase cdmEntity
, boolean recursive
, boolean update
) {
228 if(cdmEntity
== null) {
232 // start by looking up the cdm entity in the cache
233 CdmBase cachedCdmEntity
= cdmCacher
.getFromCache(cdmEntity
);
235 if(cachedCdmEntity
!= null) {
236 // if cdm entity was found in cache then
237 logger
.info(" - object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + " already exists");
238 // .. return if the cached and input objects are identical, else (this is a newly loaded object so) continue
239 if(cachedCdmEntity
== cdmEntity
) {
240 return cachedCdmEntity
;
244 CdmBase loadedCdmBase
;
245 if(isRecursiveEnabled
&& recursive
) {
246 logger
.info("---- starting recursive load for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
247 List
<Object
> alreadyVisitedEntities
= new ArrayList
<Object
>();
248 CdmBase cb
= loadRecursive(cdmEntity
, alreadyVisitedEntities
, update
);
249 alreadyVisitedEntities
.clear();
250 logger
.info("---- ending recursive load for cdm entity " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId() + "\n");
253 loadedCdmBase
= load(cdmEntity
);
255 return loadedCdmBase
;
260 protected CdmBase
load(CdmBase cdmEntity
) {
261 logger
.info("loading object of type " + cdmEntity
.getClass().getName() + " with id " + cdmEntity
.getId());
262 cdmCacher
.put((CdmBase
)ProxyUtils
.deproxy(cdmEntity
));
263 return cdmCacher
.getFromCache(cdmEntity
);
267 private CdmBase
loadRecursive(CdmBase cdmEntity
, List
<Object
> alreadyVisitedEntities
, boolean update
) {
269 CdmBase cachedCdmEntity
= load(cdmEntity
);
272 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
273 // since there could be new or deleted objects in the cdmEntity sub-graph
275 // start by getting the fields from the cdm entity
276 String className
= cdmEntity
.getClass().getName();
277 CdmModelFieldPropertyFromClass cmgmfc
= getFromCdmlibModelCache(className
);
279 alreadyVisitedEntities
.add(cdmEntity
);
280 List
<String
> fields
= cmgmfc
.getFields();
281 for(String field
: fields
) {
282 // retrieve the actual object corresponding to the field.
283 // this object will be either a CdmBase or a Collection / Map
284 // with CdmBase as the generic type
286 CdmBase cdmEntityInSubGraph
= getCdmBaseTypeFieldValue(cdmEntity
, cachedCdmEntity
, field
, alreadyVisitedEntities
, update
);
287 if(cdmEntityInSubGraph
!= null) {
288 //checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph);
289 if(!checkForIdenticalCdmEntity(alreadyVisitedEntities
, cdmEntityInSubGraph
)) {
290 logger
.info("recursive loading object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId());
291 loadRecursive(cdmEntityInSubGraph
, alreadyVisitedEntities
, update
);
293 logger
.info("object of type " + cdmEntityInSubGraph
.getClass().getName() + " with id " + cdmEntityInSubGraph
.getId() + " already visited");
298 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity
.getClass().getName() + " is not found in the cdmlib model cache. " +
299 "The cache may be corrupted or not in sync with the latest model version" );
302 return cachedCdmEntity
;
306 private CdmBase
getCdmBaseTypeFieldValue(CdmBase cdmEntity
,
307 CdmBase cachedCdmEntity
,
309 List
<Object
> alreadyVisitedEntities
,
312 // this method attempts to make sure that for any two objects found in
313 // the object graph, if they are equal then they should also be the same,
314 // which is crucial for the merge to work
315 if(cachedCdmEntity
== null) {
316 throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
319 Class
<?
> clazz
= cdmEntity
.getClass();
321 // this call will search in the provided class as well as
322 // the super classes until it finds the field
323 Field field
= ReflectionUtils
.findField(clazz
, fieldName
);
326 throw new CdmClientCacheException("Field '" + fieldName
327 + "' not found when searching in class '" + clazz
.getName() + "' and its supercalsses");
329 field
.setAccessible(true);
330 Object o
= field
.get(cdmEntity
);
331 // resetting the value in cdm entity to the deproxied object
332 o
= ProxyUtils
.deproxy(o
);
333 field
.set(cdmEntity
, o
);
334 Object cachedo
= field
.get(cachedCdmEntity
);
335 CdmBase cdmEntityInSubGraph
= null;
337 if(update
|| ProxyUtils
.isProxy(cachedo
)) {
338 // if we are in update mode we have to make the field of the cached entity
339 // up-to-date by setting it to the value of the cdm entity being loaded
341 // if the cdm entity is a proxy then we always update to make sure that
342 // newly created entities are always up-to-date
344 // NOTE : the field is overridden in the case of the exception
346 field
.set(cachedCdmEntity
, o
);
350 if(o
!= null && !ProxyUtils
.isProxy(o
)) {
351 if(CdmBase
.class.isAssignableFrom(o
.getClass())) {
352 logger
.info("found initialised cdm entity '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
354 cdmEntityInSubGraph
= (CdmBase
)o
;
355 CdmBase cachedCdmEntityInSubGraph
= cdmCacher
.getFromCache(cdmEntityInSubGraph
);
357 if(cachedCdmEntityInSubGraph
!= null) {
358 if(cachedCdmEntityInSubGraph
!= cdmEntityInSubGraph
) {
359 // exception : is the case where
360 // the field has been already initialised, cached and
361 // is not the same as the one in the cache, in which case we set the value
362 // of the field to the one found in the cache
363 logger
.info("setting cached + real value to '" + fieldName
+ "' in object of type " + clazz
.getName() + " with id " + cdmEntity
.getId());
364 field
.set(cachedCdmEntity
, cachedCdmEntityInSubGraph
);
365 field
.set(cdmEntity
, cachedCdmEntityInSubGraph
);
367 // since the field value object in cdmEntity
368 // is the same as the field value object in cachedCdmEntity
369 // we are sure that the its subgraph is also correctly loaded,
370 // so we can exit the recursion
374 } else if(o
instanceof Map
&& !checkForIdenticalCdmEntity(alreadyVisitedEntities
, o
)) {
375 loadRecursive((Map
)o
, alreadyVisitedEntities
, update
);
376 } else if(o
instanceof Collection
&& !checkForIdenticalCdmEntity(alreadyVisitedEntities
, o
)) {
377 loadRecursive((Collection
)o
, alreadyVisitedEntities
, update
);
380 // we return the original cdm entity in the sub graph because we
381 // want to continue to recurse on the input cdm entity graph
382 // and not the one in the cache
383 return cdmEntityInSubGraph
;
384 } catch (SecurityException e
) {
385 throw new CdmClientCacheException(e
);
386 } catch (IllegalArgumentException e
) {
387 throw new CdmClientCacheException(e
);
388 } catch (IllegalAccessException e
) {
389 throw new CdmClientCacheException(e
);
393 private boolean checkForIdenticalCdmEntity(List
<Object
> objList
, Object objToCompare
) {
394 if(objToCompare
!= null) {
395 for(Object obj
: objList
) {
396 if(obj
== objToCompare
) {
405 public static boolean isRecursiveEnabled() {
406 return isRecursiveEnabled
;
409 public static void setRecursiveEnabled(boolean ire
) {
410 isRecursiveEnabled
= ire
;