/** * 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; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import org.granite.config.ConvertersConfig; import org.granite.context.GraniteContext; import org.granite.logging.Logger; import org.granite.messaging.amf.io.util.ClassGetter; import org.granite.messaging.amf.io.util.DefaultClassGetter; import org.granite.messaging.amf.io.util.externalizer.annotation.ExternalizedBean; import org.granite.messaging.service.ServiceException; import org.granite.messaging.service.ServiceInvocationContext; import org.granite.tide.async.AsyncPublisher; import org.granite.tide.data.DataMergeContext; import org.granite.tide.data.DataMergeContext.CacheKey; import org.granite.util.ArrayUtil; /** * @author William DRAI */ public abstract class TideServiceContext implements Serializable { private static final long serialVersionUID = 1L; private static final Logger log = Logger.getLogger(TideServiceContext.class); protected static final Object[] EMPTY_ARGS = new Object[0]; public static final String COMPONENT_ATTR = "__TIDE_COMPONENT__"; public static final String COMPONENT_CLASS_ATTR = "__TIDE_COMPONENT_CLASS__"; private String sessionId = null; public TideServiceContext() throws ServiceException { } public String getSessionId() { return sessionId; } public void setSessionId(String sessionId) { this.sessionId = sessionId; } public void initCall() { } public Object adjustInvokee(Object instance, String componentName, Set<Class<?>> componentClasses) { return instance; } public Object[] beforeMethodSearch(Object instance, String methodName, Object[] args) { return new Object[] { args[2], args[3] }; } public abstract Object findComponent(String componentName, Class<?> componentClass, String componentPath); public abstract Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass, String componentPath); public abstract void prepareCall(ServiceInvocationContext context, IInvocationCall call, String componentName, Class<?> componentClass); public abstract IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass); public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) { } protected abstract AsyncPublisher getAsyncPublisher(); public void sendEvent(String componentName, Class<?> componentClass) { AsyncPublisher publisher = getAsyncPublisher(); if (publisher != null) { IInvocationResult eventResult = postCall(null, null, componentName, componentClass); publisher.publishMessage(sessionId, eventResult); } } protected boolean isBeanAnnotationPresent(Collection<Class<?>> beanClasses, String methodName, Class<?>[] methodArgTypes, Class<? extends Annotation> annotationClass) { for (Class<?> beanClass : beanClasses) { if (beanClass.isAnnotationPresent(annotationClass)) return true; try { Method m = beanClass.getMethod(methodName, methodArgTypes); if (m.isAnnotationPresent(annotationClass)) return true; } catch (NoSuchMethodException e) { // Method not found on this interface } } return false; } /** * Create a TidePersistenceManager * * @param create create if not existent (can be false for use in entity merge) * @return a PersistenceContextManager */ protected abstract TidePersistenceManager getTidePersistenceManager(boolean create); public Object mergeExternal(Object obj, Object previous) { ClassGetter classGetter = ((ConvertersConfig)GraniteContext.getCurrentInstance().getGraniteConfig()).getClassGetter(); return mergeExternal(classGetter, obj, previous, null, null); } @SuppressWarnings("unchecked") protected Object mergeExternal(ClassGetter classGetter, Object obj, Object previous, Object owner, String propertyName) { if (obj == null) return null; if (!classGetter.isInitialized(owner, propertyName, obj)) { if (previous != null) return previous; return obj; } Map<Object, Object> cache = DataMergeContext.getCache(); Object key = CacheKey.key(obj, owner, propertyName); Object prev = cache.get(key); Object next = obj; if (prev != null) { next = prev; } else if (obj instanceof Collection) { next = mergeCollection(classGetter, (Collection<Object>)obj, previous, owner, propertyName); } else if (obj.getClass().isArray()) { next = mergeArray(classGetter, obj, previous, owner, propertyName); } else if (obj instanceof Map) { next = mergeMap(classGetter, (Map<Object, Object>)obj, previous, owner, propertyName); } else if (classGetter.isEntity(obj) || obj.getClass().isAnnotationPresent(ExternalizedBean.class)) { next = mergeEntity(classGetter, obj, previous, owner, propertyName); } return next; } protected boolean equals(Object obj1, Object obj2) { // TODO: Should we add a check on class equality ??? if (obj1 instanceof IUID && obj2 instanceof IUID) return ((IUID)obj1).getUid() != null && ((IUID)obj1).getUid().equals(((IUID)obj2).getUid()); return obj1.equals(obj2); } private Object mergeEntity(ClassGetter classGetter, Object obj, Object previous, Object owner, String propertyName) { Object dest = obj; boolean isEntity = classGetter.isEntity(obj); boolean sameEntity = false; if (isEntity) { Object p = DataMergeContext.getLoadedEntity(obj); if (p != null) { // We are sure here that the application has loaded the entity in the persistence context // It's safe to merge the incoming entity previous = p; } } sameEntity = previous != null && equals(previous, obj); if (sameEntity) dest = previous; DataMergeContext.getCache().put(CacheKey.key(obj, null, null), dest); List<Object[]> fieldValues = isEntity ? classGetter.getFieldValues(obj, dest) : DefaultClassGetter.defaultGetFieldValues(obj, dest); // Merges field values try { for (Object[] fieldValue : fieldValues) { Field field = (Field)fieldValue[0]; Object objv = fieldValue[1]; Object destv = fieldValue[2]; objv = mergeExternal(classGetter, objv, destv, obj, field.getName()); field.set(dest, objv); } } catch (Exception e) { throw new RuntimeException("Could not merge entity ", e); } return dest; } private Object mergeCollection(ClassGetter classGetter, Collection<Object> coll, Object previous, Object owner, String propertyName) { if (log.isDebugEnabled()) log.debug("Context mergeCollection: " + coll + (previous != null ? " previous " + previous.getClass().getName() : "")); Map<Object, Object> cache = DataMergeContext.getCache(); Object key = CacheKey.key(coll, owner, propertyName); if (previous != null && previous instanceof Collection<?>) cache.put(key, previous); else cache.put(key, coll); @SuppressWarnings("unchecked") Collection<Object> prevColl = previous instanceof Collection ? (Collection<Object>)previous : null; if (coll == prevColl) { for (Object obj : coll) mergeExternal(classGetter, obj, obj, null, null); } else { List<Object> addedToColl = new ArrayList<Object>(); Iterator<Object> icoll = coll.iterator(); for (int i = 0; i < coll.size(); i++) { Object obj = icoll.next(); if (prevColl instanceof List<?>) { boolean found = false; List<Object> prevList = (List<Object>)prevColl; for (int j = 0; j < prevList.size(); j++) { Object prev = prevList.get(j); if (prev != null && equals(prev, obj)) { obj = mergeExternal(classGetter, obj, prev, null, null); if (i < prevList.size()) { if (j != i) prevList.set(j, prevList.get(i)); if (obj != prevList.get(i)) prevList.set(i, obj); } else if (obj != prevList.get(j)) prevList.set(j, obj); found = true; } } if (!found) { obj = mergeExternal(obj, null); prevColl.add(obj); } } else if (prevColl != null) { boolean found = false; Iterator<Object> iprevcoll = prevColl.iterator(); List<Object> added = new ArrayList<Object>(); for (int j = 0; j < prevColl.size(); j++) { Object prev = iprevcoll.next(); if (prev != null && equals(prev, obj)) { obj = mergeExternal(classGetter, obj, prev, null, null); if (obj != prev) { if (prevColl instanceof List<?>) ((List<Object>)prevColl).set(j, obj); else { iprevcoll.remove(); added.add(obj); } } found = true; } } prevColl.addAll(added); if (!found) { obj = mergeExternal(classGetter, obj, null, null, null); prevColl.add(obj); } } else { obj = mergeExternal(obj, null); if (icoll instanceof ListIterator<?>) ((ListIterator<Object>)icoll).set(obj); else addedToColl.add(obj); } } if (!addedToColl.isEmpty()) { coll.removeAll(addedToColl); // Ensure that all entities are replaced by the merged ones coll.addAll(addedToColl); } if (prevColl != null) { Iterator<Object> iprevcoll = prevColl.iterator(); for (int i = 0; i < prevColl.size(); i++) { Object obj = iprevcoll.next(); boolean found = false; for (Object next : coll) { if (next != null && equals(next, obj)) { found = true; break; } } if (!found) { iprevcoll.remove(); i--; } } return previous; } } return coll; } private Object mergeArray(ClassGetter classGetter, Object array, Object previous, Object owner, String propertyName) { if (log.isDebugEnabled()) log.debug("Context mergeArray: " + array + (previous != null ? " previous " + previous.getClass().getName() : "")); Object key = CacheKey.key(array, owner, propertyName); int length = Array.getLength(array); Object prevArray = ArrayUtil.newArray(ArrayUtil.getComponentType(array.getClass()), length); DataMergeContext.getCache().put(key, prevArray); for (int i = 0; i < length; i++) { Object obj = Array.get(array, i); Array.set(prevArray, i, mergeExternal(classGetter, obj, null, null, null)); } return prevArray; } private Object mergeMap(ClassGetter classGetter, Map<Object, Object> map, Object previous, Object owner, String propertyName) { if (log.isDebugEnabled()) log.debug("Context mergeMap: " + map + (previous != null ? " previous " + previous.getClass().getName() : "")); Map<Object, Object> cache = DataMergeContext.getCache(); Object cacheKey = CacheKey.key(map, owner, propertyName); if (previous != null && previous instanceof Map<?, ?>) cache.put(cacheKey, previous); else cache.put(cacheKey, map); @SuppressWarnings("unchecked") Map<Object, Object> prevMap = previous instanceof Map ? (Map<Object, Object>)previous : null; if (map == prevMap) { for (Map.Entry<Object, Object> me : map.entrySet()) { mergeExternal(classGetter, me.getKey(), null, null, null); mergeExternal(classGetter, me.getValue(), null, null, null); } } else { if (prevMap != null) { if (map != prevMap) { prevMap.clear(); for (Map.Entry<Object, Object> me : map.entrySet()) { Object key = mergeExternal(classGetter, me.getKey(), null, null, null); Object value = mergeExternal(classGetter, me.getValue(), null, null, null); prevMap.put(key, value); } } return prevMap; } Set<Object[]> addedToMap = new HashSet<Object[]>(); for (Iterator<Map.Entry<Object, Object>> ime = map.entrySet().iterator(); ime.hasNext(); ) { Map.Entry<Object, Object> me = ime.next(); ime.remove(); Object key = mergeExternal(classGetter, me.getKey(), null, null, null); Object value = mergeExternal(classGetter, me.getValue(), null, null, null); addedToMap.add(new Object[] { key, value }); } for (Object[] me : addedToMap) map.put(me[0], me[1]); } return map; } /** * Initialize the lazy property for the passed in entity. * @param entity the entity that has a lazy relationship * @param propertyNames the properties of the entity that has been marked lazy * @return the lazy collection */ public Object lazyInitialize(Object entity, String[] propertyNames) { TidePersistenceManager pm = getTidePersistenceManager(true); if (pm == null) { log.warn("No persistence manager found: lazy initialization ignored for " + entity); return entity; } return pm.attachEntity(entity, propertyNames); } }