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