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    }