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