Project

General

Profile

Download (14.6 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
    private static final String COPY_ENTITY = "!";
50

    
51
    private 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
    /**
64
     * @param entity the first entity to be cached. can be <code>null</code>.
65
     *      Further entities can be added to the cache with {@link CdmEntityCache#add(CdmBase)}
66
     */
67
    public CdmEntityCache(CdmBase entity){
68
        if(entity != null){
69
            this.entities.add(entity);
70
            update();
71
        }
72
    }
73

    
74
    @Override
75
    public boolean update() {
76

    
77
        entityPathList.clear();
78
        entityPathsMap.clear();
79
        objectsSeen.clear();
80
        copyEntitiyKeys.clear();
81

    
82
        for(CdmBase entity : entities){
83
        analyzeEntity(entity, "");
84
        }
85

    
86
        return copyEntitiyKeys.isEmpty();
87
    }
88

    
89
    /**
90
     *
91
     */
92
    protected void analyzeEntity(CdmBase bean, String propertyPath) {
93

    
94
        if(bean == null){
95
            return;
96
        }
97

    
98
        CdmBase proxyBean = bean;
99

    
100
        bean = HibernateProxyHelper.deproxy(proxyBean, CdmBase.class);
101

    
102
        EntityKey entityKey = new EntityKey(bean);
103

    
104
        propertyPath += "[" + entityKey;
105
        String flags = "";
106
        CdmBase mappedEntity = entityyMap.put(entityKey, proxyBean);
107

    
108
        if(mappedEntity != null && mappedEntity != bean) {
109
            copyEntitiyKeys.add(entityKey);
110
            flags += COPY_ENTITY + bean.hashCode();
111
        }
112

    
113
        flags = analyzeMore(bean, entityKey, flags, mappedEntity);
114

    
115
        if(!flags.isEmpty()){
116
            propertyPath += "(" + flags + ")";
117
        }
118
        propertyPath += "]";
119

    
120
        logger.debug(propertyPath);
121

    
122
        entityPathList.add(propertyPath);
123
        if(!entityPathsMap.containsKey(entityKey)){
124
            entityPathsMap.put(entityKey, new ArrayList<>());
125
        }
126
        entityPathsMap.get(entityKey).add(propertyPath);
127

    
128
        if(!objectsSeen.add(bean)){
129
            // avoid cycles, do not recurse into properties of objects that have been analyzed already
130
            return;
131
        }
132

    
133
        Set<PropertyDescriptor> properties = AbstractBeanInitializer.getProperties(bean, null);
134
        for(PropertyDescriptor prop : properties){
135

    
136
            try {
137
                Object propertyValue = PropertyUtils.getProperty(bean, prop.getName());
138

    
139
                if(propertyValue == null){
140
                    continue;
141
                }
142

    
143
                String propertyPathSuffix = "." + prop.getName();
144
                logger.debug("\t\tproperty:" + propertyPathSuffix);
145

    
146
                if(Hibernate.isInitialized(propertyValue)) {
147

    
148
                    if(CdmBase.class.isAssignableFrom(prop.getPropertyType())
149
                            || INomenclaturalReference.class.isAssignableFrom(prop.getPropertyType())
150
                            ){
151
                        analyzeEntity(HibernateProxyHelper.deproxy(propertyValue, CdmBase.class), propertyPath + propertyPathSuffix);
152
                        continue;
153
                    }
154

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

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

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

    
194
        }
195
    }
196

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

    
210

    
211
    public void printEntityGraph(PrintStream printStream){
212
        printLegend(printStream);
213
        for(String path : entityPathList) {
214
            printStream.println(path);
215
        }
216
    }
217

    
218
    public void printCopyEntities(PrintStream printStream){
219
        printLegend(printStream);
220
        for(EntityKey key : copyEntitiyKeys){
221
            for(String path : entityPathsMap.get(key)) {
222
                printStream.println(path);
223
            }
224
        }
225
    }
226

    
227
    /**
228
     * @param printStream
229
     */
230
    protected void printLegend(PrintStream printStream) {
231
        printStream.println(this.getClass().getSimpleName() + " legend: ");
232
        printStream.println("    - '!{objectHash}': detected copy entity, followed by object hash");
233
    }
234

    
235
    public class EntityKey {
236

    
237
        Class type;
238
        int id;
239

    
240
        public EntityKey(Class type, int id){
241
            this.type = type;
242
            this.id = id;
243
        }
244

    
245
        public EntityKey(CdmBase entity){
246
            type = entity.getClass();
247
            id = entity.getId();
248
        }
249

    
250
        /**
251
         * @return the type
252
         */
253
        public Class getType() {
254
            return type;
255
        }
256

    
257
        /**
258
         * @return the id
259
         */
260
        public int getId() {
261
            return id;
262
        }
263

    
264
        /**
265
         * {@inheritDoc}
266
         */
267
        @Override
268
        public int hashCode() {
269
            return new HashCodeBuilder(15, 33)
270
                    .append(type)
271
                    .append(id)
272
                    .toHashCode();
273
        }
274

    
275
        @Override
276
        public boolean equals(Object obj) {
277
            EntityKey other = (EntityKey)obj;
278
            return this.id == other.id && this.type == other.type;
279

    
280
        }
281

    
282
        @Override
283
        public String toString() {
284
            return type.getSimpleName() + "#" + getId();
285
        }
286

    
287
    }
288

    
289
    /**
290
     * {@inheritDoc}
291
     * <p>
292
     * In case the cached bean is a HibernateProxy it will be unproxied
293
     * before returning it.
294
     */
295
    @Override
296
    public <CDM extends CdmBase> CDM find(CDM value) {
297
        if(value != null){
298
            EntityKey entityKey = new EntityKey(HibernateProxyHelper.deproxy(value));
299
            CDM cachedBean = (CDM) entityyMap.get(entityKey);
300
            if(cachedBean != null){
301
                return (CDM) HibernateProxyHelper.deproxy(cachedBean, CdmBase.class);
302
            }
303
        }
304
        return null;
305
    }
306

    
307
    private <CDM extends CdmBase> CDM findProxy(CDM value) {
308
        if(value != null){
309
            EntityKey entityKey = new EntityKey(HibernateProxyHelper.deproxy(value));
310
            return (CDM) entityyMap.get(entityKey);
311
        }
312
        return null;
313
    }
314

    
315
    /**
316
     * {@inheritDoc}
317
     * <p>
318
     * In case the cached bean is a HibernateProxy it will be unproxied
319
     * before returning it.
320
     */
321
    @Override
322
    public <CDM extends CdmBase> CDM find(Class<CDM> type, int id) {
323
        EntityKey entityKey = new EntityKey(type, id);
324
        CDM cachedBean = (CDM) entityyMap.get(entityKey);
325
        if(cachedBean != null){
326
            return (CDM) HibernateProxyHelper.deproxy(cachedBean, CdmBase.class);
327
        }
328
        return null;
329
    }
330

    
331
    @Override
332
    public <CDM extends CdmBase> CDM findAndUpdate(CDM value){
333
        CDM cachedBean = findProxy(value);
334
        if(cachedBean != null && VersionableEntity.class.isAssignableFrom(cachedBean.getClass())){
335
            updatedCachedIfEarlier((VersionableEntity)cachedBean, (VersionableEntity)value);
336
        }
337
        if(cachedBean != null){
338
            return (CDM) HibernateProxyHelper.deproxy(cachedBean, CdmBase.class);
339
        }
340
        return null;
341

    
342
    }
343

    
344
    /**
345
     * @param cachedValue
346
     * @param value
347
     */
348
    private <CDM extends VersionableEntity> void updatedCachedIfEarlier(CDM cachedValue, CDM value) {
349
       if(cachedValue != null && value != null && value.getUpdated() != null){
350
           if(cachedValue.getUpdated() == null || value.getUpdated().isAfter(cachedValue.getUpdated())){
351
               try {
352
                copyProperties(cachedValue, value);
353
            } catch (IllegalAccessException e) {
354
                /* should never happen */
355
                e.printStackTrace();
356
            } catch (InvocationTargetException e) {
357
                /* critical! re-throw as runtime exception, which is ok in the context of a vaadin app */
358
                throw new RuntimeException(e);
359
            }
360
           }
361
       }
362

    
363
    }
364

    
365
    /**
366
     * partially copy of {@link BeanUtilsBean#copyProperties(Object, Object)}
367
     */
368
    private <CDM extends VersionableEntity> void copyProperties(CDM dest, CDM orig) throws IllegalAccessException, InvocationTargetException {
369

    
370
        PropertyUtilsBean propertyUtils = BeanUtilsBean.getInstance().getPropertyUtils();
371
        PropertyDescriptor[] origDescriptors =
372
                propertyUtils.getPropertyDescriptors(orig);
373
            for (int i = 0; i < origDescriptors.length; i++) {
374
                String name = origDescriptors[i].getName();
375
                if ("class".equals(name)) {
376
                    continue; // No point in trying to set an object's class
377
                }
378
                if (propertyUtils.isReadable(orig, name) &&
379
                        propertyUtils.isWriteable(dest, name)) {
380
                    try {
381
                        if(CdmBase.class.isAssignableFrom(propertyUtils.getPropertyType(dest, name))){
382
                            CdmBase origValue = (CdmBase)propertyUtils.getSimpleProperty(orig, name);
383
                            if(!Hibernate.isInitialized(origValue)){
384
                                // ignore uninitialized entities
385
                                continue;
386
                            }
387
                            // only copy entities if origValue either is a completely different entity (A)
388
                            // or if origValue is updated (B).
389
                            CdmBase destValue = (CdmBase)propertyUtils.getSimpleProperty(dest, name);
390
                            if(destValue == null && origValue == null){
391
                                continue;
392
                            }
393
                            if(
394
                                    origValue != null && destValue == null ||
395
                                    origValue == null && destValue != null ||
396

    
397
                                    origValue.getId() != destValue.getId() ||
398
                                    origValue.getClass() != destValue.getClass() ||
399
                                    origValue.getUuid() != destValue.getUuid()){
400
                                // (A)
401
                                BeanUtilsBean.getInstance().copyProperty(dest, name, origValue);
402

    
403
                            } else {
404
                                // (B) recurse into findAndUpdate
405
                                findAndUpdate(origValue);
406
                            }
407
                        } else {
408
                                Object value = propertyUtils.getSimpleProperty(orig, name);
409
                                BeanUtilsBean.getInstance().copyProperty(dest, name, value);
410
                        }
411

    
412
                    } catch (NoSuchMethodException e) {
413
                        // Should not happen
414
                    }
415
                }
416
            }
417

    
418
    }
419

    
420
    /**
421
     * {@inheritDoc}
422
     */
423
    @Override
424
    public <CDM extends CdmBase> void add(CDM value) {
425
        entities.add(value);
426
        analyzeEntity(value, "");
427
    }
428

    
429

    
430
}
(1-1/2)