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 }