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