fix failing test
[taxeditor.git] / eu.etaxonomy.taxeditor.cdmlib / src / main / java / eu / etaxonomy / taxeditor / remoting / cache / CacheLoader.java
1 // $Id$
2 /**
3 * Copyright (C) 2015 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
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.
9 */
10 package eu.etaxonomy.taxeditor.remoting.cache;
11
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;
17 import java.util.Map;
18
19 import net.sf.ehcache.Cache;
20 import net.sf.ehcache.Element;
21
22 import org.apache.log4j.Logger;
23 import org.springframework.util.ReflectionUtils;
24
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;
29
30 /**
31 * @author cmathew
32 * @date 19 Feb 2015
33 *
34 */
35 public class CacheLoader {
36 private static final Logger logger = Logger.getLogger(CacheLoader.class);
37
38 private static boolean isRecursiveEnabled = true;
39
40 protected final ICdmCacher cdmCacher;
41
42 private final Cache cdmlibModelCache;
43
44
45
46 public CacheLoader(ICdmCacher cdmCacher) {
47 this.cdmCacher = cdmCacher;
48 this.cdmlibModelCache = CdmRemoteCacheManager.getInstance().getCdmModelGetMethodsCache();
49
50 }
51
52
53 public CdmModelFieldPropertyFromClass getFromCdmlibModelCache(String className) {
54 Element e = cdmlibModelCache.get(className);
55 if (e == null) {
56 return null;
57 } else {
58 return (CdmModelFieldPropertyFromClass) e.getObjectValue();
59 }
60 }
61
62 @SuppressWarnings("unchecked")
63 public <T extends Object> T load(T obj, boolean recursive, boolean update) {
64 if(obj == null) {
65 return null;
66 }
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);
75 return obj;
76 } else if(obj instanceof MergeResult) {
77 return (T) load((MergeResult<CdmBase>)obj, recursive, update);
78 }
79
80 return obj;
81 }
82
83 @SuppressWarnings("unchecked")
84 private <T extends Object> T loadRecursive(T obj, List<Object> alreadyVisitedEntities, boolean update) {
85 if(obj == null) {
86 return null;
87 }
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);
96 }
97
98
99 logger.info("No caching yet for type " + obj.getClass().getName());
100
101 return obj;
102 }
103
104 public <T extends Object> Map<T,T> load(Map<T,T> map, boolean recursive, boolean update){
105
106
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");
113 return cachedMap;
114 } else {
115 return load(map, null, update);
116 }
117 }
118
119
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);
122
123 if(map == null || map.isEmpty()) {
124 return map;
125 }
126
127 int originalMapSize = map.size();
128 Object[] result = new Object[ map.size() * 2 ];
129 Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
130 int i=0;
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();
137 }
138
139 for(i=0; i<result.length;i++) {
140 if(alreadyVisitedEntities == null) {
141 result[i] = load(result[i], false, update);
142 } else {
143 result[i] = loadRecursive(result[i], alreadyVisitedEntities, update);
144 }
145 }
146 map.clear();
147 for(i = 0; i < originalMapSize; i+=2 ) {
148 map.put(
149 (T)result[i],
150 (T)result[i+1]
151 );
152 }
153 return map;
154 }
155
156 public <T extends Object> Collection<T> load(Collection<T> collection, boolean recursive, boolean update){
157
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;
166 } else {
167 loadedCollection = load(collection, null, update);
168 }
169 return loadedCollection;
170 }
171
172 @SuppressWarnings("unchecked")
173 private <T extends Object> Collection<T> load(Collection<T> collection, List<Object> alreadyVisitedEntities, boolean update) {
174
175
176
177 if(collection == null || collection.isEmpty()) {
178 return collection;
179 }
180 int length = collection.size();
181 Object[] result = new Object[length];
182 Iterator<T> collectionItr = collection.iterator();
183 int count = 0;
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);
190 } else {
191 result[count] = loadRecursive(obj, alreadyVisitedEntities, update);
192 }
193
194 count++;
195 }
196
197 collection.clear();
198
199 for ( int i = 0; i < length; i++ ) {
200 collection.add((T)result[i]);
201 }
202
203 return collection;
204 }
205
206
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());
211 }
212
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());
217 }
218
219 /**
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
222 *
223 * @param cacheId
224 * @param uuid
225 * @param cdmEntity
226 */
227 public CdmBase load(CdmBase cdmEntity, boolean recursive, boolean update) {
228 if(cdmEntity == null) {
229 return null;
230 }
231
232 // start by looking up the cdm entity in the cache
233 CdmBase cachedCdmEntity = cdmCacher.getFromCache(cdmEntity);
234
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;
241 }
242 }
243
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");
251 loadedCdmBase = cb;
252 } else {
253 loadedCdmBase = load(cdmEntity);
254 }
255 return loadedCdmBase;
256
257 }
258
259
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);
264 }
265
266
267 private CdmBase loadRecursive(CdmBase cdmEntity, List<Object> alreadyVisitedEntities, boolean update) {
268
269 CdmBase cachedCdmEntity = load(cdmEntity);
270
271
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
274
275 // start by getting the fields from the cdm entity
276 String className = cdmEntity.getClass().getName();
277 CdmModelFieldPropertyFromClass cmgmfc = getFromCdmlibModelCache(className);
278 if(cmgmfc != null) {
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
285
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);
292 } else {
293 logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
294 }
295 }
296 }
297 } else {
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" );
300 }
301
302 return cachedCdmEntity;
303 }
304
305
306 private CdmBase getCdmBaseTypeFieldValue(CdmBase cdmEntity,
307 CdmBase cachedCdmEntity,
308 String fieldName,
309 List<Object> alreadyVisitedEntities,
310 boolean update) {
311
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");
317 }
318
319 Class<?> clazz = cdmEntity.getClass();
320 try {
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);
324
325 if(field == null) {
326 throw new CdmClientCacheException("Field '" + fieldName
327 + "' not found when searching in class '" + clazz.getName() + "' and its supercalsses");
328 }
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;
336
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
340 //
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
343 //
344 // NOTE : the field is overridden in the case of the exception
345 // found below
346 field.set(cachedCdmEntity, o);
347
348 }
349
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());
353
354 cdmEntityInSubGraph = (CdmBase)o;
355 CdmBase cachedCdmEntityInSubGraph = cdmCacher.getFromCache(cdmEntityInSubGraph);
356
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);
366 } else {
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
371 return null;
372 }
373 }
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);
378 }
379 }
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);
390 }
391 }
392
393 private boolean checkForIdenticalCdmEntity(List<Object> objList, Object objToCompare) {
394 if(objToCompare != null) {
395 for(Object obj : objList) {
396 if(obj == objToCompare) {
397 return true;
398 }
399 }
400 }
401 return false;
402 }
403
404
405 public static boolean isRecursiveEnabled() {
406 return isRecursiveEnabled;
407 }
408
409 public static void setRecursiveEnabled(boolean ire) {
410 isRecursiveEnabled = ire;
411 }
412 }