Project

General

Profile

Download (8.93 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.PropertyUtils;
23
import org.apache.commons.lang.builder.HashCodeBuilder;
24
import org.apache.log4j.Logger;
25
import org.hibernate.Hibernate;
26
import org.hibernate.collection.internal.AbstractPersistentCollection;
27
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.CollectionProxy;
28
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.MapProxy;
29
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.SortedMapProxy;
30

    
31
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
32
import eu.etaxonomy.cdm.model.common.CdmBase;
33
import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
34

    
35
/**
36
 * @author a.kohlbecker
37
 * @since 08.11.2017
38
 *
39
 */
40
public class CdmEntityCache implements EntityCache {
41

    
42

    
43
    private final static Logger logger = Logger.getLogger(CdmEntityCache.class);
44

    
45
    private static final String COPY_ENTITY = "!";
46

    
47
    private Set<CdmBase> entities = new HashSet<>();
48

    
49
    private Map<EntityKey, CdmBase> entityyMap = new HashMap<>();
50

    
51
    private List<String> entityPathList = new ArrayList<>();
52

    
53
    private Map<EntityKey, List<String>> entityPathsMap = new HashMap<>();
54

    
55
    private Set<EntityKey> copyEntitiyKeys = new HashSet<>();
56

    
57
    private Set<Object> objectsSeen = new HashSet<>();
58

    
59
    public CdmEntityCache(CdmBase entity){
60
        this.entities.add(entity);
61
        update();
62
    }
63

    
64
    @Override
65
    public boolean update() {
66

    
67
        entityPathList.clear();
68
        entityPathsMap.clear();
69
        objectsSeen.clear();
70
        copyEntitiyKeys.clear();
71

    
72
        for(CdmBase entity : entities){
73
        analyzeEntity(entity, "");
74
        }
75

    
76
        return copyEntitiyKeys.isEmpty();
77
    }
78

    
79
    /**
80
     *
81
     */
82
    protected void analyzeEntity(CdmBase bean, String propertyPath) {
83

    
84
        EntityKey entityKey = new EntityKey(bean);
85

    
86
        propertyPath += "[" + entityKey;
87
        String flags = "";
88
        CdmBase mappedEntity = entityyMap.put(entityKey, bean);
89

    
90
        if(mappedEntity != null && mappedEntity != bean) {
91
            copyEntitiyKeys.add(entityKey);
92
            flags += COPY_ENTITY + bean.hashCode();
93
        }
94

    
95
        flags = analyzeMore(bean, entityKey, flags, mappedEntity);
96

    
97
        if(!flags.isEmpty()){
98
            propertyPath += "(" + flags + ")";
99
        }
100
        propertyPath += "]";
101

    
102
        logger.debug(propertyPath);
103

    
104
        entityPathList.add(propertyPath);
105
        if(!entityPathsMap.containsKey(entityKey)){
106
            entityPathsMap.put(entityKey, new ArrayList<>());
107
        }
108
        entityPathsMap.get(entityKey).add(propertyPath);
109

    
110
        if(!objectsSeen.add(bean)){
111
            // avoid cycles, do not recurse into properties of objects that have been analyzed already
112
            return;
113
        }
114

    
115
        Set<PropertyDescriptor> properties = AbstractBeanInitializer.getProperties(bean, null);
116
        for(PropertyDescriptor prop : properties){
117

    
118
            try {
119
                Object propertyValue = PropertyUtils.getProperty(bean, prop.getName());
120

    
121
                if(propertyValue == null){
122
                    continue;
123
                }
124

    
125
                String propertyPathSuffix = "." + prop.getName();
126

    
127
                if(Hibernate.isInitialized(propertyValue)) {
128

    
129
                    if(CdmBase.class.isAssignableFrom(prop.getPropertyType())){
130
                        analyzeEntity(HibernateProxyHelper.deproxy(propertyValue, CdmBase.class), propertyPath + propertyPathSuffix);
131
                        continue;
132
                    }
133

    
134
                    Collection<CdmBase> collection = null;
135
                    if(propertyValue instanceof AbstractPersistentCollection){
136
                        if (propertyValue  instanceof Collection) {
137
                            collection = (Collection<CdmBase>) propertyValue;
138
                        } else if (propertyValue instanceof Map) {
139
                            collection = ((Map<?,CdmBase>)propertyValue).values();
140
                        } else {
141
                            logger.error("unhandled subtype of AbstractPersistentCollection");
142
                        }
143
                    } else if (propertyValue instanceof CollectionProxy
144
                                || propertyValue instanceof MapProxy<?, ?>
145
                                || propertyValue instanceof SortedMapProxy<?, ?>){
146
                            //hibernate envers collections
147
                            collection = (Collection<CdmBase>)propertyValue;
148
                    }
149

    
150
                    if(collection != null){
151
                        for(CdmBase collectionItem : collection){
152
                            analyzeEntity(HibernateProxyHelper.deproxy(collectionItem, CdmBase.class), propertyPath + propertyPathSuffix);
153
                        }
154
                    } else {
155
                        // logger.error("Unhandled property type " + propertyValue.getClass().getName());
156
                    }
157
                }
158

    
159
            } catch (IllegalAccessException e) {
160
                String message = "Illegal access on property " + prop;
161
                logger.error(message);
162
                throw new RuntimeException(message, e);
163
            } catch (InvocationTargetException e) {
164
                String message = "Cannot invoke property " + prop + " not found";
165
                logger.error(message);
166
                throw new RuntimeException(message, e);
167
            } catch (NoSuchMethodException e) {
168
                String message = "Property " + prop.getName() + " not found for class " + bean.getClass();
169
                logger.error(message);
170
            }
171

    
172
        }
173
    }
174

    
175
    /**
176
     * Empty method which can be implemented by subclasses which do further analysis.
177
     *
178
     * @param bean
179
     * @param entityKey
180
     * @param flags
181
     * @param mappedEntity
182
     * @return
183
     */
184
    protected String analyzeMore(CdmBase bean, EntityKey entityKey, String flags, CdmBase mappedEntity) {
185
        return flags;
186
    }
187

    
188

    
189
    public void printEntityGraph(PrintStream printStream){
190
        printLegend(printStream);
191
        for(String path : entityPathList) {
192
            printStream.println(path);
193
        }
194
    }
195

    
196
    public void printCopyEntities(PrintStream printStream){
197
        printLegend(printStream);
198
        for(EntityKey key : copyEntitiyKeys){
199
            for(String path : entityPathsMap.get(key)) {
200
                printStream.println(path);
201
            }
202
        }
203
    }
204

    
205
    /**
206
     * @param printStream
207
     */
208
    protected void printLegend(PrintStream printStream) {
209
        printStream.println(this.getClass().getSimpleName() + " legend: ");
210
        printStream.println("    - '!{objectHash}': detected copy entity, followed by object hash");
211
    }
212

    
213
    public class EntityKey {
214

    
215
        Class type;
216
        int id;
217

    
218
        public EntityKey(Class type, int id){
219
            this.type = type;
220
            this.id = id;
221
        }
222

    
223
        public EntityKey(CdmBase entity){
224
            type = entity.getClass();
225
            id = entity.getId();
226
        }
227

    
228
        /**
229
         * @return the type
230
         */
231
        public Class getType() {
232
            return type;
233
        }
234

    
235
        /**
236
         * @return the id
237
         */
238
        public int getId() {
239
            return id;
240
        }
241

    
242
        /**
243
         * {@inheritDoc}
244
         */
245
        @Override
246
        public int hashCode() {
247
            return new HashCodeBuilder(15, 33)
248
                    .append(type)
249
                    .append(id)
250
                    .toHashCode();
251
        }
252

    
253
        @Override
254
        public boolean equals(Object obj) {
255
            EntityKey other = (EntityKey)obj;
256
            return this.id == other.id && this.type == other.type;
257

    
258
        }
259

    
260
        @Override
261
        public String toString() {
262
            return type.getSimpleName() + "#" + getId();
263
        }
264

    
265
    }
266

    
267
    /**
268
     * {@inheritDoc}
269
     */
270
    @Override
271
    public <CDM extends CdmBase> CDM find(CDM value) {
272
        if(value != null){
273
            EntityKey entityKey = new EntityKey(HibernateProxyHelper.deproxy(value));
274
            return (CDM) entityyMap.get(entityKey);
275
        }
276
        return null;
277
    }
278

    
279
    /**
280
     * {@inheritDoc}
281
     */
282
    @Override
283
    public <CDM extends CdmBase> CDM find(Class<CDM> type, int id) {
284
        EntityKey entityKey = new EntityKey(type, id);
285
        return (CDM) entityyMap.get(entityKey);
286
    }
287

    
288
    /**
289
     * {@inheritDoc}
290
     */
291
    @Override
292
    public <CDM extends CdmBase> void add(CDM value) {
293
        entities.add(value);
294
        analyzeEntity(value, "");
295
    }
296

    
297

    
298
}
(1-1/2)