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