Project

General

Profile

Download (8.98 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.debug;
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.Session;
27
import org.hibernate.collection.internal.AbstractPersistentCollection;
28
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.CollectionProxy;
29
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.MapProxy;
30
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.SortedMapProxy;
31

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

    
36
/**
37
 * @author a.kohlbecker
38
 * @since 08.11.2017
39
 *
40
 */
41
public class PersistentContextAnalyzer {
42

    
43
    /**
44
     *
45
     */
46
    private static final char HASH_SEPARATOR = '.';
47

    
48
    /**
49
     *
50
     */
51
    private static final String COPY_ENTITY = "!";
52

    
53
    /**
54
     *
55
     */
56
    private static final String IN_PERSITENT_CONTEXT = "*";
57

    
58
    private final static Logger logger = Logger.getLogger(PersistentContextAnalyzer.class);
59

    
60
    private Session session;
61

    
62
    private CdmBase entity;
63

    
64
    private Map<EntityKey, CdmBase> entityyMap = new HashMap<>();
65

    
66
    private List<String> entityPathList = new ArrayList<>();
67

    
68
    private Map<EntityKey, List<String>> entityPathsMap = new HashMap<>();
69

    
70
    private Set<EntityKey> copyEntitiyKeys = new HashSet<>();
71

    
72
    private Set<Object> objectsSeen = new HashSet<>();
73

    
74
    private boolean showHashCodes = false;
75

    
76
    /**
77
     * TODO the PersistentContextAnalyzer should be a subclass od the CdmEntityCache!!!
78
     *
79
     * @param entity
80
     * @param session
81
     */
82
    public PersistentContextAnalyzer(CdmBase entity, Session session){
83
        this.session = session;
84
        this.entity = entity;
85
        update();
86
    }
87

    
88
    public PersistentContextAnalyzer(CdmBase entity){
89
        this(entity, null);
90
    }
91

    
92
    /**
93
     * - find copied entities in the graph
94
     */
95
    private void update() {
96

    
97
        entityyMap.clear();
98
        entityPathList.clear();
99
        entityPathsMap.clear();
100
        copyEntitiyKeys.clear();
101
        objectsSeen.clear();
102

    
103
        String propertyPath = "";
104

    
105
        analyzeEntity(entity, propertyPath);
106
    }
107

    
108
    public void printEntityGraph(PrintStream printStream){
109
        printLegend(printStream);
110
        for(String path : entityPathList) {
111
            printStream.println(path);
112
        }
113
    }
114

    
115
    public void printCopyEntities(PrintStream printStream){
116
        printLegend(printStream);
117
        for(EntityKey key : copyEntitiyKeys){
118
            for(String path : entityPathsMap.get(key)) {
119
                printStream.println(path);
120
            }
121
        }
122
    }
123

    
124
    /**
125
     * @param printStream
126
     */
127
    protected void printLegend(PrintStream printStream) {
128
        printStream.println("PersistentContextAnalyzer legend: ");
129
        printStream.println("    - '.{objectHash}': unique copy entity, followed by object hash (only shown when showHashCodes is enabled)");
130
        printStream.println("    - '!{objectHash}': detected copy entity, followed by object hash");
131
        printStream.println("    - '*': entity mapped in persistent context");
132
    }
133

    
134
    /**
135
     *
136
     */
137
    protected void analyzeEntity(CdmBase bean, String propertyPath) {
138

    
139
        EntityKey entityKey = new EntityKey(bean);
140

    
141
        propertyPath += "[" + entityKey;
142
        String flags = "";
143
        CdmBase mappedEntity = entityyMap.put(entityKey, bean);
144

    
145
        boolean hashAdded = false;
146

    
147
        if(session != null && session.contains(bean)){
148
            flags += IN_PERSITENT_CONTEXT;
149
        }
150
        if(mappedEntity != null && mappedEntity != bean) {
151
            copyEntitiyKeys.add(entityKey);
152
            flags += COPY_ENTITY + bean.hashCode();
153
            hashAdded = true;
154
        }
155
        if(showHashCodes && ! hashAdded){
156
            flags += HASH_SEPARATOR + bean.hashCode();
157
        }
158
        if(!flags.isEmpty()){
159
            propertyPath += "(" + flags + ")";
160
        }
161
        propertyPath += "]";
162

    
163
        logger.debug(propertyPath);
164

    
165
        entityPathList.add(propertyPath);
166
        if(!entityPathsMap.containsKey(entityKey)){
167
            entityPathsMap.put(entityKey, new ArrayList<>());
168
        }
169
        entityPathsMap.get(entityKey).add(propertyPath);
170

    
171
        if(!objectsSeen.add(bean)){
172
            // avoid cycles, do not recurse into properties of objects that have been analyzed already
173
            return;
174
        }
175

    
176
        Set<PropertyDescriptor> properties = AbstractBeanInitializer.getProperties(bean, null);
177
        for(PropertyDescriptor prop : properties){
178

    
179
            try {
180
                Object propertyValue = PropertyUtils.getProperty(bean, prop.getName());
181

    
182
                if(propertyValue == null){
183
                    continue;
184
                }
185

    
186
                String propertyPathSuffix = "." + prop.getName();
187

    
188
                if(Hibernate.isInitialized(propertyValue)) {
189

    
190
                    if(CdmBase.class.isAssignableFrom(prop.getPropertyType())){
191
                        analyzeEntity(HibernateProxyHelper.deproxy(propertyValue, CdmBase.class), propertyPath + propertyPathSuffix);
192
                        continue;
193
                    }
194

    
195
                    Collection<CdmBase> collection = null;
196
                    if(propertyValue instanceof AbstractPersistentCollection){
197
                        if (propertyValue  instanceof Collection) {
198
                            collection = (Collection<CdmBase>) propertyValue;
199
                        } else if (propertyValue instanceof Map) {
200
                            collection = ((Map<?,CdmBase>)propertyValue).values();
201
                        } else {
202
                            logger.error("unhandled subtype of AbstractPersistentCollection");
203
                        }
204
                    } else if (propertyValue instanceof CollectionProxy
205
                                || propertyValue instanceof MapProxy<?, ?>
206
                                || propertyValue instanceof SortedMapProxy<?, ?>){
207
                            //hibernate envers collections
208
                            collection = (Collection<CdmBase>)propertyValue;
209
                    }
210

    
211
                    if(collection != null){
212
                        for(CdmBase collectionItem : collection){
213
                            analyzeEntity(HibernateProxyHelper.deproxy(collectionItem, CdmBase.class), propertyPath + propertyPathSuffix);
214
                        }
215
                    } else {
216
                        // logger.error("Unhandled property type " + propertyValue.getClass().getName());
217
                    }
218
                }
219

    
220
            } catch (IllegalAccessException e) {
221
                String message = "Illegal access on property " + prop;
222
                logger.error(message);
223
                throw new RuntimeException(message, e);
224
            } catch (InvocationTargetException e) {
225
                String message = "Cannot invoke property " + prop + " not found";
226
                logger.error(message);
227
                throw new RuntimeException(message, e);
228
            } catch (NoSuchMethodException e) {
229
                String message = "Property " + prop.getName() + " not found for class " + bean.getClass();
230
                logger.error(message);
231
            }
232

    
233
        }
234
    }
235

    
236
    /**
237
     * @return the showHashCodes
238
     */
239
    public boolean isShowHashCodes() {
240
        return showHashCodes;
241
    }
242

    
243
    /**
244
     * @param showHashCodes the showHashCodes to set
245
     */
246
    public void setShowHashCodes(boolean showHashCodes) {
247
        boolean runUpdate = this.showHashCodes != showHashCodes;
248
        this.showHashCodes = showHashCodes;
249
        if(runUpdate){
250
            update();
251
        }
252
    }
253

    
254
    class EntityKey {
255

    
256
        Class type;
257
        int id;
258

    
259
        public EntityKey(CdmBase entity){
260
            type = entity.getClass();
261
            id = entity.getId();
262
        }
263

    
264
        /**
265
         * @return the type
266
         */
267
        public Class getType() {
268
            return type;
269
        }
270

    
271
        /**
272
         * @return the id
273
         */
274
        public int getId() {
275
            return id;
276
        }
277

    
278
        /**
279
         * {@inheritDoc}
280
         */
281
        @Override
282
        public int hashCode() {
283
            return new HashCodeBuilder(17, 31)
284
                    .append(type)
285
                    .append(id)
286
                    .toHashCode();
287
        }
288

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

    
294
    }
295

    
296

    
297
}
    (1-1/1)