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