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}