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    
021    package org.granite.tide;
022    
023    import java.io.Serializable;
024    import java.lang.annotation.Annotation;
025    import java.lang.reflect.Array;
026    import java.lang.reflect.Field;
027    import java.lang.reflect.Method;
028    import java.util.ArrayList;
029    import java.util.Collection;
030    import java.util.HashSet;
031    import java.util.Iterator;
032    import java.util.List;
033    import java.util.ListIterator;
034    import java.util.Map;
035    import java.util.Set;
036    
037    import org.granite.context.GraniteContext;
038    import org.granite.logging.Logger;
039    import org.granite.messaging.amf.io.util.ClassGetter;
040    import org.granite.messaging.amf.io.util.DefaultClassGetter;
041    import org.granite.messaging.amf.io.util.externalizer.annotation.ExternalizedBean;
042    import org.granite.messaging.service.ServiceException;
043    import org.granite.messaging.service.ServiceInvocationContext;
044    import org.granite.tide.async.AsyncPublisher;
045    import org.granite.tide.data.DataMergeContext;
046    import org.granite.tide.data.DataMergeContext.CacheKey;
047    import org.granite.util.ArrayUtil;
048    
049    
050    /**
051     * @author William DRAI
052     */
053    public 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    }