Project

General

Profile

Download (16.4 KB) Statistics
| Branch: | Tag: | Revision:
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
        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 new MergeResult(cdmBase, mergeResult.getNewEntities());
209
    }
210

    
211
    public MergeResult<CdmBase> loadRecursive(MergeResult<CdmBase> mergeResult,List<Object> alreadyVisitedEntities, boolean update) {
212
        CdmBase cdmBase = loadRecursive(mergeResult.getMergedEntity(), alreadyVisitedEntities, update);
213
        loadRecursive(mergeResult.getNewEntities(), alreadyVisitedEntities, update);
214
        return new MergeResult(cdmBase, mergeResult.getNewEntities());
215
    }
216

    
217
    /**
218
     * Puts the (Key,Value) pair of ({@link java.util.UUID}, {@link eu.etaxonomy.cdm.model.common.CdmBase}),
219
     * in the cache corresponding to the given cache id
220
     *
221
     * @param cacheId
222
     * @param uuid
223
     * @param cdmEntity
224
     */
225
    public CdmBase load(CdmBase cdmEntity, boolean recursive, boolean update) {
226
        if(cdmEntity == null) {
227
            return null;
228
        }
229

    
230
        // start by looking up the cdm entity in the cache
231
        CdmBase cachedCdmEntity = cdmCacher.getFromCache(cdmEntity);
232

    
233
        if(cachedCdmEntity != null) {
234
            // if cdm entity was found in cache then
235
            logger.info(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " already exists");
236
            // .. return if the cached and input objects are identical, else (this is a newly loaded object so) continue
237
            if(cachedCdmEntity == cdmEntity) {
238
                return cachedCdmEntity;
239
            }
240
        }
241

    
242
        CdmBase loadedCdmBase;
243
        if(isRecursiveEnabled && recursive) {
244
            logger.info("---- starting recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
245
            List<Object> alreadyVisitedEntities = new ArrayList<Object>();
246
            CdmBase cb =  loadRecursive(cdmEntity, alreadyVisitedEntities, update);
247
            alreadyVisitedEntities.clear();
248
            logger.info("---- ending recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + "\n");
249
            loadedCdmBase =  cb;
250
        } else {
251
            loadedCdmBase = load(cdmEntity);
252
        }
253
        return loadedCdmBase;
254

    
255
    }
256

    
257

    
258
    protected CdmBase load(CdmBase cdmEntity) {
259
        logger.info("loading object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
260
        cdmCacher.put((CdmBase)ProxyUtils.deproxy(cdmEntity));
261
        return cdmCacher.getFromCache(cdmEntity);
262
    }
263

    
264

    
265
    private CdmBase loadRecursive(CdmBase cdmEntity,  List<Object> alreadyVisitedEntities, boolean update) {
266

    
267
        CdmBase cachedCdmEntity = load(cdmEntity);
268

    
269

    
270
        // we want to recursive through the cdmEntity (and not the cachedCdmEntity)
271
        // since there could be new or deleted objects in the cdmEntity sub-graph
272

    
273
        // start by getting the fields from the cdm entity
274
        String className = cdmEntity.getClass().getName();
275
        CdmModelFieldPropertyFromClass cmgmfc = getFromCdmlibModelCache(className);
276
        if(cmgmfc != null) {
277
            alreadyVisitedEntities.add(cdmEntity);
278
            List<String> fields = cmgmfc.getFields();
279
            for(String field : fields) {
280
                // retrieve the actual object corresponding to the field.
281
                // this object will be either a CdmBase or a Collection / Map
282
                // with CdmBase as the generic type
283

    
284
                CdmBase cdmEntityInSubGraph = getCdmBaseTypeFieldValue(cdmEntity, cachedCdmEntity, field, alreadyVisitedEntities, update);
285
                if(cdmEntityInSubGraph != null) {
286
                    //checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph);
287
                    if(!checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph)) {
288
                        logger.info("recursive loading object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
289
                        loadRecursive(cdmEntityInSubGraph, alreadyVisitedEntities, update);
290
                    } else {
291
                        logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
292
                    }
293
                }
294
            }
295
        } else {
296
            throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
297
                    "The cache may be corrupted or not in sync with the latest model version" );
298
        }
299

    
300
        return cachedCdmEntity;
301
    }
302

    
303

    
304
    private CdmBase getCdmBaseTypeFieldValue(CdmBase cdmEntity,
305
            CdmBase cachedCdmEntity,
306
            String fieldName,
307
            List<Object> alreadyVisitedEntities,
308
            boolean update) {
309

    
310
        // this method attempts to make sure that for any two objects found in
311
        // the object graph, if they are equal then they should also be the same,
312
        // which is crucial for the merge to work
313
        if(cachedCdmEntity == null) {
314
            throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
315
        }
316

    
317
        Class<?> clazz = cdmEntity.getClass();
318
        try {
319
            // this call will search in the provided class as well as
320
            // the super classes until it finds the field
321
            Field field = ReflectionUtils.findField(clazz, fieldName);
322

    
323
            if(field == null) {
324
                throw new CdmClientCacheException("Field '" + fieldName
325
                        + "' not found when searching in class '" + clazz.getName() + "' and its superclasses");
326
            }
327
            field.setAccessible(true);
328
            Object o = field.get(cdmEntity);
329
            // resetting the value in cdm entity to the deproxied object
330
            o = ProxyUtils.deproxy(o);
331
            field.set(cdmEntity, o);
332
            Object cachedo = field.get(cachedCdmEntity);
333
            CdmBase cdmEntityInSubGraph = null;
334

    
335
            if(!ProxyUtils.isProxy(o) && (update || ProxyUtils.isProxy(cachedo))) {
336
                // if we are in update mode we have to make the field of the cached entity
337
                // up-to-date by setting it to the value of the cdm entity being loaded
338
                //
339
                // if the cdm entity is a proxy then we always update to make sure that
340
                // newly created entities are always up-to-date
341
                //
342
                // NOTE : the field is overridden in the case of the exception
343
                // found below
344
                field.set(cachedCdmEntity, o);
345

    
346
            }
347

    
348
            if(o != null && !ProxyUtils.isProxy(o)) {
349
                if(CdmBase.class.isAssignableFrom(o.getClass())) {
350
                    logger.info("found initialised cdm entity '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
351

    
352
                    cdmEntityInSubGraph  = (CdmBase)o;
353
                    CdmBase cachedCdmEntityInSubGraph = cdmCacher.getFromCache(cdmEntityInSubGraph);
354

    
355
                    if(cachedCdmEntityInSubGraph != null) {
356
                        if(cachedCdmEntityInSubGraph != cdmEntityInSubGraph) {
357
                            // exception : is the case where
358
                            // the field has been already initialised, cached and
359
                            // is not the same as the one in the cache, in which case we set the value
360
                            // of the field to the one found in the cache
361
                            logger.info("setting cached + real value to '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
362
                            field.set(cachedCdmEntity, cachedCdmEntityInSubGraph);
363
                            field.set(cdmEntity, cachedCdmEntityInSubGraph);
364
                        } else {
365
                            // since the field value object in cdmEntity
366
                            // is the same as the field value object in cachedCdmEntity
367
                            // we are sure that the its subgraph is also correctly loaded,
368
                            // so we can exit the recursion
369
                            return null;
370
                        }
371
                    }
372
                } else if(o instanceof Map && !checkForIdenticalCdmEntity(alreadyVisitedEntities, o)) {
373
                    loadRecursive((Map)o, alreadyVisitedEntities, update);
374
                } else if(o instanceof Collection && !checkForIdenticalCdmEntity(alreadyVisitedEntities, o)) {
375
                    loadRecursive((Collection)o, alreadyVisitedEntities, update);
376
                }
377
            }
378
            // we return the original cdm entity in the sub graph because we
379
            // want to continue to recurse on the input cdm entity graph
380
            // and not the one in the cache
381
            return cdmEntityInSubGraph;
382
        } catch (SecurityException e) {
383
            throw new CdmClientCacheException(e);
384
        } catch (IllegalArgumentException e) {
385
            throw new CdmClientCacheException(e);
386
        } catch (IllegalAccessException e) {
387
            throw new CdmClientCacheException(e);
388
        }
389
    }
390

    
391
    private boolean checkForIdenticalCdmEntity(List<Object> objList, Object objToCompare) {
392
        if(objToCompare != null) {
393
            for(Object obj : objList) {
394
                if(obj == objToCompare) {
395
                    return true;
396
                }
397
            }
398
        }
399
        return false;
400
    }
401

    
402

    
403
    public static boolean isRecursiveEnabled() {
404
        return isRecursiveEnabled;
405
    }
406

    
407
    public static void  setRecursiveEnabled(boolean ire) {
408
        isRecursiveEnabled = ire;
409
    }
410
}
(1-1/10)