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 */
022 package org.granite.tide.data;
023
024 import java.io.Serializable;
025 import java.lang.reflect.Array;
026 import java.lang.reflect.Field;
027 import java.lang.reflect.InvocationTargetException;
028 import java.lang.reflect.ParameterizedType;
029 import java.lang.reflect.Type;
030 import java.util.Collection;
031 import java.util.HashMap;
032 import java.util.HashSet;
033 import java.util.Iterator;
034 import java.util.List;
035 import java.util.Map;
036 import java.util.Map.Entry;
037 import java.util.Set;
038
039 import org.granite.context.GraniteContext;
040 import org.granite.logging.Logger;
041 import org.granite.messaging.amf.io.convert.Converters;
042 import org.granite.messaging.amf.io.util.ClassGetter;
043 import org.granite.messaging.service.ServiceException;
044 import org.granite.util.Introspector;
045 import org.granite.util.PropertyDescriptor;
046 import org.granite.util.TypeUtil;
047 import org.granite.util.Entity;
048
049
050 /**
051 * Utility class that applies a ChangeSet on a persistence context
052 * @author William DRAI
053 *
054 */
055 public 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 }