Project

General

Profile

Download (15.5 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.Collection;
14
import java.util.HashSet;
15
import java.util.Iterator;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.Set;
19

    
20
import javassist.util.proxy.ProxyFactory;
21
import net.sf.ehcache.Cache;
22
import net.sf.ehcache.Element;
23

    
24
import org.apache.log4j.Logger;
25
import org.hibernate.collection.spi.PersistentCollection;
26
import org.hibernate.proxy.HibernateProxy;
27
import org.hibernate.proxy.LazyInitializer;
28
import org.springframework.util.ReflectionUtils;
29

    
30
import eu.etaxonomy.cdm.model.ICdmCacher;
31
import eu.etaxonomy.cdm.model.common.CdmBase;
32

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

    
41
    private static boolean isRecursiveEnabled = true;
42

    
43
    private final ICdmCacher cdmCacher;
44

    
45
    private final Cache cdmlibModelCache;
46

    
47

    
48
    public CacheLoader(ICdmCacher cdmCacher) {
49
        this.cdmCacher = cdmCacher;
50
        this.cdmlibModelCache = CdmRemoteCacheManager.getInstance().getCdmModelGetMethodsCache();
51
    }
52

    
53

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

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

    
76
        return obj;
77
    }
78

    
79
    @SuppressWarnings("unchecked")
80
    private <T extends Object> T loadRecursive(T obj, Set<CdmBase> alreadyVisitedEntities) {
81
        if(obj == null) {
82
            return null;
83
        }
84
        if(obj instanceof CdmBase) {
85
            return (T) loadRecursive((CdmBase)obj, alreadyVisitedEntities);
86
        } else if (obj instanceof Map) {
87
            return (T) load((Map<T,T>)obj, alreadyVisitedEntities);
88
        } else if (obj instanceof Collection) {
89
            return (T) load((Collection<T>)obj, alreadyVisitedEntities);
90
        }
91

    
92

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

    
95
        return obj;
96
    }
97

    
98
    public <T extends Object> Map<T,T> load(Map<T,T> map, boolean recursive){
99
        if(map == null) {
100
            return null;
101
        }
102

    
103
        if(isRecursiveEnabled && recursive) {
104
            logger.info("---- starting recursive load for cdm entity map");
105
            Set<CdmBase> alreadyVisitedEntities = new HashSet<CdmBase>();
106
            Map<T,T> cachedMap = load(map, alreadyVisitedEntities);
107
            alreadyVisitedEntities.clear();
108
            logger.info("---- ending recursive load for cdm entity map \n");
109
            return cachedMap;
110
        } else {
111
            return load(map, null);
112
        }
113
    }
114

    
115

    
116
    private <T extends Object> Map<T,T> load(Map<T,T> map, Set<CdmBase> alreadyVisitedEntities){
117
        if(map == null || map.isEmpty()) {
118
            return map;
119
        }
120

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

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

    
148
    public <T extends Object> Collection<T> load(Collection<T> collection, boolean recursive){
149
        if(collection == null) {
150
            return null;
151
        }
152

    
153
        Collection<T> loadedCollection;
154
        if(isRecursiveEnabled && recursive) {
155
            logger.info("---- starting recursive load for cdm entity collection");
156
            Set<CdmBase> alreadyVisitedEntities = new HashSet<CdmBase>();
157
            Collection<T> cachedCollection = load(collection, alreadyVisitedEntities);
158
            alreadyVisitedEntities.clear();
159
            logger.info("---- ending recursive load for cdm entity collection \n");
160
            loadedCollection = cachedCollection;
161
        } else {
162
            loadedCollection = load(collection, null);
163
        }
164
        return loadedCollection;
165
    }
166

    
167
    @SuppressWarnings("unchecked")
168
    private <T extends Object> Collection<T> load(Collection<T> collection, Set<CdmBase> alreadyVisitedEntities){
169
        int length = collection.size();
170
        Object[] result = new Object[length];
171
        Iterator<T> collectionItr = collection.iterator();
172
        int count = 0;
173
        while(collectionItr.hasNext()) {
174
            Object obj = collectionItr.next();
175
            if(alreadyVisitedEntities == null) {
176
                result[count] = load(obj, false);
177
            } else {
178
                result[count] = loadRecursive(obj, alreadyVisitedEntities);
179
            }
180

    
181
            count++;
182
        }
183

    
184
        collection.clear();
185

    
186
        for ( int i = 0; i < length; i++ ) {
187
            collection.add((T)result[i]);
188
        }
189

    
190
        return collection;
191
    }
192

    
193

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

    
207

    
208
        // start by looking up the cdm entity in the cache
209
        CdmBase cachedCdmEntity = cdmCacher.getFromCache(cdmEntity);
210

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

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

    
234
    }
235

    
236

    
237
    private CdmBase load(CdmBase cdmEntity) {
238
        logger.info("loading object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId());
239

    
240
        // start by looking up the cdm entity in the cache
241
        CdmBase cachedCdmEntity = cdmCacher.getFromCache(cdmEntity);
242

    
243
        if(cachedCdmEntity != null) {
244
            // if cdm entity was found in cache then return ...
245
            logger.info(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " already exists");
246
            return cachedCdmEntity;
247
        } else {
248
            // ... else save the entity in the cache
249
            cdmCacher.put(cdmEntity);
250
            logger.info(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " put in cache");
251
            return cdmEntity;
252
        }
253
    }
254

    
255
    private CdmBase loadRecursive(CdmBase cdmEntity,  Set<CdmBase> alreadyVisitedEntities) {
256

    
257
        CdmBase cachedCdmEntity = load(cdmEntity);
258

    
259

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

    
263
        // start by getting the fields from the cdm entity
264
        String className = cdmEntity.getClass().getName();
265
        CdmModelFieldPropertyFromClass cmgmfc = getFromCdmlibModelCache(className);
266
        if(cmgmfc != null) {
267
            alreadyVisitedEntities.add(cdmEntity);
268
            List<String> fields = cmgmfc.getFields();
269
            for(String field : fields) {
270
                // retrieve the actual object corresponding to the field.
271
                // this object will be either a CdmBase or a Collection / Map
272
                // with CdmBase as the generic type
273

    
274
                CdmBase cdmEntityInSubGraph = getCdmBaseTypeFieldValue(cdmEntity, cachedCdmEntity, field, alreadyVisitedEntities);
275
                if(cdmEntityInSubGraph != null) {
276
                    //checkForIdenticalCdmEntity(alreadyVisitedEntities, cdmEntityInSubGraph);
277
                    if(!alreadyVisitedEntities.contains(cdmEntityInSubGraph)) {
278
                        logger.info("recursive loading object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId());
279
                        loadRecursive(cdmEntityInSubGraph, alreadyVisitedEntities);
280
                    } else {
281
                        logger.info("object of type " + cdmEntityInSubGraph.getClass().getName() + " with id " + cdmEntityInSubGraph.getId() + " already visited");
282
                    }
283

    
284
                }
285
            }
286
        } else {
287
            throw new CdmClientCacheException("CdmEntity with class " + cdmEntity.getClass().getName() + " is not found in the cdmlib model cache. " +
288
                    "The cache may be corrupted or not in sync with the latest model version" );
289
        }
290
        return cachedCdmEntity;
291
    }
292

    
293

    
294
    private CdmBase getCdmBaseTypeFieldValue(CdmBase cdmEntity,
295
            CdmBase cachedCdmEntity,
296
            String fieldName,
297
            Set<CdmBase> alreadyVisitedEntities) {
298

    
299
        // this method attempts to make sure that for any two objects found in
300
        // the object graph, if they are equal then they should also be the same,
301
        // which is crucial for the merge to work
302
        if(cachedCdmEntity == null) {
303
            throw new CdmClientCacheException("When trying to set field value, the cached cdm entity cannot be null");
304
        }
305

    
306
        Class<?> clazz = cdmEntity.getClass();
307
        try {
308
            // this call will search in the provided class as well as
309
            // the super classes until it finds the field
310
            Field field = ReflectionUtils.findField(clazz, fieldName);
311

    
312
            if(field == null) {
313
                throw new CdmClientCacheException("Field '" + fieldName
314
                        + "' not found when searching in class '" + clazz.getName() + "' and its supercalsses");
315
            }
316
            field.setAccessible(true);
317
            Object o = field.get(cdmEntity);
318

    
319
            if(o != null && o instanceof HibernateProxy) {
320
                LazyInitializer hli = ((HibernateProxy)o).getHibernateLazyInitializer();
321
                if(!hli.isUninitialized()) {
322
                    o = ((HibernateProxy) o).getHibernateLazyInitializer().getImplementation();
323
                    field.set(cdmEntity, o);
324
                }
325
            }
326

    
327
            if(o != null && o instanceof PersistentCollection) {
328
                PersistentCollection pc = ((PersistentCollection)o);
329
                if(pc.wasInitialized()) {
330
                    o = ProxyUtils.getObject(pc);
331
                    field.set(cdmEntity, o);
332
                }
333
            }
334

    
335

    
336
            CdmBase cdmEntityInSubGraph = null;
337
            if(o != null
338
                    && !ProxyFactory.isProxyClass(o.getClass())
339
                    && !(o instanceof PersistentCollection) ) {
340

    
341
                if(CdmBase.class.isAssignableFrom(o.getClass())) {
342
                    logger.info("found initialised cdm entity '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
343
                    cdmEntityInSubGraph  = (CdmBase)o;
344

    
345

    
346
                    CdmBase cachedCdmEntityInSubGraph = cdmCacher.getFromCache(cdmEntityInSubGraph);
347
                    // making sure that the field in cached cdm entity is always
348
                    // up-to-date by setting to the value of the cdm entity being loaded
349
                    // the only execption to this is found below
350
                    field.set(cachedCdmEntity, o);
351

    
352

    
353
                    // the only exception to updating the field to the latest value
354
                    // is the case where the field has been already initialised, cached and
355
                    // is not the same as the one in the cache, in which case we set the value
356
                    // of the field to the one found in the cache
357
                    if(cachedCdmEntityInSubGraph != null) {
358
                        if(cachedCdmEntityInSubGraph != cdmEntityInSubGraph) {
359
                            logger.info("setting cached + real value to '" + fieldName + "' in object of type " + clazz.getName() + " with id " + cdmEntity.getId());
360
                            field.set(cachedCdmEntity, cachedCdmEntityInSubGraph);
361
                            field.set(cdmEntity, cachedCdmEntityInSubGraph);
362
                        }
363
                        cdmEntityInSubGraph = null;
364
                    }
365

    
366
                } else if(o instanceof Map) {
367
                    loadRecursive((Map)o, alreadyVisitedEntities);
368
                } else if(o instanceof Collection) {
369
                    loadRecursive((Collection)o, alreadyVisitedEntities);
370
                }
371
            }
372
            // we return the original cdm entity in the sub graph because we
373
            // want to continue to recurse on the input cdm entity graph
374
            // and not the one in the cache
375
            return cdmEntityInSubGraph;
376
        } catch (SecurityException e) {
377
            throw new CdmClientCacheException(e);
378
        } catch (IllegalArgumentException e) {
379
            throw new CdmClientCacheException(e);
380
        } catch (IllegalAccessException e) {
381
            throw new CdmClientCacheException(e);
382
        }
383
    }
384

    
385
    private boolean checkForIdenticalCdmEntity(Set<CdmBase> cbSet, CdmBase cbToCompare) {
386
        if(cbToCompare != null) {
387
            for(CdmBase cb : cbSet) {
388

    
389
                if(cb == cbToCompare) {
390
                    return true;
391
                } else {
392
                    if(cb.equals(cbToCompare)) {
393
                        logger.info("equal but non identical object found of type " + cbToCompare.getUserFriendlyTypeName() + " with id " + cbToCompare.getId());
394
                    }
395
                }
396
            }
397
        }
398
        return false;
399
    }
400

    
401
    public static boolean isRecursiveEnabled() {
402
        return isRecursiveEnabled;
403
    }
404

    
405
    public static void  setRecursiveEnabled(boolean ire) {
406
        isRecursiveEnabled = ire;
407
    }
408
}
(1-1/11)