fix #7312: fix immediately saving of preference changes
[taxeditor.git] / eu.etaxonomy.taxeditor.cdmlib / src / main / java / eu / etaxonomy / taxeditor / remoting / cache / CacheLoader.java
1 /**
2 * Copyright (C) 2015 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
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.
8 */
9 package eu.etaxonomy.taxeditor.remoting.cache;
10
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;
16 import java.util.Map;
17
18 import org.apache.log4j.Logger;
19 import org.springframework.util.ReflectionUtils;
20
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;
27
28 /**
29 * @author cmathew
30 * @date 19 Feb 2015
31 *
32 */
33 public class CacheLoader {
34 private static final Logger logger = Logger.getLogger(CacheLoader.class);
35
36 private static boolean isRecursiveEnabled = true;
37
38 protected final ICdmCacher cdmCacher;
39
40 private final Cache cdmlibModelCache;
41
42
43
44 public CacheLoader(ICdmCacher cdmCacher) {
45 this.cdmCacher = cdmCacher;
46 this.cdmlibModelCache = CdmRemoteCacheManager.getInstance().getCdmModelGetMethodsCache();
47
48 }
49
50
51 public CdmModelFieldPropertyFromClass getFromCdmlibModelCache(String className) {
52 Element e = cdmlibModelCache.get(className);
53 if (e == null) {
54 return null;
55 } else {
56 return (CdmModelFieldPropertyFromClass) e.getObjectValue();
57 }
58 }
59
60 @SuppressWarnings("unchecked")
61 public <T extends Object> T load(T obj, boolean recursive, boolean update) {
62 if(obj == null) {
63 return null;
64 }
65 if(obj instanceof CdmBase) {
66 return (T) load((CdmBase)obj, recursive, update);
67 } else if (obj instanceof Map) {
68 return (T) load((Map<T,T>)obj, recursive, update);
69 } else if (obj instanceof Collection) {
70 return (T) load((Collection<T>)obj, recursive, update);
71 } else if(obj instanceof Pager) {
72 load(((Pager)obj).getRecords(), recursive, update);
73 return obj;
74 } else if(obj instanceof MergeResult) {
75 return (T) load((MergeResult<CdmBase>)obj, recursive, update);
76 }
77
78 return obj;
79 }
80
81 @SuppressWarnings("unchecked")
82 private <T extends Object> T loadRecursive(T obj, List<Object> alreadyVisitedEntities, boolean update) {
83 if(obj == null) {
84 return null;
85 }
86 if(obj instanceof CdmBase) {
87 return (T) loadRecursive((CdmBase)obj, alreadyVisitedEntities, update);
88 } else if (obj instanceof Map) {
89 return (T) load((Map<T,T>)obj, alreadyVisitedEntities, update);
90 } else if (obj instanceof Collection) {
91 return (T) load((Collection<T>)obj, alreadyVisitedEntities, update);
92 } else if (obj instanceof MergeResult) {
93 return (T) loadRecursive((MergeResult)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 Object[] result = new Object[ map.size() * 2 ];
126 Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
127 int i=0;
128 // to avoid ConcurrentModificationException
129 alreadyVisitedEntities.add(map);
130 while ( iter.hasNext() ) {
131 Map.Entry<T,T> e = iter.next();
132 result[i++] = e.getKey();
133 result[i++] = e.getValue();
134 }
135
136 for(i=0; i<result.length;i++) {
137 if(alreadyVisitedEntities == null) {
138 result[i] = load(result[i], false, update);
139 } else {
140 result[i] = loadRecursive(result[i], alreadyVisitedEntities, update);
141 }
142 }
143 map.clear();
144 for(i = 0; i < result.length; i+=2 ) {
145 map.put(
146 (T)result[i],
147 (T)result[i+1]
148 );
149 }
150 return map;
151 }
152
153 public <T extends Object> Collection<T> load(Collection<T> collection, boolean recursive, boolean update){
154
155 Collection<T> loadedCollection;
156 if(isRecursiveEnabled && recursive) {
157 logger.info("---- starting recursive load for cdm entity collection");
158 List<Object> alreadyVisitedEntities = new ArrayList<Object>();
159 Collection<T> cachedCollection = load(collection, alreadyVisitedEntities, update);
160 alreadyVisitedEntities.clear();
161 logger.info("---- ending recursive load for cdm entity collection \n");
162 loadedCollection = cachedCollection;
163 } else {
164 loadedCollection = load(collection, null, update);
165 }
166 return loadedCollection;
167 }
168
169 @SuppressWarnings("unchecked")
170 private <T extends Object> Collection<T> load(Collection<T> collection, List<Object> alreadyVisitedEntities, boolean update) {
171
172
173
174 if(collection == null || collection.isEmpty()) {
175 return collection;
176 }
177 int length = collection.size();
178 Object[] result = new Object[length];
179 Iterator<T> collectionItr = collection.iterator();
180 int count = 0;
181 // to avoid ConcurrentModificationException
182 alreadyVisitedEntities.add(collection);
183 while(collectionItr.hasNext()) {
184 Object obj = collectionItr.next();
185 if(alreadyVisitedEntities == null) {
186 result[count] = load(obj, false, update);
187 } else {
188 result[count] = loadRecursive(obj, alreadyVisitedEntities, update);
189 }
190
191 count++;
192 }
193
194 collection.clear();
195
196 for ( int i = 0; i < length; i++ ) {
197 collection.add((T)result[i]);
198 }
199
200 return collection;
201 }
202
203
204 public MergeResult<CdmBase> load(MergeResult<CdmBase> mergeResult, boolean recursive, boolean update) {
205 CdmBase cdmBase = load(mergeResult.getMergedEntity(), recursive, update);
206 load(mergeResult.getNewEntities(), recursive, update);
207 return new MergeResult(cdmBase, mergeResult.getNewEntities());
208 }
209
210 public MergeResult<CdmBase> loadRecursive(MergeResult<CdmBase> mergeResult,List<Object> alreadyVisitedEntities, boolean update) {
211 CdmBase cdmBase = loadRecursive(mergeResult.getMergedEntity(), alreadyVisitedEntities, update);
212 loadRecursive(mergeResult.getNewEntities(), alreadyVisitedEntities, update);
213 return new MergeResult(cdmBase, mergeResult.getNewEntities());
214 }
215
216 /**
217 * Puts the (Key,Value) pair of ({@link java.util.UUID}, {@link eu.etaxonomy.cdm.model.common.CdmBase}),
218 * in the cache corresponding to the given cache id
219 *
220 * @param cacheId
221 * @param uuid
222 * @param cdmEntity
223 */
224 public CdmBase load(CdmBase cdmEntity, boolean recursive, boolean update) {
225 if(cdmEntity == null) {
226 return null;
227 }
228
229 // start by looking up the cdm entity in the cache
230 CdmBase cachedCdmEntity = cdmCacher.getFromCache(cdmEntity);
231
232 if(cachedCdmEntity != null) {
233 // if cdm entity was found in cache then
234 logger.info(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " already exists");
235 // .. return if the cached and input objects are identical, else (this is a newly loaded object so) continue
236 if(cachedCdmEntity == cdmEntity) {
237 return cachedCdmEntity;
238 }
239 }
240
241 CdmBase loadedCdmBase;
242 if(isRecursiveEnabled && recursive) {
243 logger.info("---- starting recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
244 List<Object> alreadyVisitedEntities = new ArrayList<Object>();
245 CdmBase cb = loadRecursive(cdmEntity, alreadyVisitedEntities, update);
246 alreadyVisitedEntities.clear();
247 logger.info("---- ending recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + "\n");
248 loadedCdmBase = cb;
249 } else {
250 loadedCdmBase = load(cdmEntity);
251 }
252 return loadedCdmBase;
253
254 }
255
256
257 protected CdmBase load(CdmBase cdmEntity) {
258 logger.info("loading object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
259 cdmCacher.put((CdmBase)ProxyUtils.deproxy(cdmEntity));
260 return cdmCacher.getFromCache(cdmEntity);
261 }
262
263
264 private CdmBase loadRecursive(CdmBase cdmEntity, List<Object> alreadyVisitedEntities, boolean update) {
265
266 CdmBase cachedCdmEntity = load(cdmEntity);
267
268
269 // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
270 // since there could be new or deleted objects in the cdmEntity sub-graph
271
272 // start by getting the fields from the cdm entity
273 String className = cdmEntity.getClass().getName();
274 CdmModelFieldPropertyFromClass cmgmfc = getFromCdmlibModelCache(className);
275 if(cmgmfc != null) {
276 alreadyVisitedEntities.add(cdmEntity);
277 List<String> fields = cmgmfc.getFields();
278 for(String field : fields) {
279 // retrieve the actual object corresponding to the field.
280 // this object will be either a CdmBase or a Collection / Map
281 // with CdmBase as the generic type
282
283 CdmBase cdmEntityInSubGraph = getCdmBaseTypeFieldValue(cdmEntity, cachedCdmEntity, field, alreadyVisitedEntities, update);
284 if(cdmEntityInSubGraph != null) {
285 //checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph);
286 if(!checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph)) {
287 logger.info("recursive loading object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
288 loadRecursive(cdmEntityInSubGraph, alreadyVisitedEntities, update);
289 } else {
290 logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
291 }
292 }
293 }
294 } else {
295 throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
296 "The cache may be corrupted or not in sync with the latest model version" );
297 }
298
299 return cachedCdmEntity;
300 }
301
302
303 private CdmBase getCdmBaseTypeFieldValue(CdmBase cdmEntity,
304 CdmBase cachedCdmEntity,
305 String fieldName,
306 List<Object> alreadyVisitedEntities,
307 boolean update) {
308
309 // this method attempts to make sure that for any two objects found in
310 // the object graph, if they are equal then they should also be the same,
311 // which is crucial for the merge to work
312 if(cachedCdmEntity == null) {
313 throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
314 }
315
316 Class<?> clazz = cdmEntity.getClass();
317 try {
318 // this call will search in the provided class as well as
319 // the super classes until it finds the field
320 Field field = ReflectionUtils.findField(clazz, fieldName);
321
322 if(field == null) {
323 throw new CdmClientCacheException("Field '" + fieldName
324 + "' not found when searching in class '" + clazz.getName() + "' and its superclasses");
325 }
326 field.setAccessible(true);
327 Object o = field.get(cdmEntity);
328 // resetting the value in cdm entity to the deproxied object
329 o = ProxyUtils.deproxy(o);
330 field.set(cdmEntity, o);
331 Object cachedo = field.get(cachedCdmEntity);
332 CdmBase cdmEntityInSubGraph = null;
333
334 if(!ProxyUtils.isProxy(o) && (update || ProxyUtils.isProxy(cachedo))) {
335 // if we are in update mode we have to make the field of the cached entity
336 // up-to-date by setting it to the value of the cdm entity being loaded
337 //
338 // if the cdm entity is a proxy then we always update to make sure that
339 // newly created entities are always up-to-date
340 //
341 // NOTE : the field is overridden in the case of the exception
342 // found below
343 field.set(cachedCdmEntity, o);
344
345 }
346
347 if(o != null && !ProxyUtils.isProxy(o)) {
348 if(CdmBase.class.isAssignableFrom(o.getClass())) {
349 logger.info("found initialised cdm entity '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
350
351 cdmEntityInSubGraph = (CdmBase)o;
352 CdmBase cachedCdmEntityInSubGraph = cdmCacher.getFromCache(cdmEntityInSubGraph);
353
354 if(cachedCdmEntityInSubGraph != null) {
355 if(cachedCdmEntityInSubGraph != cdmEntityInSubGraph) {
356 // exception : is the case where
357 // the field has been already initialised, cached and
358 // is not the same as the one in the cache, in which case we set the value
359 // of the field to the one found in the cache
360 logger.info("setting cached + real value to '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
361 field.set(cachedCdmEntity, cachedCdmEntityInSubGraph);
362 field.set(cdmEntity, cachedCdmEntityInSubGraph);
363 } else {
364 // since the field value object in cdmEntity
365 // is the same as the field value object in cachedCdmEntity
366 // we are sure that the its subgraph is also correctly loaded,
367 // so we can exit the recursion
368 return null;
369 }
370 }
371 } else if(o instanceof Map && !checkForIdenticalCdmEntity(alreadyVisitedEntities, o)) {
372 loadRecursive((Map)o, alreadyVisitedEntities, update);
373 } else if(o instanceof Collection && !checkForIdenticalCdmEntity(alreadyVisitedEntities, o)) {
374 loadRecursive((Collection)o, alreadyVisitedEntities, update);
375 }
376 }
377 // we return the original cdm entity in the sub graph because we
378 // want to continue to recurse on the input cdm entity graph
379 // and not the one in the cache
380 return cdmEntityInSubGraph;
381 } catch (SecurityException e) {
382 throw new CdmClientCacheException(e);
383 } catch (IllegalArgumentException e) {
384 throw new CdmClientCacheException(e);
385 } catch (IllegalAccessException e) {
386 throw new CdmClientCacheException(e);
387 }
388 }
389
390 private boolean checkForIdenticalCdmEntity(List<Object> objList, Object objToCompare) {
391 if(objToCompare != null) {
392 for(Object obj : objList) {
393 if(obj == objToCompare) {
394 return true;
395 }
396 }
397 }
398 return false;
399 }
400
401
402 public static boolean isRecursiveEnabled() {
403 return isRecursiveEnabled;
404 }
405
406 public static void setRecursiveEnabled(boolean ire) {
407 isRecursiveEnabled = ire;
408 }
409 }