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.data; 023 024import java.io.Serializable; 025import java.lang.reflect.Array; 026import java.lang.reflect.Field; 027import java.lang.reflect.InvocationTargetException; 028import java.lang.reflect.ParameterizedType; 029import java.lang.reflect.Type; 030import java.util.Collection; 031import java.util.HashMap; 032import java.util.HashSet; 033import java.util.Iterator; 034import java.util.List; 035import java.util.Map; 036import java.util.Map.Entry; 037import java.util.Set; 038 039import org.granite.context.GraniteContext; 040import org.granite.logging.Logger; 041import org.granite.messaging.amf.io.convert.Converters; 042import org.granite.messaging.amf.io.util.ClassGetter; 043import org.granite.messaging.service.ServiceException; 044import org.granite.util.Introspector; 045import org.granite.util.PropertyDescriptor; 046import org.granite.util.TypeUtil; 047import org.granite.util.Entity; 048 049 050/** 051 * Utility class that applies a ChangeSet on a persistence context 052 * @author William DRAI 053 * 054 */ 055public class ChangeSetApplier { 056 057 private static final Logger log = Logger.getLogger(ChangeSetApplier.class); 058 059 private TidePersistenceAdapter persistenceAdapter; 060 061 public ChangeSetApplier(TidePersistenceAdapter persistenceAdapter) { 062 this.persistenceAdapter = persistenceAdapter; 063 } 064 065 protected long getVersion(Entity e) { 066 if (!e.isVersioned()) 067 throw new IllegalStateException("Cannot apply change set on non versioned entity " + e.getName()); 068 069 Number version = (Number)e.getVersion(); 070 if (version == null) 071 throw new IllegalStateException("Cannot apply changes on non persistent entity " + e.getName() + ":" + e.getIdentifier()); 072 073 return version.longValue(); 074 } 075 076 077 protected Object mergeObject(Object entity, Set<Object> cache) { 078 if (entity == null) 079 return null; 080 081 ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter(); 082 Converters converters = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters(); 083 084 if (entity != null && !classGetter.isInitialized(null, null, entity)) { 085 // cache.contains() cannot be called on un unintialized proxy because hashCode will fail !! 086 Class<?> cls = classGetter.getClass(entity); 087 Entity e = new Entity(cls); 088 return persistenceAdapter.find(cls, (Serializable)converters.convert(classGetter.getIdentifier(entity), e.getIdentifierType())); 089 } 090 091 if (cache.contains(entity)) 092 return entity; 093 094 if (entity instanceof ChangeRef) { 095 ChangeRef ref = (ChangeRef)entity; 096 try { 097 Class<?> entityClass = TypeUtil.forName(ref.getClassName()); 098 Entity e = new Entity(entityClass); 099 Serializable refId = (Serializable)converters.convert(ref.getId(), e.getIdentifierType()); 100 101 // Lookup in current merge cache, if not found then lookup from persistence adapter 102 for (Object cached : cache) { 103 if (cached.getClass().equals(entityClass) && refId.equals(e.getIdentifier(cached))) 104 return cached; 105 } 106 107 return persistenceAdapter.find(entityClass, refId); 108 } 109 catch (ClassNotFoundException cnfe) { 110 throw new ServiceException("Could not find class " + ref.getClassName(), cnfe); 111 } 112 } 113 114 cache.add(entity); 115 116 if (entity != null && classGetter.isEntity(entity) && classGetter.isInitialized(null, null, entity)) { 117 // If the entity has an id, we should get the managed instance 118 Entity e = new Entity(entity); 119 Object id = e.getIdentifier(); 120 if (id != null) 121 return persistenceAdapter.find(entity.getClass(), (Serializable)id); 122 123 cache.add(entity); 124 125 // If there is no id, traverse the object graph to merge associated objects 126 List<Object[]> fieldValues = classGetter.getFieldValues(entity); 127 for (Object[] fieldValue : fieldValues) { 128 Object value = fieldValue[1]; 129 Field field = (Field)fieldValue[0]; 130 if (value == null) 131 continue; 132 133 if (!classGetter.isInitialized(entity, field.getName(), value)) { 134 if (!classGetter.getClass(value).isAnnotationPresent(javax.persistence.Entity.class)) 135 continue; 136 137 try { 138 // Lookup in cache 139 Serializable valueId = classGetter.getIdentifier(value); 140 Object newValue = null; 141 Entity ve = new Entity(field.getType()); 142 for (Object cached : cache) { 143 if (field.getType().isInstance(cached) && valueId.equals(ve.getIdentifier(cached))) { 144 newValue = cached; 145 break; 146 } 147 } 148 if (newValue == null) 149 newValue = persistenceAdapter.find(field.getType(), valueId); 150 151 field.set(entity, newValue); 152 } 153 catch (IllegalAccessException e1) { 154 throw new ServiceException("Could not set entity field value on " + value.getClass() + "." + field.getName()); 155 } 156 } 157 158 if (value instanceof Collection<?>) { 159 @SuppressWarnings("unchecked") 160 Collection<Object> coll = (Collection<Object>)value; 161 Iterator<Object> icoll = coll.iterator(); 162 Set<Object> addedElements = new HashSet<Object>(); 163 int idx = 0; 164 while (icoll.hasNext()) { 165 Object element = icoll.next(); 166 if (element != null) { 167 Object newElement = mergeObject(element, cache); 168 if (newElement != element) { 169 if (coll instanceof List<?>) 170 ((List<Object>)coll).set(idx, newElement); 171 else { 172 icoll.remove(); 173 addedElements.add(newElement); 174 } 175 } 176 } 177 idx++; 178 } 179 if (!(coll instanceof List<?>)) 180 coll.addAll(addedElements); 181 } 182 else if (value.getClass().isArray()) { 183 for (int idx = 0; idx < Array.getLength(value); idx++) { 184 Object element = Array.get(value, idx); 185 if (element == null) 186 continue; 187 Object newElement = mergeObject(element, cache); 188 if (newElement != element) 189 Array.set(value, idx, newElement); 190 } 191 } 192 else if (value instanceof Map<?, ?>) { 193 @SuppressWarnings("unchecked") 194 Map<Object, Object> map = (Map<Object, Object>)value; 195 Iterator<Entry<Object, Object>> ime = map.entrySet().iterator(); 196 Map<Object, Object> addedElements = new HashMap<Object, Object>(); 197 while (ime.hasNext()) { 198 Entry<Object, Object> me = ime.next(); 199 Object val = me.getValue(); 200 if (val != null) { 201 Object newVal = mergeObject(val, cache); 202 if (newVal != val) 203 me.setValue(newVal); 204 } 205 Object key = me.getKey(); 206 if (key != null) { 207 Object newKey = mergeObject(key, cache); 208 if (newKey != key) { 209 ime.remove(); 210 addedElements.put(newKey, me.getValue()); 211 } 212 } 213 } 214 map.putAll(addedElements); 215 } 216 else if (classGetter.isEntity(value)) { 217 Object newValue = mergeObject(value, cache); 218 if (newValue != value) { 219 try { 220 field.set(entity, newValue); 221 } 222 catch (IllegalAccessException e1) { 223 throw new ServiceException("Could not set entity field value on " + value.getClass() + "." + field.getName()); 224 } 225 } 226 } 227 } 228 } 229 230 return entity; 231 } 232 233 public Object[] applyChanges(ChangeSet changeSet) { 234 Set<Object> cache = new HashSet<Object>(); 235 Object[] appliedChanges = new Object[changeSet.getChanges().length]; 236 for (int i = 0; i < changeSet.getChanges().length; i++) 237 appliedChanges[i] = applyChange(changeSet.getChanges()[i], cache); 238 239 return appliedChanges; 240 } 241 242 @SuppressWarnings("unchecked") 243 private Object applyChange(Change change, Set<Object> cache) { 244 Converters converters = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters(); 245 246 Object appliedChange = null; 247 try { 248 Class<?> entityClass = TypeUtil.forName(change.getClassName()); 249 if (change.getId() != null) { 250 Entity e = new Entity(entityClass); 251 Type identifierType = e.getIdentifierType(); 252 Object entity = persistenceAdapter.find(entityClass, (Serializable)converters.convert(change.getId(), identifierType)); 253 if (entity == null) { 254 log.debug("Entity not found, maybe has already been deleted by cascading"); 255 return null; 256 } 257 258 e = new Entity(entity); 259 Long version = getVersion(e); 260 if ((change.getVersion() != null && change.getVersion().longValue() < version) || (change.getVersion() == null && version != null)) 261 persistenceAdapter.throwOptimisticLockException(entity); 262 263 appliedChange = entity; 264 265 for (Entry<String, Object> me : change.getChanges().entrySet()) { 266 try { 267 PropertyDescriptor[] propertyDescriptors = Introspector.getPropertyDescriptors(entityClass); 268 PropertyDescriptor propertyDescriptor = null; 269 for (PropertyDescriptor pd : propertyDescriptors) { 270 if (pd.getName().equals(me.getKey())) { 271 propertyDescriptor = pd; 272 break; 273 } 274 } 275 if (propertyDescriptor == null) { 276 log.warn("Could not find property " + me.getKey() + " on class " + change.getClassName()); 277 } 278 else { 279 if (me.getValue() instanceof CollectionChanges) { 280 Object collection = propertyDescriptor.getReadMethod().invoke(entity); 281 282 CollectionChanges collectionChanges = (CollectionChanges)me.getValue(); 283 for (CollectionChange collectionChange : collectionChanges.getChanges()) { 284 if (collectionChange.getType() == 1) { 285 if (collection instanceof Set<?>) { 286 Type collectionType = propertyDescriptor.getReadMethod().getGenericReturnType(); 287 Type elementType = Object.class; 288 if (collectionType instanceof ParameterizedType) 289 elementType = ((ParameterizedType)collectionType).getActualTypeArguments()[0]; 290 291 Object value = converters.convert(mergeObject(collectionChange.getValue(), cache), elementType); 292 ((Set<Object>)collection).add(value); 293 } 294 else if (collection instanceof List<?>) { 295 Type collectionType = propertyDescriptor.getReadMethod().getGenericReturnType(); 296 Type elementType = Object.class; 297 if (collectionType instanceof ParameterizedType) 298 elementType = ((ParameterizedType)collectionType).getActualTypeArguments()[0]; 299 300 Object value = converters.convert(mergeObject(collectionChange.getValue(), cache), elementType); 301 ((List<Object>)collection).add((Integer)collectionChange.getKey(), value); 302 } 303 else if (collection instanceof Map<?, ?>) { 304 Type mapType = propertyDescriptor.getReadMethod().getGenericReturnType(); 305 Type keyType = Object.class; 306 Type valueType = Object.class; 307 if (mapType instanceof ParameterizedType) { 308 keyType = ((ParameterizedType)mapType).getActualTypeArguments()[0]; 309 valueType = ((ParameterizedType)mapType).getActualTypeArguments()[1]; 310 } 311 312 Object key = converters.convert(mergeObject(collectionChange.getKey(), cache), keyType); 313 Object value = converters.convert(mergeObject(collectionChange.getValue(), cache), valueType); 314 ((Map<Object, Object>)collection).put(key, value); 315 } 316 } 317 else if (collectionChange.getType() == -1) { 318 if (collection instanceof Set<?>) { 319 Type collectionType = propertyDescriptor.getReadMethod().getGenericReturnType(); 320 Type elementType = Object.class; 321 if (collectionType instanceof ParameterizedType) 322 elementType = ((ParameterizedType)collectionType).getActualTypeArguments()[0]; 323 324 Object value = converters.convert(mergeObject(collectionChange.getValue(), cache), elementType); 325 Object removed = ((Set<Object>)collection).remove(value); 326 cache.add(removed); 327 } 328 else if (collection instanceof List<?>) { 329 int index = (Integer)collectionChange.getKey(); 330 Object removed = ((List<Object>)collection).remove(index); 331 cache.add(removed); 332 } 333 else if (collection instanceof Map<?, ?>) { 334 Type mapType = propertyDescriptor.getReadMethod().getGenericReturnType(); 335 Type keyType = Object.class; 336 if (mapType instanceof ParameterizedType) 337 keyType = ((ParameterizedType)mapType).getActualTypeArguments()[0]; 338 339 Object key = converters.convert(mergeObject(collectionChange.getKey(), cache), keyType); 340 Object removed = ((Map<Object, Object>)collection).remove(key); 341 cache.add(removed); 342 } 343 } 344 else if (collectionChange.getType() == 0) { 345 if (collection instanceof Set<?>) { 346 // This should not happen with a Set !! 347 throw new IllegalStateException("Cannot replace an indexed element on a Set, don't use setItemAt on an ArrayCollection representing a Set !"); 348 } 349 else if (collection instanceof List<?>) { 350 int index = (Integer)collectionChange.getKey(); 351 Type collectionType = propertyDescriptor.getReadMethod().getGenericReturnType(); 352 Type elementType = Object.class; 353 if (collectionType instanceof ParameterizedType) 354 elementType = ((ParameterizedType)collectionType).getActualTypeArguments()[0]; 355 356 Object value = converters.convert(mergeObject(collectionChange.getValue(), cache), elementType); 357 ((List<Object>)collection).set(index, value); 358 } 359 else if (collection instanceof Map<?, ?>) { 360 Type mapType = propertyDescriptor.getReadMethod().getGenericReturnType(); 361 Type keyType = Object.class; 362 Type valueType = Object.class; 363 if (mapType instanceof ParameterizedType) { 364 keyType = ((ParameterizedType)mapType).getActualTypeArguments()[0]; 365 valueType = ((ParameterizedType)mapType).getActualTypeArguments()[1]; 366 } 367 368 Object key = converters.convert(mergeObject(collectionChange.getKey(), cache), keyType); 369 Object value = converters.convert(mergeObject(collectionChange.getValue(), cache), valueType); 370 ((Map<Object, Object>)collection).put(key, value); 371 } 372 } 373 } 374 } 375 else { 376 if (propertyDescriptor.getWriteMethod() != null) { 377 Object value = mergeObject(me.getValue(), cache); 378 value = converters.convert(value, propertyDescriptor.getWriteMethod().getGenericParameterTypes()[0]); 379 propertyDescriptor.getWriteMethod().invoke(entity, value); 380 } 381 else 382 log.warn("Property " + me.getKey() + " on class " + change.getClassName() + " is not writeable"); 383 } 384 } 385 } 386 catch (InvocationTargetException ite) { 387 throw new ServiceException("Could not set property " + me.getKey(), ite.getTargetException()); 388 } 389 catch (IllegalAccessException iae) { 390 throw new ServiceException("Could not set property " + me.getKey(), iae); 391 } 392 } 393 } 394 395 return appliedChange; 396 } 397 catch (ClassNotFoundException cnfe) { 398 throw new ServiceException("Could not find class " + change.getClassName(), cnfe); 399 } 400 } 401 402}