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 }