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;
022
023import java.io.Serializable;
024import java.lang.annotation.Annotation;
025import java.lang.reflect.Array;
026import java.lang.reflect.Field;
027import java.lang.reflect.Method;
028import java.util.ArrayList;
029import java.util.Collection;
030import java.util.HashSet;
031import java.util.Iterator;
032import java.util.List;
033import java.util.ListIterator;
034import java.util.Map;
035import java.util.Set;
036
037import org.granite.context.GraniteContext;
038import org.granite.logging.Logger;
039import org.granite.messaging.amf.io.util.ClassGetter;
040import org.granite.messaging.amf.io.util.DefaultClassGetter;
041import org.granite.messaging.amf.io.util.externalizer.annotation.ExternalizedBean;
042import org.granite.messaging.service.ServiceException;
043import org.granite.messaging.service.ServiceInvocationContext;
044import org.granite.tide.async.AsyncPublisher;
045import org.granite.tide.data.DataMergeContext;
046import org.granite.tide.data.DataMergeContext.CacheKey;
047import org.granite.util.ArrayUtil;
048
049
050/**
051 * @author William DRAI
052 */
053public abstract class TideServiceContext implements Serializable {
054
055    private static final long serialVersionUID = 1L;
056    
057    private static final Logger log = Logger.getLogger(TideServiceContext.class);
058    
059    protected static final Object[] EMPTY_ARGS = new Object[0];
060    
061    public static final String COMPONENT_ATTR = "__TIDE_COMPONENT__";
062    public static final String COMPONENT_CLASS_ATTR = "__TIDE_COMPONENT_CLASS__";
063    
064    private String sessionId = null;
065    
066    
067    public TideServiceContext() throws ServiceException {
068    }
069    
070    public String getSessionId() {
071        return sessionId;
072    }
073    
074    public void setSessionId(String sessionId) {
075        this.sessionId = sessionId;
076    }
077    
078    public void initCall() {
079    }
080    
081        
082    public Object adjustInvokee(Object instance, String componentName, Set<Class<?>> componentClasses) {
083        return instance;
084    }
085    
086    public Object[] beforeMethodSearch(Object instance, String methodName, Object[] args) {
087        return new Object[] { args[2], args[3] };
088    }
089    
090    public abstract Object findComponent(String componentName, Class<?> componentClass);
091    
092    public abstract Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass);
093    
094    public abstract void prepareCall(ServiceInvocationContext context, IInvocationCall call, String componentName, Class<?> componentClass);
095    
096    public abstract IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass);
097    
098    public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
099    }
100
101
102    protected abstract AsyncPublisher getAsyncPublisher();
103    
104    
105    public void sendEvent(String componentName, Class<?> componentClass) {
106        AsyncPublisher publisher = getAsyncPublisher();
107        if (publisher != null) {
108            IInvocationResult eventResult = postCall(null, null, componentName, componentClass);
109            publisher.publishMessage(sessionId, eventResult);
110        }
111    }
112    
113    
114    protected boolean isBeanAnnotationPresent(Collection<Class<?>> beanClasses, String methodName, Class<?>[] methodArgTypes, Class<? extends Annotation> annotationClass) {
115        for (Class<?> beanClass : beanClasses) {
116                if (beanClass.isAnnotationPresent(annotationClass))
117                        return true;
118                
119                try {
120                        Method m = beanClass.getMethod(methodName, methodArgTypes);
121                        if (m.isAnnotationPresent(annotationClass))
122                                return true;
123                        }
124                catch (NoSuchMethodException e) {
125                        // Method not found on this interface
126                }
127        }
128        
129        return false;
130    }
131    
132    
133    /**
134     *  Create a TidePersistenceManager
135     *  
136     *  @param create create if not existent (can be false for use in entity merge)
137     *  @return a PersistenceContextManager
138     */
139    protected abstract TidePersistenceManager getTidePersistenceManager(boolean create);
140        
141    
142    public Object mergeExternal(Object obj, Object previous) {
143        ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
144        return mergeExternal(classGetter, obj, previous, null, null);
145    }
146    
147    @SuppressWarnings("unchecked")
148    protected Object mergeExternal(ClassGetter classGetter, Object obj, Object previous, Object owner, String propertyName) {
149        if (obj == null)
150            return null;
151        
152        if (!classGetter.isInitialized(owner, propertyName, obj)) {
153            if (previous != null)
154                return previous;
155            return obj;
156        }
157
158        Map<Object, Object> cache = DataMergeContext.getCache();
159        Object key = CacheKey.key(obj, owner, propertyName);
160        Object prev = cache.get(key);
161        Object next = obj;
162        if (prev != null) {
163            next = prev;
164        }
165        else if (obj instanceof Collection) {
166            next = mergeCollection(classGetter, (Collection<Object>)obj, previous, owner, propertyName);
167        }
168        else if (obj.getClass().isArray()) {
169            next = mergeArray(classGetter, obj, previous, owner, propertyName);
170        }
171        else if (obj instanceof Map) {
172            next = mergeMap(classGetter, (Map<Object, Object>)obj, previous, owner, propertyName);
173        }
174        else if (classGetter.isEntity(obj) || obj.getClass().isAnnotationPresent(ExternalizedBean.class)) {
175            next = mergeEntity(classGetter, obj, previous, owner, propertyName);
176        }
177
178        return next;
179    }
180    
181    protected boolean equals(Object obj1, Object obj2) {
182        // TODO: Should we add a check on class equality ???
183        if (obj1 instanceof IUID && obj2 instanceof IUID)
184                return ((IUID)obj1).getUid() != null && ((IUID)obj1).getUid().equals(((IUID)obj2).getUid());
185        
186        return obj1.equals(obj2);
187    }
188
189    private Object mergeEntity(ClassGetter classGetter, Object obj, Object previous, Object owner, String propertyName) {
190        Object dest = obj;
191        boolean isEntity = classGetter.isEntity(obj);
192        
193        boolean sameEntity = false;
194        if (isEntity) {
195                Object p = DataMergeContext.getLoadedEntity(obj);
196                if (p != null) {
197                        // We are sure here that the application has loaded the entity in the persistence context
198                        // It's safe to merge the incoming entity
199                        previous = p;
200                }
201        }
202        
203                sameEntity = previous != null && equals(previous, obj);
204        if (sameEntity)
205            dest = previous;
206        
207        DataMergeContext.getCache().put(CacheKey.key(obj, null, null), dest);
208        
209        List<Object[]> fieldValues = isEntity ? classGetter.getFieldValues(obj, dest) : DefaultClassGetter.defaultGetFieldValues(obj, dest);
210        // Merges field values
211        try {
212            for (Object[] fieldValue : fieldValues) {
213                Field field = (Field)fieldValue[0];
214                Object objv = fieldValue[1];
215                Object destv = fieldValue[2];
216                objv = mergeExternal(classGetter, objv, destv, obj, field.getName());
217                field.set(dest, objv);
218            }
219        }
220        catch (Exception e) {
221            throw new RuntimeException("Could not merge entity ", e);
222        }
223        
224        return dest;
225    }
226
227    private Object mergeCollection(ClassGetter classGetter, Collection<Object> coll, Object previous, Object owner, String propertyName) {
228        if (log.isDebugEnabled())
229            log.debug("Context mergeCollection: " + coll + (previous != null ? " previous " + previous.getClass().getName() : ""));
230
231        Map<Object, Object> cache = DataMergeContext.getCache();
232        Object key = CacheKey.key(coll, owner, propertyName);
233        if (previous != null && previous instanceof Collection<?>)
234            cache.put(key, previous);
235        else
236            cache.put(key, coll);
237        
238        @SuppressWarnings("unchecked")
239        Collection<Object> prevColl = previous instanceof Collection ? (Collection<Object>)previous : null;
240        
241        if (coll == prevColl) {
242                for (Object obj : coll)
243                mergeExternal(classGetter, obj, obj, null, null);
244        }
245        else {
246                List<Object> addedToColl = new ArrayList<Object>();
247                Iterator<Object> icoll = coll.iterator();
248                for (int i = 0; i < coll.size(); i++) {
249                    Object obj = icoll.next();
250                    if (prevColl instanceof List<?>) {
251                        boolean found = false;
252                        List<Object> prevList = (List<Object>)prevColl;
253                        for (int j = 0; j < prevList.size(); j++) {
254                            Object prev = prevList.get(j);
255                            if (prev != null && equals(prev, obj)) {
256                                obj = mergeExternal(classGetter, obj, prev, null, null);
257                                if (i < prevList.size()) {
258                                        if (j != i)
259                                            prevList.set(j, prevList.get(i));
260                                        
261                                        if (obj != prevList.get(i))
262                                                prevList.set(i, obj);
263                                }
264                                else if (obj != prevList.get(j))
265                                        prevList.set(j, obj);
266                                
267                                found = true;
268                            }
269                        }
270                        if (!found) {
271                            obj = mergeExternal(obj, null);
272                            prevColl.add(obj);
273                        }
274                    }
275                    else if (prevColl != null) {
276                        boolean found = false;
277                        Iterator<Object> iprevcoll = prevColl.iterator();
278                        List<Object> added = new ArrayList<Object>();
279                        for (int j = 0; j < prevColl.size(); j++) {
280                            Object prev = iprevcoll.next();
281                            if (prev != null && equals(prev, obj)) {
282                                obj = mergeExternal(classGetter, obj, prev, null, null);
283                                if (obj != prev) {
284                                    if (prevColl instanceof List<?>)
285                                        ((List<Object>)prevColl).set(j, obj);
286                                    else {
287                                        iprevcoll.remove();
288                                        added.add(obj);
289                                    }
290                                }
291                                found = true;
292                            }
293                        }
294                        prevColl.addAll(added);
295                        if (!found) {
296                            obj = mergeExternal(classGetter, obj, null, null, null);
297                            prevColl.add(obj);
298                        }
299                    }
300                    else {
301                        obj = mergeExternal(obj, null);
302                        if (icoll instanceof ListIterator<?>)
303                            ((ListIterator<Object>)icoll).set(obj);
304                        else
305                            addedToColl.add(obj);
306                    }
307                }
308                if (!addedToColl.isEmpty()) {
309                        coll.removeAll(addedToColl);    // Ensure that all entities are replaced by the merged ones 
310                        coll.addAll(addedToColl);
311                }
312                if (prevColl != null) {
313                    Iterator<Object> iprevcoll = prevColl.iterator();
314                    for (int i = 0; i < prevColl.size(); i++) {
315                        Object obj = iprevcoll.next();
316                        boolean found = false;
317                        for (Object next : coll) {
318                            if (next != null && equals(next, obj)) {
319                                found = true;
320                                break;
321                            }
322                        }
323                        if (!found) {
324                            iprevcoll.remove();
325                            i--;
326                        }
327                    }
328        
329                    return previous;
330                }
331        }
332
333        return coll;
334    }
335
336    private Object mergeArray(ClassGetter classGetter, Object array, Object previous, Object owner, String propertyName) {
337        if (log.isDebugEnabled())
338            log.debug("Context mergeArray: " + array + (previous != null ? " previous " + previous.getClass().getName() : ""));
339
340        Object key = CacheKey.key(array, owner, propertyName);
341        int length = Array.getLength(array);
342        Object prevArray = ArrayUtil.newArray(ArrayUtil.getComponentType(array.getClass()), length);
343        DataMergeContext.getCache().put(key, prevArray);
344        
345        for (int i = 0; i < length; i++) {
346            Object obj = Array.get(array, i);
347            Array.set(prevArray, i, mergeExternal(classGetter, obj, null, null, null));
348        }
349
350        return prevArray;
351    }
352
353    private Object mergeMap(ClassGetter classGetter, Map<Object, Object> map, Object previous, Object owner, String propertyName) {
354        if (log.isDebugEnabled())
355            log.debug("Context mergeMap: " + map + (previous != null ? " previous " + previous.getClass().getName() : ""));
356
357        Map<Object, Object> cache = DataMergeContext.getCache();
358        Object cacheKey = CacheKey.key(map, owner, propertyName);
359        if (previous != null && previous instanceof Map<?, ?>)
360            cache.put(cacheKey, previous);
361        else
362            cache.put(cacheKey, map);
363        
364        @SuppressWarnings("unchecked")
365        Map<Object, Object> prevMap = previous instanceof Map ? (Map<Object, Object>)previous : null;
366        
367        if (map == prevMap) {
368                for (Map.Entry<Object, Object> me : map.entrySet()) {
369                        mergeExternal(classGetter, me.getKey(), null, null, null);
370                        mergeExternal(classGetter, me.getValue(), null, null, null);
371                }
372        }
373        else {
374                if (prevMap != null) {
375                    if (map != prevMap) {
376                        prevMap.clear();
377                        for (Map.Entry<Object, Object> me : map.entrySet()) {
378                            Object key = mergeExternal(classGetter, me.getKey(), null, null, null);
379                            Object value = mergeExternal(classGetter, me.getValue(), null, null, null);
380                            prevMap.put(key, value);
381                        }
382                    }
383                    
384                    return prevMap;
385                }
386                
387                Set<Object[]> addedToMap = new HashSet<Object[]>();
388                for (Iterator<Map.Entry<Object, Object>> ime = map.entrySet().iterator(); ime.hasNext(); ) {
389                    Map.Entry<Object, Object> me = ime.next();
390                    ime.remove();
391                    Object key = mergeExternal(classGetter, me.getKey(), null, null, null);
392                    Object value = mergeExternal(classGetter, me.getValue(), null, null, null);
393                    addedToMap.add(new Object[] { key, value });
394                }
395                for (Object[] me : addedToMap)
396                    map.put(me[0], me[1]);
397        }
398
399        return map;
400    }
401    
402    
403    /**
404     * Initialize the lazy property for the passed in entity.
405     * @param entity the entity that has a lazy relationship
406     * @param propertyNames the properties of the entity that has been marked lazy
407     * @return the lazy collection
408     */
409    public Object lazyInitialize(Object entity, String[] propertyNames)  {
410        TidePersistenceManager pm = getTidePersistenceManager(true);
411        if (pm == null) {
412            log.warn("No persistence manager found: lazy initialization ignored for " + entity);
413            return entity;
414        }
415        
416        return pm.attachEntity(entity, propertyNames);
417    }
418    
419}