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