Add load capability for MergeResult objects
[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 }
95
96
97 logger.info("No caching yet for type " + obj.getClass().getName());
98
99 return obj;
100 }
101
102 public <T extends Object> Map<T,T> load(Map<T,T> map, boolean recursive, boolean update){
103
104
105 if(isRecursiveEnabled && recursive) {
106 logger.info("---- starting recursive load for cdm entity map");
107 List<Object> alreadyVisitedEntities = new ArrayList<Object>();
108 Map<T,T> cachedMap = load(map, alreadyVisitedEntities, update);
109 alreadyVisitedEntities.clear();
110 logger.info("---- ending recursive load for cdm entity map \n");
111 return cachedMap;
112 } else {
113 return load(map, null, update);
114 }
115 }
116
117
118 private <T extends Object> Map<T,T> load(Map<T,T> map, List<Object> alreadyVisitedEntities, boolean update){
119 //map = (Map<T,T>)deproxy(map);
120
121 if(map == null || map.isEmpty()) {
122 return map;
123 }
124
125 int originalMapSize = map.size();
126 Object[] result = new Object[ map.size() * 2 ];
127 Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
128 int i=0;
129 // to avoid ConcurrentModificationException
130 alreadyVisitedEntities.add(map);
131 while ( iter.hasNext() ) {
132 Map.Entry<T,T> e = iter.next();
133 result[i++] = e.getKey();
134 result[i++] = e.getValue();
135 }
136
137 for(i=0; i<result.length;i++) {
138 if(alreadyVisitedEntities == null) {
139 result[i] = load(result[i], false, update);
140 } else {
141 result[i] = loadRecursive(result[i], alreadyVisitedEntities, update);
142 }
143 }
144 map.clear();
145 for(i = 0; i < originalMapSize; i+=2 ) {
146 map.put(
147 (T)result[i],
148 (T)result[i+1]
149 );
150 }
151 return map;
152 }
153
154 public <T extends Object> Collection<T> load(Collection<T> collection, boolean recursive, boolean update){
155
156 Collection<T> loadedCollection;
157 if(isRecursiveEnabled && recursive) {
158 logger.info("---- starting recursive load for cdm entity collection");
159 List<Object> alreadyVisitedEntities = new ArrayList<Object>();
160 Collection<T> cachedCollection = load(collection, alreadyVisitedEntities, update);
161 alreadyVisitedEntities.clear();
162 logger.info("---- ending recursive load for cdm entity collection \n");
163 loadedCollection = cachedCollection;
164 } else {
165 loadedCollection = load(collection, null, update);
166 }
167 return loadedCollection;
168 }
169
170 @SuppressWarnings("unchecked")
171 private <T extends Object> Collection<T> load(Collection<T> collection, List<Object> alreadyVisitedEntities, boolean update) {
172
173
174
175 if(collection == null || collection.isEmpty()) {
176 return collection;
177 }
178 int length = collection.size();
179 Object[] result = new Object[length];
180 Iterator<T> collectionItr = collection.iterator();
181 int count = 0;
182 // to avoid ConcurrentModificationException
183 alreadyVisitedEntities.add(collection);
184 while(collectionItr.hasNext()) {
185 Object obj = collectionItr.next();
186 if(alreadyVisitedEntities == null) {
187 result[count] = load(obj, false, update);
188 } else {
189 result[count] = loadRecursive(obj, alreadyVisitedEntities, update);
190 }
191
192 count++;
193 }
194
195 collection.clear();
196
197 for ( int i = 0; i < length; i++ ) {
198 collection.add((T)result[i]);
199 }
200
201 return collection;
202 }
203
204
205 public MergeResult<CdmBase> load(MergeResult<CdmBase> mergeResult, boolean recursive, boolean update) {
206 CdmBase cdmBase = load(mergeResult.getMergedEntity(), recursive, update);
207 load(mergeResult.getNewEntities(), recursive, update);
208 return mergeResult;
209 }
210
211
212 /**
213 * Puts the (Key,Value) pair of ({@link java.util.UUID}, {@link eu.etaxonomy.cdm.model.common.CdmBase}),
214 * in the cache corresponding to the given cache id
215 *
216 * @param cacheId
217 * @param uuid
218 * @param cdmEntity
219 */
220 public CdmBase load(CdmBase cdmEntity, boolean recursive, boolean update) {
221 if(cdmEntity == null) {
222 return null;
223 }
224
225 // start by looking up the cdm entity in the cache
226 CdmBase cachedCdmEntity = cdmCacher.getFromCache(cdmEntity);
227
228 if(cachedCdmEntity != null) {
229 // if cdm entity was found in cache then
230 logger.info(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " already exists");
231 // .. return if the cached and input objects are identical, else (this is a newly loaded object so) continue
232 if(cachedCdmEntity == cdmEntity) {
233 return cachedCdmEntity;
234 }
235 }
236
237 CdmBase loadedCdmBase;
238 if(isRecursiveEnabled && recursive) {
239 logger.info("---- starting recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
240 List<Object> alreadyVisitedEntities = new ArrayList<Object>();
241 CdmBase cb = loadRecursive(cdmEntity, alreadyVisitedEntities, update);
242 alreadyVisitedEntities.clear();
243 logger.info("---- ending recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + "\n");
244 loadedCdmBase = cb;
245 } else {
246 loadedCdmBase = load(cdmEntity);
247 }
248 return loadedCdmBase;
249
250 }
251
252
253 protected CdmBase load(CdmBase cdmEntity) {
254 logger.info("loading object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
255 cdmCacher.put((CdmBase)ProxyUtils.deproxy(cdmEntity));
256 return cdmCacher.getFromCache(cdmEntity);
257 }
258
259
260 private CdmBase loadRecursive(CdmBase cdmEntity, List<Object> alreadyVisitedEntities, boolean update) {
261
262 CdmBase cachedCdmEntity = load(cdmEntity);
263
264
265 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
266 // since there could be new or deleted objects in the cdmEntity sub-graph
267
268 // start by getting the fields from the cdm entity
269 String className = cdmEntity.getClass().getName();
270 CdmModelFieldPropertyFromClass cmgmfc = getFromCdmlibModelCache(className);
271 if(cmgmfc != null) {
272 alreadyVisitedEntities.add(cdmEntity);
273 List<String> fields = cmgmfc.getFields();
274 for(String field : fields) {
275 // retrieve the actual object corresponding to the field.
276 // this object will be either a CdmBase or a Collection / Map
277 // with CdmBase as the generic type
278
279 CdmBase cdmEntityInSubGraph = getCdmBaseTypeFieldValue(cdmEntity, cachedCdmEntity, field, alreadyVisitedEntities, update);
280 if(cdmEntityInSubGraph != null) {
281 //checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph);
282 if(!checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph)) {
283 logger.info("recursive loading object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
284 loadRecursive(cdmEntityInSubGraph, alreadyVisitedEntities, update);
285 } else {
286 logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
287 }
288 }
289 }
290 } else {
291 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
292 "The cache may be corrupted or not in sync with the latest model version" );
293 }
294
295 return cachedCdmEntity;
296 }
297
298
299 private CdmBase getCdmBaseTypeFieldValue(CdmBase cdmEntity,
300 CdmBase cachedCdmEntity,
301 String fieldName,
302 List<Object> alreadyVisitedEntities,
303 boolean update) {
304
305 // this method attempts to make sure that for any two objects found in
306 // the object graph, if they are equal then they should also be the same,
307 // which is crucial for the merge to work
308 if(cachedCdmEntity == null) {
309 throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
310 }
311
312 Class<?> clazz = cdmEntity.getClass();
313 try {
314 // this call will search in the provided class as well as
315 // the super classes until it finds the field
316 Field field = ReflectionUtils.findField(clazz, fieldName);
317
318 if(field == null) {
319 throw new CdmClientCacheException("Field '" + fieldName
320 + "' not found when searching in class '" + clazz.getName() + "' and its supercalsses");
321 }
322 field.setAccessible(true);
323 Object o = field.get(cdmEntity);
324 // resetting the value in cdm entity to the deproxied object
325 o = ProxyUtils.deproxy(o);
326 field.set(cdmEntity, o);
327 Object cachedo = field.get(cachedCdmEntity);
328 CdmBase cdmEntityInSubGraph = null;
329
330 if(update || ProxyUtils.isProxy(cachedo)) {
331 // if we are in update mode we have to make the field of the cached entity
332 // up-to-date by setting it to the value of the cdm entity being loaded
333 //
334 // if the cdm entity is a proxy then we always update to make sure that
335 // newly created entities are always up-to-date
336 //
337 // NOTE : the field is overridden in the case of the exception
338 // found below
339 field.set(cachedCdmEntity, o);
340
341 }
342
343 if(o != null && !ProxyUtils.isProxy(o)) {
344 if(CdmBase.class.isAssignableFrom(o.getClass())) {
345 logger.info("found initialised cdm entity '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
346
347 cdmEntityInSubGraph = (CdmBase)o;
348 CdmBase cachedCdmEntityInSubGraph = cdmCacher.getFromCache(cdmEntityInSubGraph);
349
350 if(cachedCdmEntityInSubGraph != null) {
351 if(cachedCdmEntityInSubGraph != cdmEntityInSubGraph) {
352 // exception : is the case where
353 // the field has been already initialised, cached and
354 // is not the same as the one in the cache, in which case we set the value
355 // of the field to the one found in the cache
356 logger.info("setting cached + real value to '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
357 field.set(cachedCdmEntity, cachedCdmEntityInSubGraph);
358 field.set(cdmEntity, cachedCdmEntityInSubGraph);
359 } else {
360 // since the field value object in cdmEntity
361 // is the same as the field value object in cachedCdmEntity
362 // we are sure that the its subgraph is also correctly loaded,
363 // so we can exit the recursion
364 return null;
365 }
366 }
367 } else if(o instanceof Map && !checkForIdenticalCdmEntity(alreadyVisitedEntities, o)) {
368 loadRecursive((Map)o, alreadyVisitedEntities, update);
369 } else if(o instanceof Collection && !checkForIdenticalCdmEntity(alreadyVisitedEntities, o)) {
370 loadRecursive((Collection)o, alreadyVisitedEntities, update);
371 }
372 }
373 // we return the original cdm entity in the sub graph because we
374 // want to continue to recurse on the input cdm entity graph
375 // and not the one in the cache
376 return cdmEntityInSubGraph;
377 } catch (SecurityException e) {
378 throw new CdmClientCacheException(e);
379 } catch (IllegalArgumentException e) {
380 throw new CdmClientCacheException(e);
381 } catch (IllegalAccessException e) {
382 throw new CdmClientCacheException(e);
383 }
384 }
385
386 private boolean checkForIdenticalCdmEntity(List<Object> objList, Object objToCompare) {
387 if(objToCompare != null) {
388 for(Object obj : objList) {
389 if(obj == objToCompare) {
390 return true;
391 }
392 }
393 }
394 return false;
395 }
396
397
398 public static boolean isRecursiveEnabled() {
399 return isRecursiveEnabled;
400 }
401
402 public static void setRecursiveEnabled(boolean ire) {
403 isRecursiveEnabled = ire;
404 }
405 }