Project

General

Profile

Download (15.9 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.model.ICdmCacher;
26
import eu.etaxonomy.cdm.model.common.CdmBase;
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
        }
72

    
73
        return obj;
74
    }
75

    
76
    @SuppressWarnings("unchecked")
77
    private <T extends Object> T loadRecursive(T obj, List<Object> alreadyVisitedEntities, boolean update) {
78
        if(obj == null) {
79
            return null;
80
        }
81
        if(obj instanceof CdmBase) {
82
            return (T) loadRecursive((CdmBase)obj, alreadyVisitedEntities, update);
83
        } else if (obj instanceof Map) {
84
            return (T) load((Map<T,T>)obj, alreadyVisitedEntities, update);
85
        } else if (obj instanceof Collection) {
86
            return (T) load((Collection<T>)obj, alreadyVisitedEntities, update);
87
        }
88

    
89

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

    
92
        return obj;
93
    }
94

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

    
97

    
98
        if(isRecursiveEnabled && recursive) {
99
            logger.info("---- starting recursive load for cdm entity map");
100
            List<Object> alreadyVisitedEntities = new ArrayList<Object>();
101
            Map<T,T> cachedMap = load(map, alreadyVisitedEntities, update);
102
            alreadyVisitedEntities.clear();
103
            logger.info("---- ending recursive load for cdm entity map \n");
104
            return cachedMap;
105
        } else {
106
            return load(map, null, update);
107
        }
108
    }
109

    
110

    
111
    private <T extends Object> Map<T,T> load(Map<T,T> map, List<Object> alreadyVisitedEntities, boolean update){
112
        //map = (Map<T,T>)deproxy(map);
113

    
114
        if(map == null || map.isEmpty()) {
115
            return map;
116
        }
117

    
118
        int originalMapSize = map.size();
119
        Object[] result = new Object[ map.size() * 2 ];
120
        Iterator<Map.Entry<T,T>> iter = map.entrySet().iterator();
121
        int i=0;
122
        // to avoid ConcurrentModificationException
123
        alreadyVisitedEntities.add(map);
124
        while ( iter.hasNext() ) {
125
            Map.Entry<T,T> e = iter.next();
126
            result[i++] = e.getKey();
127
            result[i++] = e.getValue();
128
        }
129

    
130
        for(i=0; i<result.length;i++) {
131
            if(alreadyVisitedEntities == null) {
132
                result[i] = load(result[i], false, update);
133
            } else {
134
                result[i] = loadRecursive(result[i], alreadyVisitedEntities, update);
135
            }
136
        }
137
        map.clear();
138
        for(i = 0; i < originalMapSize; i+=2 ) {
139
            map.put(
140
                    (T)result[i],
141
                    (T)result[i+1]
142
                    );
143
        }
144
        return map;
145
    }
146

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

    
149
        Collection<T> loadedCollection;
150
        if(isRecursiveEnabled && recursive) {
151
            logger.info("---- starting recursive load for cdm entity collection");
152
            List<Object> alreadyVisitedEntities = new ArrayList<Object>();
153
            Collection<T> cachedCollection = load(collection, alreadyVisitedEntities, update);
154
            alreadyVisitedEntities.clear();
155
            logger.info("---- ending recursive load for cdm entity collection \n");
156
            loadedCollection = cachedCollection;
157
        } else {
158
            loadedCollection = load(collection, null, update);
159
        }
160
        return loadedCollection;
161
    }
162

    
163
    @SuppressWarnings("unchecked")
164
    private <T extends Object> Collection<T> load(Collection<T> collection, List<Object> alreadyVisitedEntities, boolean update) {
165

    
166

    
167

    
168
        if(collection == null || collection.isEmpty()) {
169
            return collection;
170
        }
171
        int length = collection.size();
172
        Object[] result = new Object[length];
173
        Iterator<T> collectionItr = collection.iterator();
174
        int count = 0;
175
        // to avoid ConcurrentModificationException
176
        alreadyVisitedEntities.add(collection);
177
        while(collectionItr.hasNext()) {
178
            Object obj = collectionItr.next();
179
            if(alreadyVisitedEntities == null) {
180
                result[count] = load(obj, false, update);
181
            } else {
182
                result[count] = loadRecursive(obj, alreadyVisitedEntities, update);
183
            }
184

    
185
            count++;
186
        }
187

    
188
        collection.clear();
189

    
190
        for ( int i = 0; i < length; i++ ) {
191
            collection.add((T)result[i]);
192
        }
193

    
194
        return collection;
195
    }
196

    
197

    
198
    /**
199
     * Puts the (Key,Value) pair of ({@link java.util.UUID}, {@link eu.etaxonomy.cdm.model.common.CdmBase}),
200
     * in the cache corresponding to the given cache id
201
     *
202
     * @param cacheId
203
     * @param uuid
204
     * @param cdmEntity
205
     */
206
    public CdmBase load(CdmBase cdmEntity, boolean recursive, boolean update) {
207
        if(cdmEntity == null) {
208
            return null;
209
        }
210

    
211
        // start by looking up the cdm entity in the cache
212
        CdmBase cachedCdmEntity = cdmCacher.getFromCache(cdmEntity);
213

    
214
        if(cachedCdmEntity != null) {
215
            // if cdm entity was found in cache then
216
            logger.info(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " already exists");
217
            // .. return if the cached and input objects are identical, else (this is a newly loaded object so) continue
218
            if(cachedCdmEntity == cdmEntity) {
219
                return cachedCdmEntity;
220
            }
221
        }
222

    
223
        CdmBase loadedCdmBase;
224
        if(isRecursiveEnabled && recursive) {
225
            logger.info("---- starting recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
226
            List<Object> alreadyVisitedEntities = new ArrayList<Object>();
227
            CdmBase cb =  loadRecursive(cdmEntity, alreadyVisitedEntities, update);
228
            alreadyVisitedEntities.clear();
229
            logger.info("---- ending recursive load for cdm entity " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + "\n");
230
            loadedCdmBase =  cb;
231
        } else {
232
            loadedCdmBase = load(cdmEntity);
233
        }
234
        return loadedCdmBase;
235

    
236
    }
237

    
238

    
239
    protected CdmBase load(CdmBase cdmEntity) {
240
        logger.info("loading object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
241
        cdmCacher.put((CdmBase)ProxyUtils.deproxy(cdmEntity));
242
        return cdmCacher.getFromCache(cdmEntity);
243
    }
244

    
245

    
246
    private CdmBase loadRecursive(CdmBase cdmEntity,  List<Object> alreadyVisitedEntities, boolean update) {
247

    
248
        CdmBase cachedCdmEntity = load(cdmEntity);
249

    
250

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

    
254
        // start by getting the fields from the cdm entity
255
        String className = cdmEntity.getClass().getName();
256
        CdmModelFieldPropertyFromClass cmgmfc = getFromCdmlibModelCache(className);
257
        if(cmgmfc != null) {
258
            alreadyVisitedEntities.add(cdmEntity);
259
            List<String> fields = cmgmfc.getFields();
260
            for(String field : fields) {
261
                // retrieve the actual object corresponding to the field.
262
                // this object will be either a CdmBase or a Collection / Map
263
                // with CdmBase as the generic type
264

    
265
                CdmBase cdmEntityInSubGraph = getCdmBaseTypeFieldValue(cdmEntity, cachedCdmEntity, field, alreadyVisitedEntities, update);
266
                if(cdmEntityInSubGraph != null) {
267
                    //checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph);
268
                    if(!checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph)) {
269
                        logger.info("recursive loading object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
270
                        loadRecursive(cdmEntityInSubGraph, alreadyVisitedEntities, update);
271
                    } else {
272
                        logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
273
                    }
274
                }
275
            }
276
        } else {
277
            throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
278
                    "The cache may be corrupted or not in sync with the latest model version" );
279
        }
280

    
281
        return cachedCdmEntity;
282
    }
283

    
284

    
285
    private CdmBase getCdmBaseTypeFieldValue(CdmBase cdmEntity,
286
            CdmBase cachedCdmEntity,
287
            String fieldName,
288
            List<Object> alreadyVisitedEntities,
289
            boolean update) {
290

    
291
        // this method attempts to make sure that for any two objects found in
292
        // the object graph, if they are equal then they should also be the same,
293
        // which is crucial for the merge to work
294
        if(cachedCdmEntity == null) {
295
            throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
296
        }
297

    
298
        Class<?> clazz = cdmEntity.getClass();
299
        try {
300
            // this call will search in the provided class as well as
301
            // the super classes until it finds the field
302
            Field field = ReflectionUtils.findField(clazz, fieldName);
303

    
304
            if(field == null) {
305
                throw new CdmClientCacheException("Field '" + fieldName
306
                        + "' not found when searching in class '" + clazz.getName() + "' and its supercalsses");
307
            }
308
            field.setAccessible(true);
309
            Object o = field.get(cdmEntity);
310
            // resetting the value in cdm entity to the deproxied object
311
            o = ProxyUtils.deproxy(o);
312
            field.set(cdmEntity, o);
313

    
314
            CdmBase cdmEntityInSubGraph = null;
315

    
316
            if(update) {
317
                // if we are in update mode we have to make the field of the cached entity
318
                // up-to-date by setting it to the value of the cdm entity being loaded
319
                // NOTE : the filed is overridden in the case of two exceptions
320
                // found below
321
                field.set(cachedCdmEntity, o);
322

    
323
            }
324

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

    
329
                    cdmEntityInSubGraph  = (CdmBase)o;
330
                    CdmBase cachedCdmEntityInSubGraph = cdmCacher.getFromCache(cdmEntityInSubGraph);
331

    
332
//                    Object oldCachedCdmEntityInSubGraph = field.get(cachedCdmEntity);
333
//                    if(ProxyUtils.isProxy(oldCachedCdmEntityInSubGraph)) {
334
//                        LazyInitializer hli =
335
//                                ((HibernateProxy)oldCachedCdmEntityInSubGraph).getHibernateLazyInitializer();
336
//
337
//                        if(cdmEntityInSubGraph.getId() == ((Integer)hli.getIdentifier()).intValue()) {
338
//                            // exception 1 : is the case where
339
//                            // the earlier value of the field in the cached entity
340
//                            // was a proxy with the same id then we don't need to
341
//                            // update it here as it will be updated on demand,
342
//                            // so we reset it to the earlier proxy
343
//                            field.set(cachedCdmEntity, oldCachedCdmEntityInSubGraph);
344
//                            return null;
345
//                        }
346
//                    }
347

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

    
384
    private boolean checkForIdenticalCdmEntity(List<Object> objList, Object objToCompare) {
385
        if(objToCompare != null) {
386
            for(Object obj : objList) {
387
                if(obj == objToCompare) {
388
                    return true;
389
                }
390
            }
391
        }
392
        return false;
393
    }
394

    
395

    
396
    public static boolean isRecursiveEnabled() {
397
        return isRecursiveEnabled;
398
    }
399

    
400
    public static void  setRecursiveEnabled(boolean ire) {
401
        isRecursiveEnabled = ire;
402
    }
403
}
(1-1/9)