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 }