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 }