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}