Project

General

Profile

Download (16.3 KB) Statistics
| Branch: | Tag: | Revision:
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
import eu.etaxonomy.cdm.persistence.dto.MergeResult;
29

    
30
/**
31
 * @author cmathew
32
 * @date 19 Feb 2015
33
 *
34
 */
35
public class CacheLoader {
36
    private static final Logger logger = Logger.getLogger(CacheLoader.class);
37

    
38
    private static boolean isRecursiveEnabled = true;
39

    
40
    protected final ICdmCacher cdmCacher;
41

    
42
    private final Cache cdmlibModelCache;
43

    
44

    
45

    
46
    public CacheLoader(ICdmCacher cdmCacher) {
47
        this.cdmCacher = cdmCacher;
48
        this.cdmlibModelCache = CdmRemoteCacheManager.getInstance().getCdmModelGetMethodsCache();
49

    
50
    }
51

    
52

    
53
    public CdmModelFieldPropertyFromClass getFromCdmlibModelCache(String className) {
54
        Element e = cdmlibModelCache.get(className);
55
        if (e == null) {
56
            return null;
57
        } else {
58
            return (CdmModelFieldPropertyFromClass) e.getObjectValue();
59
        }
60
    }
61

    
62
    @SuppressWarnings("unchecked")
63
    public <T extends Object> T load(T obj, boolean recursive, boolean update) {
64
        if(obj == null) {
65
            return null;
66
        }
67
        if(obj instanceof CdmBase) {
68
            return (T) load((CdmBase)obj, recursive, update);
69
        } else if (obj instanceof Map) {
70
            return (T) load((Map<T,T>)obj, recursive, update);
71
        } else if (obj instanceof Collection) {
72
            return (T) load((Collection<T>)obj, recursive, update);
73
        } else if(obj instanceof Pager) {
74
        	load(((Pager)obj).getRecords(), recursive, update);
75
            return obj;
76
        } else if(obj instanceof MergeResult) {
77
            return (T) load((MergeResult<CdmBase>)obj, recursive, update);
78
        }
79

    
80
        return obj;
81
    }
82

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

    
98

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

    
101
        return obj;
102
    }
103

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

    
106

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

    
119

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

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

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

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

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

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

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

    
175

    
176

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

    
194
            count++;
195
        }
196

    
197
        collection.clear();
198

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

    
203
        return collection;
204
    }
205

    
206

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

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

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

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

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

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

    
257
    }
258

    
259

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

    
266

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

    
269
        CdmBase cachedCdmEntity = load(cdmEntity);
270

    
271

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

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

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

    
302
        return cachedCdmEntity;
303
    }
304

    
305

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

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

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

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

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

    
348
            }
349

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

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

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

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

    
404

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

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