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