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