Project

General

Profile

Download (8.83 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
    public PersistentContextAnalyzer(CdmBase entity, Session session){
78
        this.session = session;
79
        this.entity = entity;
80
        update();
81
    }
82

    
83
    public PersistentContextAnalyzer(CdmBase entity){
84
        this(entity, null);
85
    }
86

    
87
    /**
88
     * - find copied entities in the graph
89
     */
90
    private void update() {
91

    
92
        entityyMap.clear();
93
        entityPathList.clear();
94
        entityPathsMap.clear();
95
        copyEntitiyKeys.clear();
96
        objectsSeen.clear();
97

    
98
        String propertyPath = "";
99

    
100
        analyzeEntity(entity, propertyPath);
101
    }
102

    
103
    public void printEntityGraph(PrintStream printStream){
104
        printLegend(printStream);
105
        for(String path : entityPathList) {
106
            printStream.println(path);
107
        }
108
    }
109

    
110
    public void printCopyEntities(PrintStream printStream){
111
        printLegend(printStream);
112
        for(EntityKey key : copyEntitiyKeys){
113
            for(String path : entityPathsMap.get(key)) {
114
                printStream.println(path);
115
            }
116
        }
117
    }
118

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

    
129
    /**
130
     *
131
     */
132
    protected void analyzeEntity(CdmBase bean, String propertyPath) {
133

    
134
        EntityKey entityKey = new EntityKey(bean);
135

    
136
        propertyPath += "[" + entityKey;
137
        String flags = "";
138
        CdmBase mappedEntity = entityyMap.put(entityKey, bean);
139

    
140
        boolean hashAdded = false;
141

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

    
158
        logger.debug(propertyPath);
159

    
160
        entityPathList.add(propertyPath);
161
        if(!entityPathsMap.containsKey(entityKey)){
162
            entityPathsMap.put(entityKey, new ArrayList<>());
163
        }
164
        entityPathsMap.get(entityKey).add(propertyPath);
165

    
166
        if(!objectsSeen.add(bean)){
167
            // avoid cycles, do not recurse into properties of objects that have been analyzed already
168
            return;
169
        }
170

    
171
        Set<PropertyDescriptor> properties = AbstractBeanInitializer.getProperties(bean, null);
172
        for(PropertyDescriptor prop : properties){
173

    
174
            try {
175
                Object propertyValue = PropertyUtils.getProperty(bean, prop.getName());
176

    
177
                if(propertyValue == null){
178
                    continue;
179
                }
180

    
181
                String propertyPathSuffix = "." + prop.getName();
182

    
183
                if(Hibernate.isInitialized(propertyValue)) {
184

    
185
                    if(CdmBase.class.isAssignableFrom(prop.getPropertyType())){
186
                        analyzeEntity(HibernateProxyHelper.deproxy(propertyValue, CdmBase.class), propertyPath + propertyPathSuffix);
187
                        continue;
188
                    }
189

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

    
206
                    if(collection != null){
207
                        for(CdmBase collectionItem : collection){
208
                            analyzeEntity(HibernateProxyHelper.deproxy(collectionItem, CdmBase.class), propertyPath + propertyPathSuffix);
209
                        }
210
                    } else {
211
                        // logger.error("Unhandled property type " + propertyValue.getClass().getName());
212
                    }
213
                }
214

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

    
228
        }
229
    }
230

    
231
    /**
232
     * @return the showHashCodes
233
     */
234
    public boolean isShowHashCodes() {
235
        return showHashCodes;
236
    }
237

    
238
    /**
239
     * @param showHashCodes the showHashCodes to set
240
     */
241
    public void setShowHashCodes(boolean showHashCodes) {
242
        boolean runUpdate = this.showHashCodes != showHashCodes;
243
        this.showHashCodes = showHashCodes;
244
        if(runUpdate){
245
            update();
246
        }
247
    }
248

    
249
    class EntityKey {
250

    
251
        Class type;
252
        int id;
253

    
254
        public EntityKey(CdmBase entity){
255
            type = entity.getClass();
256
            id = entity.getId();
257
        }
258

    
259
        /**
260
         * @return the type
261
         */
262
        public Class getType() {
263
            return type;
264
        }
265

    
266
        /**
267
         * @return the id
268
         */
269
        public int getId() {
270
            return id;
271
        }
272

    
273
        /**
274
         * {@inheritDoc}
275
         */
276
        @Override
277
        public int hashCode() {
278
            return new HashCodeBuilder(17, 31)
279
                    .append(type)
280
                    .append(id)
281
                    .toHashCode();
282
        }
283

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

    
289
    }
290

    
291

    
292
}
    (1-1/1)