org.hibernate.engine.ActionQueue.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.engine.ActionQueue.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2007, Red Hat Middleware LLC or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Middleware LLC.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program 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 distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 *
 */
package org.hibernate.engine;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.action.BulkOperationCleanupAction;
import org.hibernate.action.CollectionRecreateAction;
import org.hibernate.action.CollectionRemoveAction;
import org.hibernate.action.CollectionUpdateAction;
import org.hibernate.action.EntityDeleteAction;
import org.hibernate.action.EntityIdentityInsertAction;
import org.hibernate.action.EntityInsertAction;
import org.hibernate.action.EntityUpdateAction;
import org.hibernate.action.Executable;
import org.hibernate.cache.CacheException;
import org.hibernate.type.Type;

/**
 * Responsible for maintaining the queue of actions related to events.
 * </p>
 * The ActionQueue holds the DML operations queued as part of a session's
 * transactional-write-behind semantics.  DML operations are queued here
 * until a flush forces them to be executed against the database.
 *
 * @author Steve Ebersole
 */
public class ActionQueue {

    private static final Log log = LogFactory.getLog(ActionQueue.class);
    private static final int INIT_QUEUE_LIST_SIZE = 5;

    private SessionImplementor session;

    // Object insertions, updates, and deletions have list semantics because
    // they must happen in the right order so as to respect referential
    // integrity
    private ArrayList insertions;
    private ArrayList deletions;
    private ArrayList updates;
    // Actually the semantics of the next three are really "Bag"
    // Note that, unlike objects, collection insertions, updates,
    // deletions are not really remembered between flushes. We
    // just re-use the same Lists for convenience.
    private ArrayList collectionCreations;
    private ArrayList collectionUpdates;
    private ArrayList collectionRemovals;

    private ArrayList executions;

    /**
     * Constructs an action queue bound to the given session.
     *
     * @param session The session "owning" this queue.
     */
    public ActionQueue(SessionImplementor session) {
        this.session = session;
        init();
    }

    private void init() {
        insertions = new ArrayList(INIT_QUEUE_LIST_SIZE);
        deletions = new ArrayList(INIT_QUEUE_LIST_SIZE);
        updates = new ArrayList(INIT_QUEUE_LIST_SIZE);

        collectionCreations = new ArrayList(INIT_QUEUE_LIST_SIZE);
        collectionRemovals = new ArrayList(INIT_QUEUE_LIST_SIZE);
        collectionUpdates = new ArrayList(INIT_QUEUE_LIST_SIZE);

        executions = new ArrayList(INIT_QUEUE_LIST_SIZE * 3);
    }

    public void clear() {
        updates.clear();
        insertions.clear();
        deletions.clear();

        collectionCreations.clear();
        collectionRemovals.clear();
        collectionUpdates.clear();
    }

    public void addAction(EntityInsertAction action) {
        insertions.add(action);
    }

    public void addAction(EntityDeleteAction action) {
        deletions.add(action);
    }

    public void addAction(EntityUpdateAction action) {
        updates.add(action);
    }

    public void addAction(CollectionRecreateAction action) {
        collectionCreations.add(action);
    }

    public void addAction(CollectionRemoveAction action) {
        collectionRemovals.add(action);
    }

    public void addAction(CollectionUpdateAction action) {
        collectionUpdates.add(action);
    }

    public void addAction(EntityIdentityInsertAction insert) {
        insertions.add(insert);
    }

    public void addAction(BulkOperationCleanupAction cleanupAction) {
        // Add these directly to the executions queue
        executions.add(cleanupAction);
    }

    /**
     * Perform all currently queued entity-insertion actions.
     *
     * @throws HibernateException error executing queued insertion actions.
     */
    public void executeInserts() throws HibernateException {
        executeActions(insertions);
    }

    /**
     * Perform all currently queued actions.
     *
     * @throws HibernateException error executing queued actions.
     */
    public void executeActions() throws HibernateException {
        executeActions(insertions);
        executeActions(updates);
        executeActions(collectionRemovals);
        executeActions(collectionUpdates);
        executeActions(collectionCreations);
        executeActions(deletions);
    }

    /**
     * Prepares the internal action queues for execution.
     *
     * @throws HibernateException error preparing actions.
     */
    public void prepareActions() throws HibernateException {
        prepareActions(collectionRemovals);
        prepareActions(collectionUpdates);
        prepareActions(collectionCreations);
    }

    /**
     * Performs cleanup of any held cache softlocks.
     *
     * @param success Was the transaction successful.
     */
    public void afterTransactionCompletion(boolean success) {
        int size = executions.size();
        final boolean invalidateQueryCache = session.getFactory().getSettings().isQueryCacheEnabled();
        for (int i = 0; i < size; i++) {
            try {
                Executable exec = (Executable) executions.get(i);
                try {
                    exec.afterTransactionCompletion(success);
                } finally {
                    if (invalidateQueryCache) {
                        session.getFactory().getUpdateTimestampsCache().invalidate(exec.getPropertySpaces());
                    }
                }
            } catch (CacheException ce) {
                log.error("could not release a cache lock", ce);
                // continue loop
            } catch (Exception e) {
                throw new AssertionFailure("Exception releasing cache locks", e);
            }
        }
        executions.clear();
    }

    /**
     * Check whether the given tables/query-spaces are to be executed against
     * given the currently queued actions.
     *
     * @param tables The table/query-spaces to check.
     *
     * @return True if we contain pending actions against any of the given
     *         tables; false otherwise.
     */
    public boolean areTablesToBeUpdated(Set tables) {
        return areTablesToUpdated(updates, tables) || areTablesToUpdated(insertions, tables)
                || areTablesToUpdated(deletions, tables) || areTablesToUpdated(collectionUpdates, tables)
                || areTablesToUpdated(collectionCreations, tables)
                || areTablesToUpdated(collectionRemovals, tables);
    }

    /**
     * Check whether any insertion or deletion actions are currently queued.
     *
     * @return True if insertions or deletions are currently queued; false otherwise.
     */
    public boolean areInsertionsOrDeletionsQueued() {
        return (insertions.size() > 0 || deletions.size() > 0);
    }

    private static boolean areTablesToUpdated(List executables, Set tablespaces) {
        int size = executables.size();
        for (int j = 0; j < size; j++) {
            Serializable[] spaces = ((Executable) executables.get(j)).getPropertySpaces();
            for (int i = 0; i < spaces.length; i++) {
                if (tablespaces.contains(spaces[i])) {
                    if (log.isDebugEnabled()) {
                        log.debug("changes must be flushed to space: " + spaces[i]);
                    }
                    return true;
                }
            }
        }
        return false;
    }

    private void executeActions(List list) throws HibernateException {
        int size = list.size();
        for (int i = 0; i < size; i++) {
            execute((Executable) list.get(i));
        }
        list.clear();
        session.getBatcher().executeBatch();
    }

    public void execute(Executable executable) {
        final boolean lockQueryCache = session.getFactory().getSettings().isQueryCacheEnabled();
        if (executable.hasAfterTransactionCompletion() || lockQueryCache) {
            executions.add(executable);
        }
        if (lockQueryCache) {
            session.getFactory().getUpdateTimestampsCache().preinvalidate(executable.getPropertySpaces());
        }
        executable.execute();
    }

    private void prepareActions(List queue) throws HibernateException {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            Executable executable = (Executable) queue.get(i);
            executable.beforeExecutions();
        }
    }

    /**
     * Returns a string representation of the object.
     *
     * @return a string representation of the object.
     */
    public String toString() {
        return new StringBuffer().append("ActionQueue[insertions=").append(insertions).append(" updates=")
                .append(updates).append(" deletions=").append(deletions).append(" collectionCreations=")
                .append(collectionCreations).append(" collectionRemovals=").append(collectionRemovals)
                .append(" collectionUpdates=").append(collectionUpdates).append("]").toString();
    }

    public int numberOfCollectionRemovals() {
        return collectionRemovals.size();
    }

    public int numberOfCollectionUpdates() {
        return collectionUpdates.size();
    }

    public int numberOfCollectionCreations() {
        return collectionCreations.size();
    }

    public int numberOfDeletions() {
        return deletions.size();
    }

    public int numberOfUpdates() {
        return updates.size();
    }

    public int numberOfInsertions() {
        return insertions.size();
    }

    public void sortCollectionActions() {
        if (session.getFactory().getSettings().isOrderUpdatesEnabled()) {
            //sort the updates by fk
            java.util.Collections.sort(collectionCreations);
            java.util.Collections.sort(collectionUpdates);
            java.util.Collections.sort(collectionRemovals);
        }
    }

    public void sortActions() {
        if (session.getFactory().getSettings().isOrderUpdatesEnabled()) {
            //sort the updates by pk
            java.util.Collections.sort(updates);
        }
        if (session.getFactory().getSettings().isOrderInsertsEnabled()) {
            sortInsertActions();
        }
    }

    /**
     * Order the {@link #insertions} queue such that we group inserts
     * against the same entity together (without violating constraints).  The
     * original order is generated by cascade order, which in turn is based on
     * the directionality of foreign-keys.  So even though we will be changing
     * the ordering here, we need to make absolutely certain that we do not
     * circumvent this FK ordering to the extent of causing constraint
     * violations
     */
    private void sortInsertActions() {
        new InsertActionSorter().sort();
    }

    public ArrayList cloneDeletions() {
        return (ArrayList) deletions.clone();
    }

    public void clearFromFlushNeededCheck(int previousCollectionRemovalSize) {
        collectionCreations.clear();
        collectionUpdates.clear();
        updates.clear();
        // collection deletions are a special case since update() can add
        // deletions of collections not loaded by the session.
        for (int i = collectionRemovals.size() - 1; i >= previousCollectionRemovalSize; i--) {
            collectionRemovals.remove(i);
        }
    }

    public boolean hasAfterTransactionActions() {
        return executions.size() > 0;
    }

    public boolean hasAnyQueuedActions() {
        return updates.size() > 0 || insertions.size() > 0 || deletions.size() > 0 || collectionUpdates.size() > 0
                || collectionRemovals.size() > 0 || collectionCreations.size() > 0;
    }

    /**
     * Used by the owning session to explicitly control serialization of the
     * action queue
     *
     * @param oos The stream to which the action queue should get written
     *
     * @throws IOException
     */
    public void serialize(ObjectOutputStream oos) throws IOException {
        log.trace("serializing action-queue");

        int queueSize = insertions.size();
        log.trace("starting serialization of [" + queueSize + "] insertions entries");
        oos.writeInt(queueSize);
        for (int i = 0; i < queueSize; i++) {
            oos.writeObject(insertions.get(i));
        }

        queueSize = deletions.size();
        log.trace("starting serialization of [" + queueSize + "] deletions entries");
        oos.writeInt(queueSize);
        for (int i = 0; i < queueSize; i++) {
            oos.writeObject(deletions.get(i));
        }

        queueSize = updates.size();
        log.trace("starting serialization of [" + queueSize + "] updates entries");
        oos.writeInt(queueSize);
        for (int i = 0; i < queueSize; i++) {
            oos.writeObject(updates.get(i));
        }

        queueSize = collectionUpdates.size();
        log.trace("starting serialization of [" + queueSize + "] collectionUpdates entries");
        oos.writeInt(queueSize);
        for (int i = 0; i < queueSize; i++) {
            oos.writeObject(collectionUpdates.get(i));
        }

        queueSize = collectionRemovals.size();
        log.trace("starting serialization of [" + queueSize + "] collectionRemovals entries");
        oos.writeInt(queueSize);
        for (int i = 0; i < queueSize; i++) {
            oos.writeObject(collectionRemovals.get(i));
        }

        queueSize = collectionCreations.size();
        log.trace("starting serialization of [" + queueSize + "] collectionCreations entries");
        oos.writeInt(queueSize);
        for (int i = 0; i < queueSize; i++) {
            oos.writeObject(collectionCreations.get(i));
        }
    }

    /**
     * Used by the owning session to explicitly control deserialization of the
     * action queue
     *
     * @param ois The stream from which to read the action queue
     *
     * @throws IOException
     */
    public static ActionQueue deserialize(ObjectInputStream ois, SessionImplementor session)
            throws IOException, ClassNotFoundException {
        log.trace("deserializing action-queue");
        ActionQueue rtn = new ActionQueue(session);

        int queueSize = ois.readInt();
        log.trace("starting deserialization of [" + queueSize + "] insertions entries");
        rtn.insertions = new ArrayList(queueSize);
        for (int i = 0; i < queueSize; i++) {
            rtn.insertions.add(ois.readObject());
        }

        queueSize = ois.readInt();
        log.trace("starting deserialization of [" + queueSize + "] deletions entries");
        rtn.deletions = new ArrayList(queueSize);
        for (int i = 0; i < queueSize; i++) {
            rtn.deletions.add(ois.readObject());
        }

        queueSize = ois.readInt();
        log.trace("starting deserialization of [" + queueSize + "] updates entries");
        rtn.updates = new ArrayList(queueSize);
        for (int i = 0; i < queueSize; i++) {
            rtn.updates.add(ois.readObject());
        }

        queueSize = ois.readInt();
        log.trace("starting deserialization of [" + queueSize + "] collectionUpdates entries");
        rtn.collectionUpdates = new ArrayList(queueSize);
        for (int i = 0; i < queueSize; i++) {
            rtn.collectionUpdates.add(ois.readObject());
        }

        queueSize = ois.readInt();
        log.trace("starting deserialization of [" + queueSize + "] collectionRemovals entries");
        rtn.collectionRemovals = new ArrayList(queueSize);
        for (int i = 0; i < queueSize; i++) {
            rtn.collectionRemovals.add(ois.readObject());
        }

        queueSize = ois.readInt();
        log.trace("starting deserialization of [" + queueSize + "] collectionCreations entries");
        rtn.collectionCreations = new ArrayList(queueSize);
        for (int i = 0; i < queueSize; i++) {
            rtn.collectionCreations.add(ois.readObject());
        }
        return rtn;
    }

    /**
     * Sorts the insert actions using more hashes.
     *
     * @author Jay Erb
     */
    private class InsertActionSorter {

        // the mapping of entity names to their latest batch numbers.
        private HashMap latestBatches = new HashMap();
        private HashMap entityBatchNumber;

        // the map of batch numbers to EntityInsertAction lists
        private HashMap actionBatches = new HashMap();

        public InsertActionSorter() {
            //optimize the hash size to eliminate a rehash.
            entityBatchNumber = new HashMap(insertions.size() + 1, 1.0f);
        }

        /**
         * Sort the insert actions.
         */
        public void sort() {

            // the list of entity names that indicate the batch number
            for (Iterator actionItr = insertions.iterator(); actionItr.hasNext();) {
                EntityInsertAction action = (EntityInsertAction) actionItr.next();
                // remove the current element from insertions. It will be added back later.
                String entityName = action.getEntityName();

                // the entity associated with the current action.
                Object currentEntity = action.getInstance();

                Integer batchNumber;
                if (latestBatches.containsKey(entityName)) {
                    // There is already an existing batch for this type of entity.
                    // Check to see if the latest batch is acceptable.
                    batchNumber = findBatchNumber(action, entityName);
                } else {
                    // add an entry for this type of entity.
                    // we can be assured that all referenced entities have already
                    // been processed,
                    // so specify that this entity is with the latest batch.
                    // doing the batch number before adding the name to the list is
                    // a faster way to get an accurate number.

                    batchNumber = new Integer(actionBatches.size());
                    latestBatches.put(entityName, batchNumber);
                }
                entityBatchNumber.put(currentEntity, batchNumber);
                addToBatch(batchNumber, action);
            }
            insertions.clear();

            // now rebuild the insertions list. There is a batch for each entry in the name list.
            for (int i = 0; i < actionBatches.size(); i++) {
                List batch = (List) actionBatches.get(new Integer(i));
                for (Iterator batchItr = batch.iterator(); batchItr.hasNext();) {
                    EntityInsertAction action = (EntityInsertAction) batchItr.next();
                    insertions.add(action);
                }
            }
        }

        /**
         * Finds an acceptable batch for this entity to be a member.
         */
        private Integer findBatchNumber(EntityInsertAction action, String entityName) {
            // loop through all the associated entities and make sure they have been
            // processed before the latest
            // batch associated with this entity type.

            // the current batch number is the latest batch for this entity type.
            Integer latestBatchNumberForType = (Integer) latestBatches.get(entityName);

            // loop through all the associations of the current entity and make sure that they are processed
            // before the current batch number
            Object[] propertyValues = action.getState();
            Type[] propertyTypes = action.getPersister().getClassMetadata().getPropertyTypes();

            for (int i = 0; i < propertyValues.length; i++) {
                Object value = propertyValues[i];
                Type type = propertyTypes[i];
                if (type.isEntityType() && value != null) {
                    // find the batch number associated with the current association, if any.
                    Integer associationBatchNumber = (Integer) entityBatchNumber.get(value);
                    if (associationBatchNumber != null
                            && associationBatchNumber.compareTo(latestBatchNumberForType) > 0) {
                        // create a new batch for this type. The batch number is the number of current batches.
                        latestBatchNumberForType = new Integer(actionBatches.size());
                        latestBatches.put(entityName, latestBatchNumberForType);
                        // since this entity will now be processed in the latest possible batch,
                        // we can be assured that it will come after all other associations,
                        // there's not need to continue checking.
                        break;
                    }
                }
            }
            return latestBatchNumberForType;
        }

        private void addToBatch(Integer batchNumber, EntityInsertAction action) {
            List actions = (List) actionBatches.get(batchNumber);

            if (actions == null) {
                actions = new LinkedList();
                actionBatches.put(batchNumber, actions);
            }
            actions.add(action);
        }

    }

}