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.DataContext.EntityUpdate;
043import org.granite.tide.data.DataContext.EntityUpdateType;
044import org.granite.tide.data.DataUtils;
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 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}