/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Granite Data Services is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.tide.hibernate4; import static org.granite.util.Reflections.get; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.persistence.Entity; import org.granite.tide.data.AnnotationUtils; import org.granite.tide.data.Change; import org.granite.tide.data.ChangeRef; import org.granite.tide.data.CollectionChange; import org.granite.tide.data.DataContext; import org.granite.tide.data.DataContext.EntityUpdate; import org.granite.tide.data.DataContext.EntityUpdateType; import org.granite.tide.data.DataPublishListener; import org.granite.tide.data.ExcludeFromDataPublish; import org.granite.tide.data.Utils; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.event.internal.DefaultFlushEntityEventListener; import org.hibernate.event.spi.FlushEntityEvent; import org.hibernate.event.spi.FlushEntityEventListener; import org.hibernate.event.spi.PostDeleteEvent; import org.hibernate.event.spi.PostDeleteEventListener; import org.hibernate.event.spi.PostInsertEvent; import org.hibernate.event.spi.PostInsertEventListener; import org.hibernate.event.spi.PostUpdateEvent; import org.hibernate.event.spi.PostUpdateEventListener; import org.hibernate.event.spi.PreCollectionUpdateEvent; import org.hibernate.event.spi.PreCollectionUpdateEventListener; import org.hibernate.persister.entity.EntityPersister; /** * @author William Drai */ public class HibernateDataChangePublishListener implements PostInsertEventListener, PostUpdateEventListener, PostDeleteEventListener, PreCollectionUpdateEventListener, FlushEntityEventListener { private static final long serialVersionUID = 1L; private DefaultFlushEntityEventListener defaultFlushEntityEventListener = new DefaultFlushEntityEventListener(); public void onPostInsert(PostInsertEvent event) { if (DataPublishListener.handleExclude(event.getEntity())) return; DataContext.addUpdate(EntityUpdateType.PERSIST, event.getEntity(), event.getEntity()); } public void onPostUpdate(PostUpdateEvent event) { if (DataPublishListener.handleExclude(event.getEntity())) return; if (event.getDirtyProperties() != null && event.getDirtyProperties().length > 0) { Object change = getChange(event.getPersister(), event.getSession(), event.getPersister().getEntityName(), event.getId(), event.getEntity()); if (change instanceof Change) { for (int i = 0; i < event.getDirtyProperties().length; i++) { int pidx = event.getDirtyProperties()[i]; String pname = event.getPersister().getPropertyNames()[pidx]; if (AnnotationUtils.isAnnotatedWith(event.getEntity(), pname, ExcludeFromDataPublish.class)) continue; ((Change)change).getChanges().put(pname, event.getState()[pidx]); } } else if (change == null) DataContext.addUpdate(EntityUpdateType.UPDATE, event.getEntity(), event.getEntity()); } } public void onPostDelete(PostDeleteEvent event) { if (DataPublishListener.handleExclude(event.getEntity())) return; String uid = getUid(event.getPersister(), event.getEntity()); if (uid != null) { ChangeRef deleteRef = new ChangeRef(event.getPersister().getEntityName(), uid, event.getId()); DataContext.addUpdate(EntityUpdateType.REMOVE, deleteRef, event.getEntity()); } else DataContext.addUpdate(EntityUpdateType.REMOVE, event.getEntity(), event.getEntity()); } private static Object getChange(EntityPersister persister, Session session, String entityName, Serializable id, Object entity) { Number version = (Number)persister.getVersion(entity); String uid = getUid(persister, entity); if (uid == null) return null; Object change = null; for (EntityUpdate du : DataContext.get().getDataUpdates()) { if (du.entity instanceof Change) { if (du.type == EntityUpdateType.UPDATE && ((Change)du.entity).getClassName().equals(entityName) && ((Change)du.entity).getId().equals(id)) { change = du.entity; break; } } else if (du.entity.getClass().getName().equals(entityName) && id.equals(persister.getIdentifier(entity, (SessionImplementor)session))) { change = du.entity; break; } } if (change == null) { change = new Change(entityName, id, version, uid); DataContext.addUpdate(EntityUpdateType.UPDATE, change, entity, 1); } else if (change instanceof Change) ((Change)change).updateVersion(version); return change; } private static String getUid(EntityPersister persister, Object entity) { for (int i = 0; i < persister.getPropertyNames().length; i++) { if ("uid".equals(persister.getPropertyNames()[i])) return (String)persister.getPropertyValue(entity, i); } return null; } @SuppressWarnings("unchecked") public void onPreUpdateCollection(PreCollectionUpdateEvent event) { Object owner = event.getAffectedOwnerOrNull(); if (DataPublishListener.handleExclude(owner)) return; CollectionEntry collectionEntry = event.getSession().getPersistenceContext().getCollectionEntry(event.getCollection()); String propertyName = collectionEntry.getRole().substring(collectionEntry.getLoadedPersister().getOwnerEntityPersister().getEntityName().length()+1); Object change = getChange(collectionEntry.getLoadedPersister().getOwnerEntityPersister(), event.getSession(), event.getAffectedOwnerEntityName(), event.getAffectedOwnerIdOrNull(), owner); if (change == null || !(change instanceof Change)) return; PersistentCollection newColl = event.getCollection(); Serializable oldColl = collectionEntry.getSnapshot(); if (oldColl == null && newColl.hasQueuedOperations()) { List<Object[]> added = new ArrayList<Object[]>(); List<Object[]> removed = new ArrayList<Object[]>(); List<Object[]> updated = new ArrayList<Object[]>(); List<?> queuedOperations = get(newColl, "operationQueue", List.class); for (Object op : queuedOperations) { // Great !! if (op.getClass().getName().endsWith("$Add")) { added.add(new Object[] { get(op, "index"), get(op, "value") }); } else if (op.getClass().getName().endsWith("$SimpleAdd")) { added.add(new Object[] { null, get(op, "value") }); } else if (op.getClass().getName().endsWith("$Put")) { added.add(new Object[] { get(op, "index"), get(op, "value") }); } else if (op.getClass().getName().endsWith("$Remove")) { removed.add(new Object[] { get(op, "index"), get(op, "old") }); } else if (op.getClass().getName().endsWith("$SimpleRemove")) { removed.add(new Object[] { null, get(op, "value") }); } else if (op.getClass().getName().endsWith("$Set")) { updated.add(new Object[] { get(op, "index"), get(op, "value") }); } } CollectionChange[] collChanges = new CollectionChange[added.size()+removed.size()+updated.size()]; int idx = 0; for (Object[] obj : added) collChanges[idx++] = new CollectionChange(1, obj[0], obj[1]); for (Object[] obj : removed) { Object value = obj[1]; if (value != null && value.getClass().isAnnotationPresent(Entity.class)) { org.granite.util.Entity e = new org.granite.util.Entity(value); value = new ChangeRef(e.getName(), (String)get(value, "uid"), (Serializable)e.getIdentifier()); } collChanges[idx++] = new CollectionChange(-1, obj[0], value); } for (Object[] obj : updated) collChanges[idx++] = new CollectionChange(0, obj[0], obj[1]); ((Change)change).addCollectionChanges(propertyName, collChanges); } else if (oldColl != null && newColl instanceof List<?>) { List<?> oldSnapshot = (List<?>)oldColl; List<?> newList = (List<?>)newColl; List<Object[]> ops = Utils.diffLists(oldSnapshot, newList); CollectionChange[] collChanges = new CollectionChange[ops.size()]; int idx = 0; for (Object[] obj : ops) collChanges[idx++] = new CollectionChange((Integer)obj[0], obj[1], obj[2]); ((Change)change).addCollectionChanges(propertyName, collChanges); } else if (oldColl != null && newColl instanceof Collection<?>) { Map<?, ?> oldSnapshot = (Map<?, ?>)oldColl; Set<Object> added = new HashSet<Object>(); added.addAll((Collection<?>)newColl); added.removeAll(new HashSet<Object>(oldSnapshot.keySet())); Set<Object> removed = new HashSet<Object>(); removed.addAll(new HashSet<Object>(oldSnapshot.keySet())); removed.removeAll((Collection<?>)newColl); CollectionChange[] collChanges = new CollectionChange[added.size()+removed.size()]; int idx = 0; for (Object obj : added) collChanges[idx++] = new CollectionChange(1, null, obj); for (Object obj : removed) collChanges[idx++] = new CollectionChange(-1, null, obj); ((Change)change).addCollectionChanges(propertyName, collChanges); } else if (oldColl != null && newColl instanceof Map<?, ?>) { Map<?, ?> oldSnapshot = (Map<?, ?>)oldColl; Set<Entry<Object, Object>> added = new HashSet<Entry<Object, Object>>(); added.addAll(((Map<Object, Object>)newColl).entrySet()); added.removeAll(new HashMap<Object, Object>(oldSnapshot).entrySet()); Set<Entry<Object, Object>> removed = new HashSet<Entry<Object, Object>>(); removed.addAll(new HashMap<Object, Object>(oldSnapshot).entrySet()); removed.removeAll(((Map<Object, Object>)newColl).entrySet()); CollectionChange[] collChanges = new CollectionChange[added.size()+removed.size()]; int idx = 0; for (Entry<?, ?> me : added) collChanges[idx++] = new CollectionChange(1, me.getKey(), me.getValue()); for (Entry<?, ?> me : removed) collChanges[idx++] = new CollectionChange(-1, me.getKey(), me.getValue()); ((Change)change).addCollectionChanges(propertyName, collChanges); } } public void onFlushEntity(FlushEntityEvent event) throws HibernateException { defaultFlushEntityEventListener.onFlushEntity(event); } @Override public boolean requiresPostCommitHanding(EntityPersister persister) { return false; } }