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