/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Granite Data Services is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.tide.data;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.granite.config.ConvertersConfig;
import org.granite.context.GraniteContext;
import org.granite.messaging.amf.io.util.ClassGetter;
import org.granite.tide.IUID;
/**
* @author William DRAI
*/
public class DataMergeContext {
private static ThreadLocal<DataMergeContext> dataMergeContext = new ThreadLocal<DataMergeContext>() {
@Override
protected DataMergeContext initialValue() {
return new DataMergeContext();
}
};
public static DataMergeContext get() {
return dataMergeContext.get();
}
public static void remove() {
dataMergeContext.remove();
}
private final Map<Object, Object> cache = new HashMap<Object, Object>();
private final Map<Object, Object> loadedEntities = new HashMap<Object, Object>();
public static Map<Object, Object> getCache() {
return dataMergeContext.get().cache;
}
public static void addLoadedEntity(Object entity) {
DataMergeContext mergeContext = dataMergeContext.get();
mergeContext.entityLoaded(entity);
}
public static void addResultEntity(Object result) {
DataMergeContext mergeContext = dataMergeContext.get();
ClassGetter classGetter = ((ConvertersConfig)GraniteContext.getCurrentInstance().getGraniteConfig()).getClassGetter();
mergeContext.addResultEntity(result, classGetter, new HashSet<Object>());
}
@SuppressWarnings("unchecked")
public void addResultEntity(Object result, ClassGetter classGetter, Set<Object> cache) {
if (result == null || cache.contains(result))
return;
cache.add(result);
if (classGetter.isEntity(result))
addLoadedEntity(result);
List<Object[]> values = classGetter.getFieldValues(result, result);
for (Object[] value : values) {
Object val = value[1];
if (val == null || val.getClass().isPrimitive())
continue;
if (!classGetter.isInitialized(result, ((Field)value[0]).getName(), val))
continue;
if (val.getClass().isArray()) {
for (int i = 0; i < Array.getLength(val); i++)
addResultEntity(Array.get(val, i), classGetter, cache);
}
else if (val instanceof Collection) {
for (Iterator<?> i = ((Collection<?>)val).iterator(); i.hasNext(); )
addResultEntity(i.next(), classGetter, cache);
}
else if (val instanceof Map) {
for (Iterator<Map.Entry<Object, Object>> i = ((Map<Object, Object>)val).entrySet().iterator(); i.hasNext(); ) {
Map.Entry<Object, Object> me = i.next();
addResultEntity(me.getKey(), classGetter, cache);
addResultEntity(me.getValue(), classGetter, cache);
}
}
else
addResultEntity(val, classGetter, cache);
}
}
private void entityLoaded(Object entity) {
Object key = CacheKey.key(entity, null, null);
Object cachedEntity = cache.get(key);
if (cachedEntity != null) { // && cachedEntity != entity) {
// Entity has already been merged before, replace current merge context by the one from JPA
cache.clear();
}
if (entity instanceof IUID)
loadedEntities.put(entity.getClass().getName() + "-" + ((IUID)entity).getUid(), entity);
else
loadedEntities.put(entity, entity);
}
public static Object getLoadedEntity(Object entity) {
if (entity instanceof IUID)
return dataMergeContext.get().loadedEntities.get(entity.getClass().getName() + "-" + ((IUID)entity).getUid());
return dataMergeContext.get().loadedEntities.get(entity);
}
public static Collection<Object> getLoadedEntities() {
return dataMergeContext.get().loadedEntities.values();
}
public static void restoreLoadedEntities(Set<Object> loadedEntities) {
DataMergeContext mergeContext = dataMergeContext.get();
for (Object entity : loadedEntities) {
if (entity instanceof IUID)
mergeContext.loadedEntities.put(entity.getClass().getName() + "-" + ((IUID)entity).getUid(), entity);
else
mergeContext.loadedEntities.put(entity, entity);
}
}
public static class CacheKey {
public static Object key(Object obj, Object owner, String propertyName) {
if (obj instanceof IUID)
return new UIDKey(obj.getClass(), ((IUID)obj).getUid());
if (owner != null && (obj instanceof Collection<?> || obj instanceof Map<?, ?>))
return new CollectionKey(owner, propertyName);
return obj;
}
}
public static class UIDKey extends CacheKey {
private Class<?> clazz;
private String uid;
public UIDKey(Class<?> clazz, String uid) {
this.clazz = clazz;
this.uid = uid;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !obj.getClass().equals(UIDKey.class))
return false;
return this.clazz.equals(((UIDKey)obj).clazz) && this.uid.equals(((UIDKey)obj).uid);
}
@Override
public int hashCode() {
return (clazz.getName() + "-" + uid).hashCode();
}
}
public static class CollectionKey extends CacheKey {
private Object owner;
private String propertyName;
public CollectionKey(Object owner, String propertyName) {
this.owner = owner;
this.propertyName = propertyName;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !obj.getClass().equals(CollectionKey.class))
return false;
return this.owner.equals(((CollectionKey)obj).owner) && this.propertyName.equals(((CollectionKey)obj).propertyName);
}
@Override
public int hashCode() {
return owner.hashCode()*31 + propertyName.hashCode();
}
}
}