Java tutorial
/* * Mirante Tecnologia * Copyright 2010, Mirante Informatica LTDA, * and individual contributors as indicated by the @authors tag * * This 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. * * This software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package com.miranteinfo.seam.hibernate; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.HibernateException; import org.hibernate.action.CollectionRecreateAction; import org.hibernate.action.CollectionRemoveAction; import org.hibernate.action.CollectionUpdateAction; import org.hibernate.collection.PersistentCollection; import org.hibernate.engine.ActionQueue; import org.hibernate.engine.CollectionEntry; import org.hibernate.engine.CollectionKey; import org.hibernate.engine.Collections; import org.hibernate.engine.EntityEntry; import org.hibernate.engine.PersistenceContext; import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.Status; import org.hibernate.event.EventSource; import org.hibernate.event.FlushEntityEvent; import org.hibernate.event.FlushEntityEventListener; import org.hibernate.event.FlushEvent; import org.hibernate.event.FlushEventListener; import org.hibernate.event.def.AbstractFlushingEventListener; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.Printer; import org.hibernate.util.IdentityMap; import org.hibernate.util.LazyIterator; import com.miranteinfo.seam.hibernate.envers.EnversConfiguration; /** * Override hibernate flush event listener to support our persist logic. * * @author lucas lins * */ @SuppressWarnings("rawtypes") public class HibernateFlushEventListener extends AbstractFlushingEventListener implements FlushEventListener { private EnversConfiguration envers; private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(HibernateFlushEventListener.class); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // constructor // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public HibernateFlushEventListener(EnversConfiguration envers) { this.envers = envers; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // on-flushing section // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** Handle the given flush event. * * @param event The flush event to be handled. * @throws HibernateException */ public void onFlush(FlushEvent event) throws HibernateException { final EventSource source = event.getSession(); try { if (source.getPersistenceContext().hasNonReadOnlyEntities()) { flushEverythingToExecutions(event); performExecutions(source); postFlush(source); if (source.getFactory().getStatistics().isStatisticsEnabled()) { source.getFactory().getStatisticsImplementor().flush(); } } } finally { cleanDirties(source); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Pre-flushing section // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** * Coordinates the processing necessary to get things ready for executions * as db calls by preping the session caches and moving the appropriate * entities and collections to their respective execution queues. * * @param event The flush event. * @throws HibernateException Error flushing caches to execution queues. */ @Override protected void flushEverythingToExecutions(FlushEvent event) throws HibernateException { log.trace("flushing session"); EventSource session = event.getSession(); final PersistenceContext persistenceContext = session.getPersistenceContext(); session.getInterceptor().preFlush(new LazyIterator(persistenceContext.getEntitiesByKey())); prepareEntityFlushes(session); // we could move this inside if we wanted to // tolerate collection initializations during // collection dirty checking: prepareCollectionFlushes(session); // now, any collections that are initialized // inside this block do not get updated - they // are ignored until the next flush persistenceContext.setFlushing(true); try { flushEntities(event); flushCollections(session); } finally { persistenceContext.setFlushing(false); } //some statistics if (log.isDebugEnabled()) { log.debug("Flushed: " + session.getActionQueue().numberOfInsertions() + " insertions, " + session.getActionQueue().numberOfUpdates() + " updates, " + session.getActionQueue().numberOfDeletions() + " deletions to " + persistenceContext.getEntityEntries().size() + " objects"); log.debug("Flushed: " + session.getActionQueue().numberOfCollectionCreations() + " (re)creations, " + session.getActionQueue().numberOfCollectionUpdates() + " updates, " + session.getActionQueue().numberOfCollectionRemovals() + " removals to " + persistenceContext.getCollectionEntries().size() + " collections"); new Printer(session.getFactory()).toString(persistenceContext.getEntitiesByKey().values().iterator(), session.getEntityMode()); } } /** * process cascade save/update at the start of a flush to discover * any newly referenced entity that must be passed to saveOrUpdate(), * and also apply orphan delete */ private void prepareEntityFlushes(EventSource session) throws HibernateException { log.debug("processing flush-time cascades"); final Map.Entry[] list = IdentityMap.concurrentEntries(session.getPersistenceContext().getEntityEntries()); //safe from concurrent modification because of how entryList() is implemented on IdentityMap final int size = list.length; final Object anything = getAnything(); for (int i = 0; i < size; i++) { Map.Entry me = list[i]; EntityEntry entry = (EntityEntry) me.getValue(); Status status = entry.getStatus(); if (!isEntityDirty(me.getKey(), entry.getEntityName())) continue; if (status == Status.MANAGED || status == Status.SAVING) { cascadeOnFlush(session, entry.getPersister(), me.getKey(), anything); } } } private void cascadeOnFlush(EventSource session, EntityPersister persister, Object object, Object anything) throws HibernateException { session.getPersistenceContext().incrementCascadeLevel(); try { new HibernateCascade(getCascadingAction(), HibernateCascade.BEFORE_FLUSH, session).cascade(persister, object, anything); } finally { session.getPersistenceContext().decrementCascadeLevel(); } } /** * Initialize the flags of the CollectionEntry, including the * dirty check. */ private void prepareCollectionFlushes(SessionImplementor session) throws HibernateException { // Initialize dirty flags for arrays + collections with composite elements // and reset reached, doupdate, etc. log.debug("dirty checking collections"); final List list = IdentityMap.entries(session.getPersistenceContext().getCollectionEntries()); final int size = list.size(); for (int i = 0; i < size; i++) { Map.Entry e = (Map.Entry) list.get(i); ((CollectionEntry) e.getValue()).preFlush((PersistentCollection) e.getKey()); } } /** * 1. detect any dirty entities * 2. schedule any entity updates * 3. search out any reachable collections */ private void flushEntities(FlushEvent event) throws HibernateException { log.trace("Flushing entities and processing referenced collections"); // Among other things, updateReachables() will recursively load all // collections that are moving roles. This might cause entities to // be loaded. // So this needs to be safe from concurrent modification problems. // It is safe because of how IdentityMap implements entrySet() final EventSource source = event.getSession(); final Map.Entry[] list = IdentityMap.concurrentEntries(source.getPersistenceContext().getEntityEntries()); final int size = list.length; for (int i = 0; i < size; i++) { // Update the status of the object and if necessary, schedule an update Map.Entry me = list[i]; EntityEntry entry = (EntityEntry) me.getValue(); Status status = entry.getStatus(); if (!isEntityDirty(me.getKey(), entry.getEntityName())) continue; if (status != Status.LOADING && status != Status.GONE) { FlushEntityEvent entityEvent = new FlushEntityEvent(source, me.getKey(), entry); FlushEntityEventListener[] listeners = source.getListeners().getFlushEntityEventListeners(); for (int j = 0; j < listeners.length; j++) { listeners[j].onFlushEntity(entityEvent); } } } source.getActionQueue().sortActions(); } /** * process any unreferenced collections and then inspect all known collections, * scheduling creates/removes/updates */ private void flushCollections(EventSource session) throws HibernateException { log.trace("Processing unreferenced collections"); List list = IdentityMap.entries(session.getPersistenceContext().getCollectionEntries()); int size = list.size(); for (int i = 0; i < size; i++) { Map.Entry me = (Map.Entry) list.get(i); CollectionEntry ce = (CollectionEntry) me.getValue(); if (!ce.isReached() && !ce.isIgnore()) { Collections.processUnreachableCollection((PersistentCollection) me.getKey(), session); } } // Schedule updates to collections: log.trace("Scheduling collection removes/(re)creates/updates"); list = IdentityMap.entries(session.getPersistenceContext().getCollectionEntries()); size = list.size(); ActionQueue actionQueue = session.getActionQueue(); for (int i = 0; i < size; i++) { Map.Entry me = (Map.Entry) list.get(i); PersistentCollection coll = (PersistentCollection) me.getKey(); CollectionEntry ce = (CollectionEntry) me.getValue(); if (coll.getOwner() != null) { if (!isEntityDirty(coll.getOwner(), null)) continue; } if (ce.isDorecreate()) { session.getInterceptor().onCollectionRecreate(coll, ce.getCurrentKey()); actionQueue.addAction( new CollectionRecreateAction(coll, ce.getCurrentPersister(), ce.getCurrentKey(), session)); } if (ce.isDoremove()) { session.getInterceptor().onCollectionRemove(coll, ce.getLoadedKey()); actionQueue.addAction(new CollectionRemoveAction(coll, ce.getLoadedPersister(), ce.getLoadedKey(), ce.isSnapshotEmpty(coll), session)); } if (ce.isDoupdate()) { session.getInterceptor().onCollectionUpdate(coll, ce.getLoadedKey()); actionQueue.addAction(new CollectionUpdateAction(coll, ce.getLoadedPersister(), ce.getLoadedKey(), ce.isSnapshotEmpty(coll), session)); } } actionQueue.sortCollectionActions(); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Post-flushing section // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** * 1. Recreate the collection key -> collection map * 2. rebuild the collection entries * 3. call Interceptor.postFlush() */ @Override @SuppressWarnings("unchecked") protected void postFlush(SessionImplementor session) throws HibernateException { log.trace("post flush"); final PersistenceContext persistenceContext = session.getPersistenceContext(); persistenceContext.getCollectionsByKey().clear(); persistenceContext.getBatchFetchQueue().clearSubselects(); //the database has changed now, so the subselect results need to be invalidated Iterator iter = persistenceContext.getCollectionEntries().entrySet().iterator(); while (iter.hasNext()) { Map.Entry me = (Map.Entry) iter.next(); CollectionEntry collectionEntry = (CollectionEntry) me.getValue(); PersistentCollection persistentCollection = (PersistentCollection) me.getKey(); if (!collectionEntry.isProcessed()) continue; collectionEntry.postFlush(persistentCollection); if (collectionEntry.getLoadedPersister() == null) { //if the collection is dereferenced, remove from the session cache //iter.remove(); //does not work, since the entrySet is not backed by the set persistenceContext.getCollectionEntries().remove(persistentCollection); } else { //otherwise recreate the mapping between the collection and its key CollectionKey collectionKey = new CollectionKey(collectionEntry.getLoadedPersister(), collectionEntry.getLoadedKey(), session.getEntityMode()); persistenceContext.getCollectionsByKey().put(collectionKey, persistentCollection); } } session.getInterceptor().postFlush(new LazyIterator(persistenceContext.getEntitiesByKey())); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // clean dirty section // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ protected void cleanDirties(SessionImplementor session) { Iterator entities = new LazyIterator(session.getPersistenceContext().getEntitiesByKey()); while (entities.hasNext()) { Object entity = entities.next(); if (entity instanceof BaseEntity) { ((BaseEntity) entity).cleanDirty(); } } } private boolean isEntityDirty(Object entity, String entityName) { if (!(entity instanceof BaseEntity) && !envers.isAuditedEntity(entityName)) { throw new HibernateException( "Trying to flush an object that is not an instace of BaseEntity nor an audited entity."); } if (entity instanceof BaseEntity) { return ((BaseEntity) entity).isDirty(); } else { return true; } } }