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 }