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