Project

General

Profile

Download (19.3 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.cdm.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
 \* @since 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.debug("---- 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.debug("---- 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 < result.length; 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.debug("---- 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.debug("---- 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
     * Loads the {@link eu.etaxonomy.cdm.model.common.CdmBase cdmEntity}) in the
219
     * cache.
220
     * <p>
221
     * <b>WARNING: Recursive updating of the cached entity will not take place
222
     * in case there is a cached entity which is the same object as
223
     * <code>cdmEntity</code>.</b>
224
     *
225
     * For in depth details on the mechanism see
226
     * {@link #loadRecursive(CdmBase, List, boolean)} and
227
     * {@link #getCdmBaseTypeFieldValue(CdmBase, CdmBase, String, List, boolean)}
228
     *
229
     * @param cdmEntity
230
     *            the entity to be put into the cache
231
     * @param recursive
232
     *            if <code>true</code>, the cache loader will load the whole
233
     *            entity graph recursively into the cache
234
     * @param update
235
     *            all fields of the cached entity will be overwritten by setting
236
     *            them to the value of the cdm entity being loaded
237
     */
238
    public <T extends CdmBase> T load(T cdmEntity, boolean recursive, boolean update) {
239
        if(cdmEntity == null) {
240
            return null;
241
        }
242

    
243
        // start by looking up the cdm entity in the cache
244
        T cachedCdmEntity = cdmCacher.getFromCache(cdmEntity);
245

    
246
        if(cachedCdmEntity != null) {
247
            // if cdm entity was found in cache then
248
            logger.debug(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " already exists");
249
            // .. return if the cached and input objects are identical, else (this is a newly loaded object so) continue
250
            if(cachedCdmEntity == cdmEntity) {
251
                return cachedCdmEntity;
252
            }
253
        }
254

    
255
        CdmBase loadedCdmBase;
256
        if(isRecursiveEnabled && recursive) {
257
            logger.debug("---- starting recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
258
            List<Object> alreadyVisitedEntities = new ArrayList<Object>();
259
            CdmBase cb =  loadRecursive(cdmEntity, alreadyVisitedEntities, update);
260
            alreadyVisitedEntities.clear();
261
            logger.debug("---- ending recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + "\n");
262
            loadedCdmBase =  cb;
263
        } else {
264
            loadedCdmBase = load(cdmEntity);
265
        }
266
        return (T) loadedCdmBase;
267

    
268
    }
269

    
270

    
271
    protected CdmBase load(CdmBase cdmEntity) {
272
        logger.debug("loading object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
273
        cdmCacher.put((CdmBase)ProxyUtils.deproxy(cdmEntity));
274
        return cdmCacher.getFromCache(cdmEntity);
275
    }
276

    
277

    
278
    /**
279
     * Load the <code>cdmEntity</code> graph recursively into the cache and
280
     * updates entity which are already in the cache depending on the value of
281
     * <code>update</code>, for more in depth details on this mechanism see
282
     * {@link #getCdmBaseTypeFieldValue(CdmBase, CdmBase, String, List, boolean)}.
283
     *
284
     *
285
     * @param cdmEntity
286
     *            the entity to be loaded into the cache
287
     * @param alreadyVisitedEntities
288
     *            protocol list of entities already visited during loading an
289
     *            entity graph recursively into the cache.
290
     *
291
     * @param update
292
     *            all fields of the cached entity will be overwritten by setting
293
     *            them to the value of the cdm entity being loaded
294
     * @return
295
     */
296
    private CdmBase loadRecursive(CdmBase cdmEntity,  List<Object> alreadyVisitedEntities, boolean update) {
297

    
298
        CdmBase cachedCdmEntity = load(cdmEntity);
299

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

    
303
        // start by getting the fields from the cdm entity
304
        CdmBase deproxiedEntity = (CdmBase)ProxyUtils.deproxyOrNull(cdmEntity);
305
        if(deproxiedEntity != null){
306
            String className = deproxiedEntity.getClass().getName();
307
            CdmModelFieldPropertyFromClass cmgmfc = getFromCdmlibModelCache(className);
308
            if(cmgmfc != null) {
309
                alreadyVisitedEntities.add(cdmEntity);
310
                List<String> fields = cmgmfc.getFields();
311
                for(String field : fields) {
312
                    // retrieve the actual object corresponding to the field.
313
                    // this object will be either a CdmBase or a Collection / Map
314
                    // with CdmBase as the generic type
315

    
316
                    CdmBase cdmEntityInSubGraph = getCdmBaseTypeFieldValue(deproxiedEntity, cachedCdmEntity, field, alreadyVisitedEntities, update);
317
                    if(cdmEntityInSubGraph != null) {
318
                        //checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph);
319
                        if(!checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph)) {
320
                            logger.debug("recursive loading object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
321
                            loadRecursive(cdmEntityInSubGraph, alreadyVisitedEntities, update);
322
                        } else {
323
                            logger.debug("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
324
                        }
325
                    }
326
                }
327
            } else {
328
                throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
329
                        "The cache may be corrupted or not in sync with the latest model version" );
330
            }
331
        } else {
332
            logger.debug("ignoring uninitlialized proxy " + cdmEntity.getClass() + "#" + cdmEntity.getId());
333
        }
334

    
335
        return cachedCdmEntity;
336
    }
337

    
338
    /**
339
     * All field of the <code>cdmEntity</code> containing proxy objects will be
340
     * set to the un-proxied field value. If <code>update</code> is enabled the
341
     * value of the cached entity will be overwritten by the value of the
342
     * <code>cdmEntity</code>. In case the cached field value contains a proxy
343
     * object the value will aways be overwritten (Q: This might only occur in
344
     * case of uninitialized proxies, since initialized proxies are expected to
345
     * be replaces by the target entity.)
346
     *
347
     * @param cdmEntity
348
     *            the entity to be loaded into the cache
349
     * @param cachedCdmEntity
350
     *            the entity which resides in the cache
351
     * @param fieldName
352
     *            the field name to operate on
353
     * @param alreadyVisitedEntities
354
     *            protocol list of entities already visited during loading an
355
     *            entity graph recursively into the cache.
356
     * @param update
357
     *            all fields of the cached entity will be overwritten by setting
358
     *            them to the value of the cdm entity being loaded
359
     * @return
360
     */
361
    private CdmBase getCdmBaseTypeFieldValue(CdmBase cdmEntity,
362
            CdmBase cachedCdmEntity,
363
            String fieldName,
364
            List<Object> alreadyVisitedEntities,
365
            boolean update) {
366

    
367
        // this method attempts to make sure that for any two objects found in
368
        // the object graph, if they are equal then they should also be the same,
369
        // which is crucial for the merge to work
370
        if(cachedCdmEntity == null) {
371
            throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
372
        }
373

    
374
        Class<?> clazz = cdmEntity.getClass();
375
        try {
376
            // this call will search in the provided class as well as
377
            // the super classes until it finds the field
378
            Field field = ReflectionUtils.findField(clazz, fieldName);
379

    
380
            if(field == null) {
381
                throw new CdmClientCacheException("Field '" + fieldName
382
                        + "' not found when searching in class '" + clazz.getName() + "' and its superclasses");
383
            }
384
            field.setAccessible(true);
385
            Object o = field.get(cdmEntity);
386
            // resetting the value in cdm entity to the deproxied object
387
            o = ProxyUtils.deproxy(o);
388
            field.set(cdmEntity, o);
389
            Object cachedo = field.get(cachedCdmEntity);
390
            CdmBase cdmEntityInSubGraph = null;
391

    
392
            if(!ProxyUtils.isUninitializedProxy(o) && (update || ProxyUtils.isUninitializedProxy(cachedo))) {
393
                // if we are in update mode we have to make the field of the cached entity
394
                // up-to-date by setting it to the value of the cdm entity being loaded
395
                //
396
                // if the cdm entity is a proxy then we always update to make sure that
397
                // newly created entities are always up-to-date
398
                //
399
                // NOTE : the field is overridden in the case of the exception
400
                // found below
401
                field.set(cachedCdmEntity, o);
402

    
403
            }
404

    
405
            if(o != null && !ProxyUtils.isUninitializedProxy(o)) {
406
                if(CdmBase.class.isAssignableFrom(o.getClass())) {
407
                    logger.debug("found initialised cdm entity '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
408

    
409
                    cdmEntityInSubGraph  = (CdmBase)o;
410
                    CdmBase cachedCdmEntityInSubGraph = cdmCacher.getFromCache(cdmEntityInSubGraph);
411

    
412
                    if(cachedCdmEntityInSubGraph != null) {
413
                        if(cachedCdmEntityInSubGraph != cdmEntityInSubGraph) {
414
                            // exception : is the case where
415
                            // the field has been already initialised, cached and
416
                            // is not the same as the one in the cache, in which case we set the value
417
                            // of the field to the one found in the cache
418
                            logger.debug("setting cached + real value to '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
419
                            field.set(cachedCdmEntity, cachedCdmEntityInSubGraph);
420
                            field.set(cdmEntity, cachedCdmEntityInSubGraph);
421
                        } else {
422
                            // since the field value object in cdmEntity
423
                            // is the same as the field value object in cachedCdmEntity
424
                            // we are sure that the its subgraph is also correctly loaded,
425
                            // so we can exit the recursion
426
                            return null;
427
                        }
428
                    }
429
                } else if(o instanceof Map && !checkForIdenticalCdmEntity(alreadyVisitedEntities, o)) {
430
                    loadRecursive((Map)o, alreadyVisitedEntities, update);
431
                } else if(o instanceof Collection && !checkForIdenticalCdmEntity(alreadyVisitedEntities, o)) {
432
                    loadRecursive((Collection)o, alreadyVisitedEntities, update);
433
                }
434
            }
435
            // we return the original cdm entity in the sub graph because we
436
            // want to continue to recurse on the input cdm entity graph
437
            // and not the one in the cache
438
            return cdmEntityInSubGraph;
439
        } catch (SecurityException e) {
440
            throw new CdmClientCacheException(e);
441
        } catch (IllegalArgumentException e) {
442
            throw new CdmClientCacheException(e);
443
        } catch (IllegalAccessException e) {
444
            throw new CdmClientCacheException(e);
445
        }
446
    }
447

    
448
    private boolean checkForIdenticalCdmEntity(List<Object> objList, Object objToCompare) {
449
        if(objToCompare != null) {
450
            for(Object obj : objList) {
451
                if(obj == objToCompare) {
452
                    return true;
453
                }
454
            }
455
        }
456
        return false;
457
    }
458

    
459

    
460
    public static boolean isRecursiveEnabled() {
461
        return isRecursiveEnabled;
462
    }
463

    
464
    public static void  setRecursiveEnabled(boolean ire) {
465
        isRecursiveEnabled = ire;
466
    }
467
}
(1-1/11)