Project

General

Profile

Download (16.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.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 net.sf.ehcache.Cache;
19
import net.sf.ehcache.Element;
20

    
21
import org.apache.log4j.Logger;
22
import org.springframework.util.ReflectionUtils;
23

    
24
import eu.etaxonomy.cdm.api.service.pager.Pager;
25
import eu.etaxonomy.cdm.model.ICdmCacher;
26
import eu.etaxonomy.cdm.model.common.CdmBase;
27
import eu.etaxonomy.cdm.persistence.dto.MergeResult;
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
        } else if(obj instanceof MergeResult) {
76
            return (T) load((MergeResult<CdmBase>)obj, recursive, update);
77
        }
78

    
79
        return obj;
80
    }
81

    
82
    @SuppressWarnings("unchecked")
83
    private <T extends Object> T loadRecursive(T obj, List<Object> alreadyVisitedEntities, boolean update) {
84
        if(obj == null) {
85
            return null;
86
        }
87
        if(obj instanceof CdmBase) {
88
            return (T) loadRecursive((CdmBase)obj, alreadyVisitedEntities, update);
89
        } else if (obj instanceof Map) {
90
            return (T) load((Map<T,T>)obj, alreadyVisitedEntities, update);
91
        } else if (obj instanceof Collection) {
92
            return (T) load((Collection<T>)obj, alreadyVisitedEntities, update);
93
        } else if (obj instanceof MergeResult) {
94
            return (T) loadRecursive((MergeResult)obj, alreadyVisitedEntities, update);
95
        }
96

    
97

    
98
        logger.info("No caching yet for type " + obj.getClass().getName());
99

    
100
        return obj;
101
    }
102

    
103
    public <T extends Object> Map<T,T> load(Map<T,T> map, boolean recursive, boolean update){
104

    
105

    
106
        if(isRecursiveEnabled && recursive) {
107
            logger.info("---- starting recursive load for cdm entity map");
108
            List<Object> alreadyVisitedEntities = new ArrayList<Object>();
109
            Map<T,T> cachedMap = load(map, alreadyVisitedEntities, update);
110
            alreadyVisitedEntities.clear();
111
            logger.info("---- ending recursive load for cdm entity map \n");
112
            return cachedMap;
113
        } else {
114
            return load(map, null, update);
115
        }
116
    }
117

    
118

    
119
    private <T extends Object> Map<T,T> load(Map<T,T> map, List<Object> alreadyVisitedEntities, boolean update){
120
        //map = (Map<T,T>)deproxy(map);
121

    
122
        if(map == null || map.isEmpty()) {
123
            return map;
124
        }
125

    
126
        int originalMapSize = map.size();
127
        Object[] result = new Object[ map.size() * 2 ];
128
        Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
129
        int i=0;
130
        // to avoid ConcurrentModificationException
131
        alreadyVisitedEntities.add(map);
132
        while ( iter.hasNext() ) {
133
            Map.Entry<T,T> e = iter.next();
134
            result[i++] = e.getKey();
135
            result[i++] = e.getValue();
136
        }
137

    
138
        for(i=0; i<result.length;i++) {
139
            if(alreadyVisitedEntities == null) {
140
                result[i] = load(result[i], false, update);
141
            } else {
142
                result[i] = loadRecursive(result[i], alreadyVisitedEntities, update);
143
            }
144
        }
145
        map.clear();
146
        for(i = 0; i < originalMapSize; i+=2 ) {
147
            map.put(
148
                    (T)result[i],
149
                    (T)result[i+1]
150
                    );
151
        }
152
        return map;
153
    }
154

    
155
    public <T extends Object> Collection<T> load(Collection<T> collection,  boolean recursive, boolean update){
156

    
157
        Collection<T> loadedCollection;
158
        if(isRecursiveEnabled && recursive) {
159
            logger.info("---- starting recursive load for cdm entity collection");
160
            List<Object> alreadyVisitedEntities = new ArrayList<Object>();
161
            Collection<T> cachedCollection = load(collection, alreadyVisitedEntities, update);
162
            alreadyVisitedEntities.clear();
163
            logger.info("---- ending recursive load for cdm entity collection \n");
164
            loadedCollection = cachedCollection;
165
        } else {
166
            loadedCollection = load(collection, null, update);
167
        }
168
        return loadedCollection;
169
    }
170

    
171
    @SuppressWarnings("unchecked")
172
    private <T extends Object> Collection<T> load(Collection<T> collection, List<Object> alreadyVisitedEntities, boolean update) {
173

    
174

    
175

    
176
        if(collection == null || collection.isEmpty()) {
177
            return collection;
178
        }
179
        int length = collection.size();
180
        Object[] result = new Object[length];
181
        Iterator<T> collectionItr = collection.iterator();
182
        int count = 0;
183
        // to avoid ConcurrentModificationException
184
        alreadyVisitedEntities.add(collection);
185
        while(collectionItr.hasNext()) {
186
            Object obj = collectionItr.next();
187
            if(alreadyVisitedEntities == null) {
188
                result[count] = load(obj, false, update);
189
            } else {
190
                result[count] = loadRecursive(obj, alreadyVisitedEntities, update);
191
            }
192

    
193
            count++;
194
        }
195

    
196
        collection.clear();
197

    
198
        for ( int i = 0; i < length; i++ ) {
199
            collection.add((T)result[i]);
200
        }
201

    
202
        return collection;
203
    }
204

    
205

    
206
    public MergeResult<CdmBase> load(MergeResult<CdmBase> mergeResult, boolean recursive, boolean update) {
207
        CdmBase cdmBase = load(mergeResult.getMergedEntity(), recursive, update);
208
        load(mergeResult.getNewEntities(), recursive, update);
209
        return new MergeResult(cdmBase, mergeResult.getNewEntities());
210
    }
211

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

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

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

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

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

    
256
    }
257

    
258

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

    
265

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

    
268
        CdmBase cachedCdmEntity = load(cdmEntity);
269

    
270

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

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

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

    
301
        return cachedCdmEntity;
302
    }
303

    
304

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

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

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

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

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

    
347
            }
348

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

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

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

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

    
403

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

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