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 */
022package org.granite.tide.data;
023
024import java.lang.reflect.Array;
025import java.lang.reflect.Field;
026import java.util.Collection;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033
034import org.granite.context.GraniteContext;
035import org.granite.messaging.amf.io.util.ClassGetter;
036import org.granite.tide.IUID;
037
038
039/**
040 * @author William DRAI
041 */
042public 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}