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}