Project

General

Profile

Download (14.8 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2017 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.beans.PropertyDescriptor;
12
import java.io.PrintStream;
13
import java.lang.reflect.InvocationTargetException;
14
import java.util.ArrayList;
15
import java.util.Collection;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.List;
19
import java.util.Map;
20
import java.util.Set;
21

    
22
import org.apache.commons.beanutils.BeanUtilsBean;
23
import org.apache.commons.beanutils.PropertyUtils;
24
import org.apache.commons.beanutils.PropertyUtilsBean;
25
import org.apache.commons.lang.builder.HashCodeBuilder;
26
import org.apache.log4j.Logger;
27
import org.hibernate.Hibernate;
28
import org.hibernate.collection.internal.AbstractPersistentCollection;
29
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.CollectionProxy;
30
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.MapProxy;
31
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.SortedMapProxy;
32

    
33
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
34
import eu.etaxonomy.cdm.model.common.CdmBase;
35
import eu.etaxonomy.cdm.model.common.VersionableEntity;
36
import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
37
import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
38

    
39
/**
40
 * @author a.kohlbecker
41
 * @since 08.11.2017
42
 *
43
 */
44
public class CdmEntityCache implements EntityCache {
45

    
46

    
47
    private final static Logger logger = Logger.getLogger(CdmEntityCache.class);
48

    
49
    protected static final String COPY_ENTITY = "!";
50

    
51
    protected Set<CdmBase> entities = new HashSet<>();
52

    
53
    private Map<EntityKey, CdmBase> entityyMap = new HashMap<>();
54

    
55
    private List<String> entityPathList = new ArrayList<>();
56

    
57
    private Map<EntityKey, List<String>> entityPathsMap = new HashMap<>();
58

    
59
    private Set<EntityKey> copyEntitiyKeys = new HashSet<>();
60

    
61
    private Set<Object> objectsSeen = new HashSet<>();
62

    
63
    protected CdmEntityCache(){
64

    
65
    }
66

    
67
    /**
68
     * @param entity the first entity to be cached. can be <code>null</code>.
69
     *      Further entities can be added to the cache with {@link CdmEntityCache#add(CdmBase)}
70
     */
71
    public CdmEntityCache(CdmBase entity){
72
        if(entity != null){
73
            this.entities.add(entity);
74
            update();
75
        }
76
    }
77

    
78
    @Override
79
    public boolean update() {
80

    
81
        entityPathList.clear();
82
        entityPathsMap.clear();
83
        objectsSeen.clear();
84
        copyEntitiyKeys.clear();
85

    
86
        for(CdmBase entity : entities){
87
            analyzeEntity(entity, "");
88
        }
89

    
90
        return copyEntitiyKeys.isEmpty();
91
    }
92

    
93
    /**
94
     *
95
     */
96
    protected void analyzeEntity(CdmBase bean, String propertyPath) {
97

    
98
        if(bean == null){
99
            return;
100
        }
101

    
102
        CdmBase proxyBean = bean;
103

    
104
        bean = HibernateProxyHelper.deproxy(proxyBean, CdmBase.class);
105

    
106
        EntityKey entityKey = new EntityKey(bean);
107

    
108
        propertyPath += "[" + entityKey;
109
        String flags = "";
110
        CdmBase mappedEntity = entityyMap.put(entityKey, proxyBean);
111

    
112
        if(mappedEntity != null && mappedEntity != bean) {
113
            copyEntitiyKeys.add(entityKey);
114
            flags += COPY_ENTITY + bean.hashCode();
115
        }
116

    
117
        flags = analyzeMore(bean, entityKey, flags, mappedEntity);
118

    
119
        if(!flags.isEmpty()){
120
            propertyPath += "(" + flags + ")";
121
        }
122
        propertyPath += "]";
123

    
124
        logger.debug(propertyPath);
125

    
126
        entityPathList.add(propertyPath);
127
        if(!entityPathsMap.containsKey(entityKey)){
128
            entityPathsMap.put(entityKey, new ArrayList<>());
129
        }
130
        entityPathsMap.get(entityKey).add(propertyPath);
131

    
132
        if(!objectsSeen.add(bean)){
133
            // avoid cycles, do not recurse into properties of objects that have been analyzed already
134
            return;
135
        }
136

    
137
        Set<PropertyDescriptor> properties = AbstractBeanInitializer.getProperties(bean, null);
138
        for(PropertyDescriptor prop : properties){
139

    
140
            try {
141
                Object propertyValue = PropertyUtils.getProperty(bean, prop.getName());
142

    
143
                if(propertyValue == null){
144
                    continue;
145
                }
146

    
147
                String propertyPathSuffix = "." + prop.getName();
148
                logger.debug("\t\tproperty:" + propertyPathSuffix);
149

    
150
                if(Hibernate.isInitialized(propertyValue)) {
151

    
152
                    if(CdmBase.class.isAssignableFrom(prop.getPropertyType())
153
                            || INomenclaturalReference.class.equals(prop.getPropertyType())
154
                            ){
155
                        analyzeEntity(HibernateProxyHelper.deproxy(propertyValue, CdmBase.class), propertyPath + propertyPathSuffix);
156
                        continue;
157
                    }
158

    
159
                    Collection<CdmBase> collection = null;
160
                    if(propertyValue instanceof AbstractPersistentCollection){
161
                        if (propertyValue  instanceof Collection) {
162
                            collection = (Collection<CdmBase>) propertyValue;
163
                        } else if (propertyValue instanceof Map) {
164
                            collection = ((Map<?,CdmBase>)propertyValue).values();
165
                        } else {
166
                            logger.error("unhandled subtype of AbstractPersistentCollection");
167
                        }
168
                    } else if (propertyValue instanceof CollectionProxy
169
                                || propertyValue instanceof MapProxy<?, ?>
170
                                || propertyValue instanceof SortedMapProxy<?, ?>){
171
                            //hibernate envers collections
172
                            // FIXME this won't work!!!!
173
                            collection = (Collection<CdmBase>)propertyValue;
174
                    }
175

    
176
                    if(collection != null){
177
                        for(CdmBase collectionItem : collection){
178
                            analyzeEntity(HibernateProxyHelper.deproxy(collectionItem, CdmBase.class), propertyPath + propertyPathSuffix);
179
                        }
180
                    } else {
181
                        // logger.error("Unhandled property type " + propertyValue.getClass().getName());
182
                    }
183
                }
184

    
185
            } catch (IllegalAccessException e) {
186
                String message = "Illegal access on property " + prop;
187
                logger.error(message);
188
                throw new RuntimeException(message, e);
189
            } catch (InvocationTargetException e) {
190
                String message = "Cannot invoke property " + prop + " not found";
191
                logger.error(message);
192
                throw new RuntimeException(message, e);
193
            } catch (NoSuchMethodException e) {
194
                String message = "Property " + prop.getName() + " not found for class " + bean.getClass();
195
                logger.error(message);
196
            }
197

    
198
        }
199
    }
200

    
201
    /**
202
     * Empty method which can be implemented by subclasses which do further analysis.
203
     *
204
     * @param bean
205
     * @param entityKey
206
     * @param flags
207
     * @param mappedEntity
208
     * @return
209
     */
210
    protected String analyzeMore(CdmBase bean, EntityKey entityKey, String flags, CdmBase mappedEntity) {
211
        return flags;
212
    }
213

    
214

    
215
    public void printEntityGraph(PrintStream printStream){
216
        printLegend(printStream);
217
        for(String path : entityPathList) {
218
            printStream.println(path);
219
        }
220
    }
221

    
222
    public void printCopyEntities(PrintStream printStream){
223
        printStream.println("-------------- Copy Entities --------------");
224
        printLegend(printStream);
225
        for(EntityKey key : copyEntitiyKeys){
226
            for(String path : entityPathsMap.get(key)) {
227
                printStream.println(path);
228
            }
229
        }
230
    }
231

    
232
    /**
233
     * @param printStream
234
     */
235
    protected void printLegend(PrintStream printStream) {
236
        printStream.println(this.getClass().getSimpleName() + " legend: ");
237
        printStream.println("    - '!{objectHash}': detected copy entity, followed by object hash");
238
    }
239

    
240
    public class EntityKey {
241

    
242
        Class type;
243
        int id;
244

    
245
        public EntityKey(Class type, int id){
246
            this.type = type;
247
            this.id = id;
248
        }
249

    
250
        public EntityKey(CdmBase entity){
251
            type = entity.getClass();
252
            id = entity.getId();
253
        }
254

    
255
        /**
256
         * @return the type
257
         */
258
        public Class getType() {
259
            return type;
260
        }
261

    
262
        /**
263
         * @return the id
264
         */
265
        public int getId() {
266
            return id;
267
        }
268

    
269
        /**
270
         * {@inheritDoc}
271
         */
272
        @Override
273
        public int hashCode() {
274
            return new HashCodeBuilder(15, 33)
275
                    .append(type)
276
                    .append(id)
277
                    .toHashCode();
278
        }
279

    
280
        @Override
281
        public boolean equals(Object obj) {
282
            EntityKey other = (EntityKey)obj;
283
            return this.id == other.id && this.type == other.type;
284

    
285
        }
286

    
287
        @Override
288
        public String toString() {
289
            return type.getSimpleName() + "#" + getId();
290
        }
291

    
292
    }
293

    
294
    /**
295
     *
296
     * @return the entities in this cache
297
     */
298
    public Set<CdmBase> getEntities(){
299
        return entities;
300
    }
301

    
302
    /**
303
     * {@inheritDoc}
304
     * <p>
305
     * In case the cached bean is a HibernateProxy it will be unproxied
306
     * before returning it.
307
     */
308
    @Override
309
    public <CDM extends CdmBase> CDM find(CDM value) {
310
        if(value != null){
311
            EntityKey entityKey = new EntityKey(HibernateProxyHelper.deproxy(value));
312
            CDM cachedBean = (CDM) entityyMap.get(entityKey);
313
            if(cachedBean != null){
314
                return (CDM) HibernateProxyHelper.deproxy(cachedBean, CdmBase.class);
315
            }
316
        }
317
        return null;
318
    }
319

    
320
    private <CDM extends CdmBase> CDM findProxy(CDM value) {
321
        if(value != null){
322
            EntityKey entityKey = new EntityKey(HibernateProxyHelper.deproxy(value));
323
            return (CDM) entityyMap.get(entityKey);
324
        }
325
        return null;
326
    }
327

    
328
    /**
329
     * {@inheritDoc}
330
     * <p>
331
     * In case the cached bean is a HibernateProxy it will be unproxied
332
     * before returning it.
333
     */
334
    @Override
335
    public <CDM extends CdmBase> CDM find(Class<CDM> type, int id) {
336
        EntityKey entityKey = new EntityKey(type, id);
337
        CDM cachedBean = (CDM) entityyMap.get(entityKey);
338
        if(cachedBean != null){
339
            return (CDM) HibernateProxyHelper.deproxy(cachedBean, CdmBase.class);
340
        }
341
        return null;
342
    }
343

    
344
    @Override
345
    public <CDM extends CdmBase> CDM findAndUpdate(CDM value){
346
        CDM cachedBean = findProxy(value);
347
        if(cachedBean != null && VersionableEntity.class.isAssignableFrom(cachedBean.getClass())){
348
            updatedCachedIfEarlier((VersionableEntity)cachedBean, (VersionableEntity)value);
349
        }
350
        if(cachedBean != null){
351
            return (CDM) HibernateProxyHelper.deproxy(cachedBean, CdmBase.class);
352
        }
353
        return null;
354

    
355
    }
356

    
357
    /**
358
     * @param cachedValue
359
     * @param value
360
     */
361
    private <CDM extends VersionableEntity> void updatedCachedIfEarlier(CDM cachedValue, CDM value) {
362
       if(cachedValue != null && value != null && value.getUpdated() != null){
363
           if(cachedValue.getUpdated() == null || value.getUpdated().isAfter(cachedValue.getUpdated())){
364
               try {
365
                copyProperties(cachedValue, value);
366
            } catch (IllegalAccessException e) {
367
                /* should never happen */
368
                e.printStackTrace();
369
            } catch (InvocationTargetException e) {
370
                /* critical! re-throw as runtime exception, which is ok in the context of a vaadin app */
371
                throw new RuntimeException(e);
372
            }
373
           }
374
       }
375

    
376
    }
377

    
378
    /**
379
     * partially copy of {@link BeanUtilsBean#copyProperties(Object, Object)}
380
     */
381
    private <CDM extends VersionableEntity> void copyProperties(CDM dest, CDM orig) throws IllegalAccessException, InvocationTargetException {
382

    
383
        PropertyUtilsBean propertyUtils = BeanUtilsBean.getInstance().getPropertyUtils();
384
        PropertyDescriptor[] origDescriptors =
385
                propertyUtils.getPropertyDescriptors(orig);
386
            for (int i = 0; i < origDescriptors.length; i++) {
387
                String name = origDescriptors[i].getName();
388
                if ("class".equals(name)) {
389
                    continue; // No point in trying to set an object's class
390
                }
391
                if (propertyUtils.isReadable(orig, name) &&
392
                        propertyUtils.isWriteable(dest, name)) {
393
                    try {
394
                        if(CdmBase.class.isAssignableFrom(propertyUtils.getPropertyType(dest, name))){
395
                            CdmBase origValue = (CdmBase)propertyUtils.getSimpleProperty(orig, name);
396
                            if(!Hibernate.isInitialized(origValue)){
397
                                // ignore uninitialized entities
398
                                continue;
399
                            }
400
                            // only copy entities if origValue either is a completely different entity (A)
401
                            // or if origValue is updated (B).
402
                            CdmBase destValue = (CdmBase)propertyUtils.getSimpleProperty(dest, name);
403
                            if(destValue == null && origValue == null){
404
                                continue;
405
                            }
406
                            if(
407
                                    origValue != null && destValue == null ||
408
                                    origValue == null && destValue != null ||
409

    
410
                                    origValue.getId() != destValue.getId() ||
411
                                    origValue.getClass() != destValue.getClass() ||
412
                                    origValue.getUuid() != destValue.getUuid()){
413
                                // (A)
414
                                BeanUtilsBean.getInstance().copyProperty(dest, name, origValue);
415

    
416
                            } else {
417
                                // (B) recurse into findAndUpdate
418
                                findAndUpdate(origValue);
419
                            }
420
                        } else {
421
                                Object value = propertyUtils.getSimpleProperty(orig, name);
422
                                BeanUtilsBean.getInstance().copyProperty(dest, name, value);
423
                        }
424

    
425
                    } catch (NoSuchMethodException e) {
426
                        // Should not happen
427
                    }
428
                }
429
            }
430

    
431
    }
432

    
433
    /**
434
     * {@inheritDoc}
435
     */
436
    @Override
437
    public <CDM extends CdmBase> void add(CDM value) {
438
        entities.add(value);
439
        analyzeEntity(value, "");
440
    }
441

    
442

    
443
}
(1-1/2)