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