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