001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.tide.data;
022    
023    import java.lang.reflect.Array;
024    import java.lang.reflect.Field;
025    import java.util.Collection;
026    import java.util.HashMap;
027    import java.util.HashSet;
028    import java.util.Iterator;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Set;
032    
033    import org.granite.context.GraniteContext;
034    import org.granite.messaging.amf.io.util.ClassGetter;
035    import org.granite.tide.IUID;
036    
037    
038    /**
039     * @author William DRAI
040     */
041    public class DataMergeContext {
042        
043        private static ThreadLocal<DataMergeContext> dataMergeContext = new ThreadLocal<DataMergeContext>() {
044            @Override
045            protected DataMergeContext initialValue() {
046                return new DataMergeContext();
047            }
048        };
049        
050        public static DataMergeContext get() {
051            return dataMergeContext.get();
052        }
053        
054        public static void remove() {
055            dataMergeContext.remove();
056        }
057        
058        private final Map<Object, Object> cache = new HashMap<Object, Object>();
059        private final Map<Object, Object> loadedEntities = new HashMap<Object, Object>();
060    
061    
062        public static Map<Object, Object> getCache() {
063            return dataMergeContext.get().cache;
064        }
065        
066        public static void addLoadedEntity(Object entity) {
067            DataMergeContext mergeContext = dataMergeContext.get();
068            mergeContext.entityLoaded(entity);
069        }
070        
071        public static void addResultEntity(Object result) {
072            DataMergeContext mergeContext = dataMergeContext.get();
073            ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
074            mergeContext.addResultEntity(result, classGetter, new HashSet<Object>());
075        }
076        
077        @SuppressWarnings("unchecked")
078        public void addResultEntity(Object result, ClassGetter classGetter, Set<Object> cache) {
079            if (result == null || cache.contains(result))
080                    return;
081            
082            cache.add(result);
083            
084            if (classGetter.isEntity(result))
085                    addLoadedEntity(result);
086            
087            List<Object[]> values = classGetter.getFieldValues(result, result);
088            for (Object[] value : values) {
089                    Object val = value[1];
090                    if (val == null || val.getClass().isPrimitive())
091                            continue;
092                    if (!classGetter.isInitialized(result, ((Field)value[0]).getName(), val))
093                            continue;
094                            
095                    if (val.getClass().isArray()) {
096                            for (int i = 0; i < Array.getLength(val); i++)
097                                    addResultEntity(Array.get(val, i), classGetter, cache);
098                    }
099                    else if (val instanceof Collection) {
100                            for (Iterator<?> i = ((Collection<?>)val).iterator(); i.hasNext(); )
101                                    addResultEntity(i.next(), classGetter, cache);
102                    }
103                    else if (val instanceof Map) {
104                            for (Iterator<Map.Entry<Object, Object>> i = ((Map<Object, Object>)val).entrySet().iterator(); i.hasNext(); ) {
105                                    Map.Entry<Object, Object> me = i.next();
106                                    addResultEntity(me.getKey(), classGetter, cache);
107                                    addResultEntity(me.getValue(), classGetter, cache);
108                            }
109                    }
110                    else
111                            addResultEntity(val, classGetter, cache);
112            }
113        }
114        
115        private void entityLoaded(Object entity) {
116            Object key = CacheKey.key(entity, null, null);
117            Object cachedEntity = cache.get(key);
118            if (cachedEntity != null) { // && cachedEntity != entity) {
119                    // Entity has already been merged before, replace current merge context by the one from JPA
120                    cache.clear();
121            }
122            
123            if (entity instanceof IUID)
124                    loadedEntities.put(entity.getClass().getName() + "-" + ((IUID)entity).getUid(), entity);
125            else
126                    loadedEntities.put(entity, entity);
127        }
128        
129        public static Object getLoadedEntity(Object entity) {
130            if (entity instanceof IUID)
131                    return dataMergeContext.get().loadedEntities.get(entity.getClass().getName() + "-" + ((IUID)entity).getUid());
132            return dataMergeContext.get().loadedEntities.get(entity);
133        }
134        
135        public static Collection<Object> getLoadedEntities() {
136            return dataMergeContext.get().loadedEntities.values();
137        }
138        
139        public static void restoreLoadedEntities(Set<Object> loadedEntities) {
140            DataMergeContext mergeContext = dataMergeContext.get();
141            for (Object entity : loadedEntities) {
142                    if (entity instanceof IUID)
143                            mergeContext.loadedEntities.put(entity.getClass().getName() + "-" + ((IUID)entity).getUid(), entity);
144                    else
145                            mergeContext.loadedEntities.put(entity, entity);
146            }
147        }
148        
149        
150        public static class CacheKey {
151            
152            public static Object key(Object obj, Object owner, String propertyName) {
153                if (obj instanceof IUID)
154                    return new UIDKey(obj.getClass(), ((IUID)obj).getUid());
155                if (owner != null && (obj instanceof Collection<?> || obj instanceof Map<?, ?>))
156                    return new CollectionKey(owner, propertyName);
157                return obj;
158            }
159        }
160        
161        public static class UIDKey extends CacheKey {
162            
163            private Class<?> clazz;
164            private String uid;
165            
166            public UIDKey(Class<?> clazz, String uid) {
167                    this.clazz = clazz;
168                    this.uid = uid;
169            }
170            
171            @Override
172            public boolean equals(Object obj) {
173                if (obj == null || !obj.getClass().equals(UIDKey.class))
174                    return false;
175                return this.clazz.equals(((UIDKey)obj).clazz) && this.uid.equals(((UIDKey)obj).uid);
176            }
177            
178            @Override
179            public int hashCode() {
180                return (clazz.getName() + "-" + uid).hashCode();
181            }
182        }
183        
184        public static class CollectionKey extends CacheKey {
185            
186            private Object owner;
187            private String propertyName;
188            
189            public CollectionKey(Object owner, String propertyName) {
190                this.owner = owner;
191                this.propertyName = propertyName;
192            }
193            
194            @Override
195            public boolean equals(Object obj) {
196                if (obj == null || !obj.getClass().equals(CollectionKey.class))
197                    return false;
198                return this.owner.equals(((CollectionKey)obj).owner) && this.propertyName.equals(((CollectionKey)obj).propertyName);
199            }
200            
201            @Override
202            public int hashCode() {
203                return owner.hashCode()*31 + propertyName.hashCode();
204            }
205        }
206    }