org.topazproject.otm.spring.OtmTransactionManager.java Source code

Java tutorial

Introduction

Here is the source code for org.topazproject.otm.spring.OtmTransactionManager.java

Source

/* $HeadURL::                                                                            $
 * $Id$
 *
 * Copyright (c) 2006-2008 by Topaz, Inc.
 * http://topazproject.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.topazproject.otm.spring;

import org.topazproject.otm.OtmException;
import org.topazproject.otm.Session;
import org.topazproject.otm.Transaction;

import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.TransactionUsageException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.SmartTransactionObject;

/**
 * A Spring transaction-manager that uses OTM transactions. Due to the current limitations on
 * Mulgara transactions, and hence current limitations on OTM transactions, this transaction
 * manager does not support suspending transactions or nested transactions (i.e. you can't use
 * propagation REQUIRES_NEW, NOT_SUPPORTED, or NESTED); however it does support re-using existing
 * transactions (i.e. you can use propagation REQUIRED, SUPPORTS, MANDATORY, and NEVER).
 *
 * <p>This transaction-manager has limited thread-safety: multiple threads may use the same
 * instance as long as there's only one thread per underlying session. "Underlying" here refers to
 * the fact that spring may inject a proxy for the Session object which actually delegates to one
 * of several real Session objects (this commonly happens when the transaction-manager has a longer
 * scope, such as singleton scope, than the Session objects).
 *
 * <p>Note that {@link #doGetTransaction doGetTransaction}, {@link #doBegin doBegin},
 * {@link #doCommit doCommit}, and {@link #doRollback doRollback} should really all be
 * <var>protected</var>, but are <var>public</var> instead to allow for the necessary tx stop/start
 * hacks in ambra. Once Mulgara supports multiple parallel transactions we can/should change this
 * back.
 *
 * @author Ronald Tschalr
 */
public class OtmTransactionManager extends AbstractPlatformTransactionManager {
    private Session session;
    private boolean clearSessionOnRB = false;
    private boolean skipFlushForRoTx = false;

    /** 
     * Create a new otm-transaction-manager instance. 
     */
    public OtmTransactionManager() {
        setRollbackOnCommitFailure(false);
    }

    @Override
    public Object doGetTransaction() {
        return new TransactionObject(session);
    }

    @Override
    public void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
        try {
            Session s = ((TransactionObject) transaction).getSession();
            s.beginTransaction(definition.isReadOnly(), determineTimeout(definition));

            if (definition.isReadOnly() && skipFlushForRoTx) {
                ((TransactionObject) transaction).savedFlushMode = s.getFlushMode();
                s.setFlushMode(Session.FlushMode.commit);
            }
        } catch (OtmException oe) {
            throw new TransactionSystemException("error beginning transaction", oe);
        }
    }

    @Override
    public void doCommit(DefaultTransactionStatus status) throws TransactionException {
        TransactionObject txObj = (TransactionObject) status.getTransaction();

        Transaction tx = txObj.getSession().getTransaction();
        if (tx == null)
            throw new TransactionUsageException("no transaction active");

        try {
            tx.commit();
        } catch (OtmException oe) {
            throw new TransactionSystemException("error committing transaction", oe);
        } finally {
            if (txObj.savedFlushMode != null)
                txObj.getSession().setFlushMode(txObj.savedFlushMode);
        }
    }

    @Override
    public void doRollback(DefaultTransactionStatus status) throws TransactionException {
        TransactionObject txObj = (TransactionObject) status.getTransaction();

        Transaction tx = txObj.getSession().getTransaction();
        if (tx == null)
            throw new TransactionUsageException("no transaction active");

        try {
            tx.rollback();
        } catch (OtmException oe) {
            throw new TransactionSystemException("error rolling back transaction", oe);
        } finally {
            if (clearSessionOnRB)
                txObj.getSession().clear();
            if (txObj.savedFlushMode != null)
                txObj.getSession().setFlushMode(txObj.savedFlushMode);
        }
    }

    @Override
    protected boolean isExistingTransaction(Object transaction) {
        return ((TransactionObject) transaction).getSession().getTransaction() != null;
    }

    @Override
    protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
        TransactionObject txObj = (TransactionObject) status.getTransaction();
        try {
            txObj.setRollbackOnly();
        } catch (OtmException oe) {
            throw new TransactionSystemException("error setting rollback-only", oe);
        }
    }

    /**
     * Set the OTM session. Called by spring's bean wiring.
     *
     * @param session the otm session
     */
    @Required
    public void setOtmSession(Session session) {
        this.session = session;
    }

    /**
     * Set the clear-session-on-rollback flag. Because after a rollback the state of the objects in
     * the session does not match the state of the database, it's usually prudent to clear the
     * session after a rollback to prevent the application from continuing with stale objects.
     *
     * <p>Changing this flag will affect the current transaction, if there is one.
     *
     * @param clearSessionOnRB true if the session should be <code>clear()</code>'d on transaction
     *                         rollback
     */
    public void setClearSessionOnRollback(boolean clearSessionOnRB) {
        this.clearSessionOnRB = clearSessionOnRB;
    }

    /**
     * Set the skip-flush-on-readonly-transaction flag. If there are a large number of objects in
     * the session, a {@link Session#flush flush()} can take significant time. Since no modifications
     * should be done in a read-only transaction, this check can be skipped when in one. However,
     * disabling this check does mean that inadvertant modifications will be silently dropped (as
     * opposed to having an exception thrown). For this reason this flag currently sets the
     * flush-mode to <var>commit</var>, i.e. it just skips the flushes done before queries.
     *
     * <p>Changing this flag does not affect any current transaction, only new ones.
     *
     * @param skipFlushForRoTx true if Session.flush's should skipped in read-only transactions
     */
    public void setSkipFlushOnReadonlyTx(boolean skipFlushForRoTx) {
        this.skipFlushForRoTx = skipFlushForRoTx;
    }

    /**
     * Implement SmartTransactionObject so spring can do proper rollback-only handling.
     */
    private class TransactionObject implements SmartTransactionObject {
        private final Session session;
        public Session.FlushMode savedFlushMode = null;

        public TransactionObject(Session session) {
            this.session = session;
        }

        Session getSession() {
            return session;
        }

        void setRollbackOnly() {
            session.getTransaction().setRollbackOnly();
        }

        public boolean isRollbackOnly() {
            return session.getTransaction().isRollbackOnly();
        }
    }
}