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.hibernate;
023    
024    import static org.granite.util.Reflections.get;
025    
026    import java.io.Serializable;
027    import java.util.ArrayList;
028    import java.util.Collection;
029    import java.util.HashMap;
030    import java.util.HashSet;
031    import java.util.List;
032    import java.util.Map;
033    import java.util.Map.Entry;
034    import java.util.Set;
035    
036    import javax.persistence.Entity;
037    
038    import org.granite.tide.data.Change;
039    import org.granite.tide.data.ChangeRef;
040    import org.granite.tide.data.CollectionChange;
041    import org.granite.tide.data.DataContext;
042    import org.granite.tide.data.DataContext.EntityUpdate;
043    import org.granite.tide.data.DataContext.EntityUpdateType;
044    import org.granite.tide.data.DataUtils;
045    import org.hibernate.EntityMode;
046    import org.hibernate.HibernateException;
047    import org.hibernate.collection.PersistentCollection;
048    import org.hibernate.engine.CollectionEntry;
049    import org.hibernate.event.FlushEntityEvent;
050    import org.hibernate.event.FlushEntityEventListener;
051    import org.hibernate.event.PostDeleteEvent;
052    import org.hibernate.event.PostDeleteEventListener;
053    import org.hibernate.event.PostInsertEvent;
054    import org.hibernate.event.PostInsertEventListener;
055    import org.hibernate.event.PostUpdateEvent;
056    import org.hibernate.event.PostUpdateEventListener;
057    import org.hibernate.event.PreCollectionUpdateEvent;
058    import org.hibernate.event.PreCollectionUpdateEventListener;
059    import org.hibernate.event.def.DefaultFlushEntityEventListener;
060    import org.hibernate.persister.entity.EntityPersister;
061    
062    /**
063     * @author William DRAI
064     */
065    public class Hibernate35DataChangePublishListener implements PostInsertEventListener, PostUpdateEventListener,
066            PostDeleteEventListener, PreCollectionUpdateEventListener, FlushEntityEventListener {
067            
068            private static final long serialVersionUID = 1L;
069            
070            private DefaultFlushEntityEventListener defaultFlushEntityEventListener = new DefaultFlushEntityEventListener();
071    
072    
073        public void onPostInsert(PostInsertEvent event) {
074            DataContext.addUpdate(EntityUpdateType.PERSIST, event.getEntity());
075        }
076    
077        public void onPostUpdate(PostUpdateEvent event) {
078            int[] dirtyProperties = (int[])DataContext.getEntityExtraData(event.getEntity());
079            if (dirtyProperties != null && dirtyProperties.length > 0) {
080                    Change change = getChange(event.getPersister(), event.getPersister().getEntityName(), event.getId(), event.getEntity());
081                    if (change != null) {
082                            for (int i = 0; i < dirtyProperties.length; i++) {
083                                    int pidx = dirtyProperties[i];
084                                    change.getChanges().put(event.getPersister().getPropertyNames()[pidx], event.getState()[pidx]);
085                            }
086                    }
087                    else
088                            DataContext.addUpdate(EntityUpdateType.UPDATE, event.getEntity());
089            }
090        }
091    
092        public void onPostDelete(PostDeleteEvent event) {
093            String uid = getUid(event.getPersister(), event.getEntity());
094            if (uid != null) {
095                            ChangeRef deleteRef = new ChangeRef(event.getPersister().getEntityName(), uid, event.getId());
096                    DataContext.addUpdate(EntityUpdateType.REMOVE, deleteRef);
097            }
098            else
099                    DataContext.addUpdate(EntityUpdateType.REMOVE, event.getEntity());
100        }
101        
102        private static Change getChange(EntityPersister persister, String entityName, Serializable id, Object entity) {
103            Number version = (Number)persister.getVersion(entity, EntityMode.POJO);
104            String uid = getUid(persister, entity);
105            if (uid == null)
106                    return null;
107            
108                    Change change = null;
109            for (EntityUpdate du : DataContext.get().getDataUpdates()) {
110                    if (du.type.equals(EntityUpdateType.UPDATE.name()) 
111                                    && ((Change)du.entity).getClassName().equals(entityName)
112                            && ((Change)du.entity).getId().equals(id)) {
113                            change = (Change)du.entity;
114                            break;
115                    }
116            }
117            if (change == null) {
118                    change = new Change(entityName, id, version, uid);
119                    DataContext.addUpdate(EntityUpdateType.UPDATE, change, 1);
120            }
121                    return change;
122        }
123    
124            private static String getUid(EntityPersister persister, Object entity) {
125                    for (int i = 0; i < persister.getPropertyNames().length; i++) {
126                            if ("uid".equals(persister.getPropertyNames()[i]))
127                                    return (String)persister.getPropertyValue(entity, i, EntityMode.POJO);
128                    }
129                    return null;
130            }
131    
132        @SuppressWarnings("unchecked")
133            public void onPreUpdateCollection(PreCollectionUpdateEvent event) {
134            Object owner = event.getAffectedOwnerOrNull();
135            if (owner == null)
136                    return;
137            
138            CollectionEntry collectionEntry = event.getSession().getPersistenceContext().getCollectionEntry(event.getCollection());
139            
140            Change change = getChange(collectionEntry.getLoadedPersister().getOwnerEntityPersister(), event.getAffectedOwnerEntityName(), event.getAffectedOwnerIdOrNull(), owner);
141            if (change == null)
142                    return;
143            
144            PersistentCollection newColl = event.getCollection();
145            Serializable oldColl = collectionEntry.getSnapshot();
146            String propertyName = collectionEntry.getRole().substring(collectionEntry.getLoadedPersister().getOwnerEntityPersister().getEntityName().length()+1);
147            
148            if (oldColl == null && newColl.hasQueuedOperations()) {
149                    List<Object[]> added = new ArrayList<Object[]>();                   
150                    List<Object[]> removed = new ArrayList<Object[]>();
151                    List<Object[]> updated = new ArrayList<Object[]>();
152                    
153                            List<?> queuedOperations = get(newColl, "operationQueue", List.class);
154                            for (Object op : queuedOperations) {
155                                    // Great !!
156                                    if (op.getClass().getName().endsWith("$Add")) {
157                                            added.add(new Object[] { get(op, "index"), get(op, "value") });
158                                    }
159                                    else if (op.getClass().getName().endsWith("$SimpleAdd")) {
160                                            added.add(new Object[] { null, get(op, "value") });
161                                    }
162                                    else if (op.getClass().getName().endsWith("$Put")) {
163                                            added.add(new Object[] { get(op, "index"), get(op, "value") });
164                                    }
165                                    else if (op.getClass().getName().endsWith("$Remove")) {
166                                            removed.add(new Object[] { get(op, "index"), get(op, "old") });
167                                    }
168                                    else if (op.getClass().getName().endsWith("$SimpleRemove")) {
169                                            removed.add(new Object[] { null, get(op, "value") });
170                                    }
171                                    else if (op.getClass().getName().endsWith("$Set")) {
172                                            updated.add(new Object[] { get(op, "index"), get(op, "value") });
173                                    }
174                            }
175                            
176                    CollectionChange[] collChanges = new CollectionChange[added.size()+removed.size()+updated.size()];
177                    int idx = 0;
178                    for (Object[] obj : added)
179                            collChanges[idx++] = new CollectionChange(1, obj[0], obj[1]);
180                    
181                    for (Object[] obj : removed) {
182                            Object value = obj[1];
183                            if (value != null && value.getClass().isAnnotationPresent(Entity.class)) {
184                                    org.granite.util.Entity e = new org.granite.util.Entity(value);
185                                    value = new ChangeRef(e.getName(), (String)get(value, "uid"), (Serializable)e.getIdentifier());
186                            }
187                            collChanges[idx++] = new CollectionChange(-1, obj[0], value);
188                    }
189                    
190                    for (Object[] obj : updated)
191                            collChanges[idx++] = new CollectionChange(0, obj[0], obj[1]);
192                    
193                    change.addCollectionChanges(propertyName, collChanges);
194            }
195            else if (oldColl != null && newColl instanceof List<?>) {
196                    List<?> oldSnapshot = (List<?>)oldColl;
197                    
198                    List<?> newList = (List<?>)newColl;
199                    
200                    List<Object[]> ops = DataUtils.diffLists(oldSnapshot, newList);
201                    
202                    CollectionChange[] collChanges = new CollectionChange[ops.size()];
203                    int idx = 0;
204                    for (Object[] obj : ops)
205                            collChanges[idx++] = new CollectionChange((Integer)obj[0], obj[1], obj[2]);
206                    
207                    change.addCollectionChanges(propertyName, collChanges);
208            }
209            else if (oldColl != null && newColl instanceof Collection<?>) {
210                    Map<?, ?> oldSnapshot = (Map<?, ?>)oldColl;
211                    
212                    Set<Object> added = new HashSet<Object>();
213                    added.addAll((Collection<?>)newColl);
214                    added.removeAll(new HashSet<Object>(oldSnapshot.keySet()));
215                    
216                    Set<Object> removed = new HashSet<Object>();
217                    removed.addAll(new HashSet<Object>(oldSnapshot.keySet()));
218                    removed.removeAll((Collection<?>)newColl);
219                    
220                    CollectionChange[] collChanges = new CollectionChange[added.size()+removed.size()];
221                    int idx = 0;
222                    for (Object obj : added)
223                            collChanges[idx++] = new CollectionChange(1, null, obj);
224                    
225                    for (Object obj : removed)
226                            collChanges[idx++] = new CollectionChange(-1, null, obj);
227                    
228                    change.addCollectionChanges(propertyName, collChanges);
229            }
230            else if (oldColl != null && newColl instanceof Map<?, ?>) {
231                    Map<?, ?> oldSnapshot = (Map<?, ?>)oldColl;
232                    
233                    Set<Entry<Object, Object>> added = new HashSet<Entry<Object, Object>>();
234                    added.addAll(((Map<Object, Object>)newColl).entrySet());
235                    added.removeAll(new HashMap<Object, Object>(oldSnapshot).entrySet());
236                    
237                    Set<Entry<Object, Object>> removed = new HashSet<Entry<Object, Object>>();
238                    removed.addAll(new HashMap<Object, Object>(oldSnapshot).entrySet());
239                    removed.removeAll(((Map<Object, Object>)newColl).entrySet());
240                    
241                    CollectionChange[] collChanges = new CollectionChange[added.size()+removed.size()];
242                    int idx = 0;
243                    for (Entry<?, ?> me : added)
244                            collChanges[idx++] = new CollectionChange(1, me.getKey(), me.getValue());
245                    
246                    for (Entry<?, ?> me : removed)
247                            collChanges[idx++] = new CollectionChange(-1, me.getKey(), me.getValue());
248                    
249                    change.addCollectionChanges(propertyName, collChanges);
250            }
251        }
252    
253            public void onFlushEntity(FlushEntityEvent event) throws HibernateException {
254                    defaultFlushEntityEventListener.onFlushEntity(event);
255                    
256                    Object entity = event.getEntity();
257                    if (event.getDirtyProperties() != null && event.getDirtyProperties().length > 0) {
258                            int[] dirtyProperties = (int[])DataContext.getEntityExtraData(entity);
259                            int[] newDirtyProperties;
260                            if (dirtyProperties == null)
261                                    newDirtyProperties = event.getDirtyProperties();
262                            else {
263                                    newDirtyProperties = new int[dirtyProperties.length + event.getDirtyProperties().length];
264                                    System.arraycopy(dirtyProperties, 0, newDirtyProperties, 0, dirtyProperties.length);
265                                    System.arraycopy(event.getDirtyProperties(), 0, newDirtyProperties, dirtyProperties.length, event.getDirtyProperties().length);
266                            }
267                            
268                            DataContext.addEntityExtraData(entity, newDirtyProperties);
269                    }
270            }
271        
272    }