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