org.guzz.web.context.spring.GuzzTransactionManager.java Source code

Java tutorial

Introduction

Here is the source code for org.guzz.web.context.spring.GuzzTransactionManager.java

Source

/*
 * Copyright 2008-2011 the original author or authors.
 *
 * 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.guzz.web.context.spring;

import org.guzz.GuzzContext;
import org.guzz.exception.DaoException;
import org.guzz.exception.JDBCException;
import org.guzz.transaction.IsolationsSavePointer;
import org.guzz.transaction.TransactionManager;
import org.guzz.transaction.WriteTranSession;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.support.SQLExceptionTranslator;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.ResourceTransactionManager;
import org.springframework.transaction.support.SmartTransactionObject;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * {@link org.springframework.transaction.PlatformTransactionManager}
 * implementation for a single Guzz {@link org.guzz.TransactionManager}.
 * Binds a Guzz <code>WriteTranSession</code> from the specified factory to the thread, 
 * potentially allowing for one thread-bound Session per factory. 
 * {@link TransactionManagerUtils}
 * and {@link SpringWriteTemplate} are aware of thread-bound Sessions and participate
 * in such transactions automatically. Using either of those is required for Guzz
 * access code that needs to support this transaction handling mechanism.
 *
 * <p>Supports custom isolation levels, and timeouts that get applied as
 * Guzz transaction timeouts.
 * 
 * <p>JTA and nested transactions are not supported!
 *
 * @author liukaixuan(liukaixuan@gmail.com)
 * @see TransactionManagerUtils#getSession
 * @see TransactionManagerUtils#applyTransactionTimeout
 * @see TransactionManagerUtils#releaseSession
 * @see SpringWriteTemplate
 */
@SuppressWarnings("serial")
public class GuzzTransactionManager extends AbstractPlatformTransactionManager
        implements ResourceTransactionManager, InitializingBean {

    private TransactionManager transactionManager;

    private SQLExceptionTranslator jdbcExceptionTranslator;

    /**
     * Create a new GuzzTransactionManager instance.
     * A TransactionManager has to be set to be able to use it.
     * @see #setTransactionManager
     */
    public GuzzTransactionManager() {
    }

    /**
     * Create a new GuzzTransactionManager instance.
     * @param transactionManager transactionManager to manage transactions for
     */
    public GuzzTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
        afterPropertiesSet();
    }

    /**
     * Create a new GuzzTransactionManager instance.
     * @param sessionFactory SessionFactory to manage transactions for
     */
    public GuzzTransactionManager(GuzzContext guzzContext) {
        this(guzzContext.getTransactionManager());
    }

    /**
     * Set the TransactionManager that this instance should manage transactions for.
     */
    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    /**
     * Set the TransactionManager that this instance should manage transactions for.
     */
    public void setGuzzContext(GuzzContext guzzContext) {
        this.setTransactionManager(guzzContext.getTransactionManager());
    }

    /**
     * Return the TransactionManager that this instance should manage transactions for.
     */
    public TransactionManager getTransactionManager() {
        return this.transactionManager;
    }

    /**
     * Set the JDBC exception translator for this transaction manager.
     * <p>Applied to any SQLException root cause of a Guzz JDBCException that
     * is thrown on flush, overriding Guzz default SQLException translation
     * @param jdbcExceptionTranslator the exception translator
     * @see java.sql.SQLException
     */
    public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
        this.jdbcExceptionTranslator = jdbcExceptionTranslator;
    }

    /**
     * Return the JDBC exception translator for this transaction manager, if any.
     */
    public SQLExceptionTranslator getJdbcExceptionTranslator() {
        return this.jdbcExceptionTranslator;
    }

    protected DataAccessException convertGuzzAccessException(DaoException ex) {
        if (ex instanceof JDBCException && getJdbcExceptionTranslator() != null) {
            JDBCException e = (JDBCException) ex;

            return getJdbcExceptionTranslator().translate(e.getMessage(), e.getSQL(), e.getSQLException());
        }

        return TransactionManagerUtils.convertGuzzAccessException(ex);
    }

    public void afterPropertiesSet() {
        if (getTransactionManager() == null) {
            throw new IllegalArgumentException("Property 'transactionManager' is required");
        }
    }

    public Object getResourceFactory() {
        return getTransactionManager();
    }

    @Override
    protected Object doGetTransaction() {
        GuzzTransactionObject txObject = new GuzzTransactionObject();

        WriteTranSessionHolder writeTranSessionHolder = (WriteTranSessionHolder) TransactionSynchronizationManager
                .getResource(getTransactionManager());

        if (writeTranSessionHolder != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Found thread-bound WriteTranSession ["
                        + TransactionManagerUtils.toString(writeTranSessionHolder.getWriteTranSession())
                        + "] for Guzz transaction");
            }
            txObject.setWriteTranSessionHolder(writeTranSessionHolder);
        }

        return txObject;
    }

    @Override
    protected boolean isExistingTransaction(Object transaction) {
        GuzzTransactionObject txObject = (GuzzTransactionObject) transaction;

        return txObject.hasSpringManagedTransaction();
    }

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        GuzzTransactionObject txObject = (GuzzTransactionObject) transaction;

        //TODO: checkout the outside DataSourceTransactionManager 
        //      if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
        //         throw new IllegalTransactionStateException(
        //               "Pre-bound JDBC Connection found! GuzzTransactionManager does not support " +
        //               "running within DataSourceTransactionManager if told to manage the DataSource itself. ") ;
        //      }

        WriteTranSession writeTranSession = null;

        try {
            if (txObject.getSessionHolder() == null
                    || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
                writeTranSession = getTransactionManager().openRWTran(false);

                if (logger.isDebugEnabled()) {
                    logger.debug("Opened new Session [" + TransactionManagerUtils.toString(writeTranSession)
                            + "] for Guzz transaction");
                }
                txObject.setWriteTranSession(writeTranSession);
            }

            writeTranSession = txObject.getSessionHolder().getWriteTranSession();

            if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
                // We should set a specific isolation level but are not allowed to...
                IsolationsSavePointer oldSavePointer = writeTranSession
                        .setTransactionIsolation(definition.getIsolationLevel());

                txObject.setIsolationsSavePointer(oldSavePointer);
            }

            // Register transaction timeout.
            int timeout = determineTimeout(definition);
            if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                txObject.getSessionHolder().setTimeoutInSeconds(timeout);
            }

            // Bind the session holder to the thread.
            if (txObject.isNewWriteTranSessionHolder()) {
                TransactionSynchronizationManager.bindResource(getTransactionManager(),
                        txObject.getSessionHolder());
            }
            txObject.getSessionHolder().setSynchronizedWithTransaction(true);

        } catch (Exception ex) {
            if (txObject.isNewWriteTranSession()) {
                try {
                    if (writeTranSession != null) {
                        //TransactionIsolation?????
                        writeTranSession.rollback();
                    }
                } catch (Throwable ex2) {
                    logger.debug("Could not rollback WriteTranSession after failed transaction begin", ex);
                } finally {
                    TransactionManagerUtils.closeSession(writeTranSession);
                }
            }
            throw new CannotCreateTransactionException("Could not open Guzz WriteTranSession for transaction", ex);
        }
    }

    @Override
    protected Object doSuspend(Object transaction) {
        GuzzTransactionObject txObject = (GuzzTransactionObject) transaction;
        txObject.setWriteTranSessionHolder(null);
        WriteTranSessionHolder writeTranSessionHolder = (WriteTranSessionHolder) TransactionSynchronizationManager
                .unbindResource(getTransactionManager());

        return new SuspendedResourcesHolder(writeTranSessionHolder);
    }

    @Override
    protected void doResume(Object transaction, Object suspendedResources) {
        SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
        if (TransactionSynchronizationManager.hasResource(getTransactionManager())) {
            // From non-transactional code running in active transaction synchronization
            // -> can be safely removed, will be closed on transaction completion.
            TransactionSynchronizationManager.unbindResource(getTransactionManager());
        }

        TransactionSynchronizationManager.bindResource(getTransactionManager(), resourcesHolder.getSessionHolder());
    }

    @Override
    protected void doCommit(DefaultTransactionStatus status) {
        GuzzTransactionObject txObject = (GuzzTransactionObject) status.getTransaction();
        if (status.isDebug()) {
            logger.debug("Committing Guzz transaction on WriteTranSession ["
                    + TransactionManagerUtils.toString(txObject.getSessionHolder().getWriteTranSession()) + "]");
        }
        try {
            txObject.getSessionHolder().getWriteTranSession().commit();
        } catch (DaoException ex) {
            throw convertGuzzAccessException(ex);
        } catch (Throwable ex) {
            throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
        }
    }

    @Override
    protected void doRollback(DefaultTransactionStatus status) {
        GuzzTransactionObject txObject = (GuzzTransactionObject) status.getTransaction();
        if (status.isDebug()) {
            logger.debug("Rolling back Guzz transaction on WriteTranSession ["
                    + TransactionManagerUtils.toString(txObject.getSessionHolder().getWriteTranSession()) + "]");
        }

        try {
            txObject.getSessionHolder().getWriteTranSession().rollback();
        } catch (Throwable ex) {
            throw new TransactionSystemException("Could not roll back Hibernate transaction", ex);
        }
    }

    @Override
    protected void doSetRollbackOnly(DefaultTransactionStatus status) {
        GuzzTransactionObject txObject = (GuzzTransactionObject) status.getTransaction();
        if (status.isDebug()) {
            logger.debug("Setting Guzz transaction on WriteTranSession ["
                    + TransactionManagerUtils.toString(txObject.getSessionHolder().getWriteTranSession())
                    + "] rollback-only");
        }
        txObject.setRollbackOnly();
    }

    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        GuzzTransactionObject txObject = (GuzzTransactionObject) transaction;

        // Remove the session holder from the thread.
        if (txObject.isNewWriteTranSessionHolder()) {
            TransactionSynchronizationManager.unbindResource(getTransactionManager());
        }

        WriteTranSession session = txObject.getSessionHolder().getWriteTranSession();

        if (txObject.getIsolationsSavePointer() != null) {
            session.resetTransactionIsolationTo(txObject.getIsolationsSavePointer());
        }

        if (txObject.isNewWriteTranSession()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Closing Guzz WriteTranSession [" + TransactionManagerUtils.toString(session)
                        + "] after transaction");
            }

            TransactionManagerUtils.closeSession(session);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Not closing pre-bound Guzz WriteTranSession ["
                        + TransactionManagerUtils.toString(session) + "] after transaction");
            }
        }

        txObject.getSessionHolder().clear();
    }

    /**
     * Guzz transaction object, representing a WriteTranSessionHolder.
     * Used as transaction object by GuzzTransactionManager.
     */
    private class GuzzTransactionObject implements SmartTransactionObject {

        private WriteTranSessionHolder writeTranSessionHolder;

        private IsolationsSavePointer isolationsSavePointer;

        private boolean newWriteTranSessionHolder;

        private boolean newWriteTranSession;

        public void setWriteTranSession(WriteTranSession session) {
            this.writeTranSessionHolder = new WriteTranSessionHolder(session);
            this.newWriteTranSessionHolder = true;
            this.newWriteTranSession = true;
        }

        public void setWriteTranSessionHolder(WriteTranSessionHolder writeTranSessionHolder) {
            this.writeTranSessionHolder = writeTranSessionHolder;
            this.newWriteTranSessionHolder = false;
            this.newWriteTranSession = false;
        }

        public WriteTranSessionHolder getSessionHolder() {
            return this.writeTranSessionHolder;
        }

        public boolean isNewWriteTranSessionHolder() {
            return this.newWriteTranSessionHolder;
        }

        public boolean isNewWriteTranSession() {
            return this.newWriteTranSession;
        }

        public boolean hasSpringManagedTransaction() {
            return (this.writeTranSessionHolder != null
                    && this.writeTranSessionHolder.getWriteTranSession() != null);
        }

        public void setRollbackOnly() {
            this.writeTranSessionHolder.setRollbackOnly();
        }

        public boolean isRollbackOnly() {
            return this.writeTranSessionHolder.isRollbackOnly();
        }

        public IsolationsSavePointer getIsolationsSavePointer() {
            return this.isolationsSavePointer;
        }

        public void setIsolationsSavePointer(IsolationsSavePointer isolationsSavePointer) {
            this.isolationsSavePointer = isolationsSavePointer;
        }

        public void flush() {
            //no-op
        }

    }

    /**
     * Holder for suspended resources.
     * Used internally by <code>doSuspend</code> and <code>doResume</code>.
     */
    private static class SuspendedResourcesHolder {

        private final WriteTranSessionHolder writeTranSessionHolder;

        private SuspendedResourcesHolder(WriteTranSessionHolder writeTranSessionHolder) {
            this.writeTranSessionHolder = writeTranSessionHolder;
        }

        private WriteTranSessionHolder getSessionHolder() {
            return this.writeTranSessionHolder;
        }
    }

}