/* * Copyright 2002-2007 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.springframework.jca.cci.connection; import javax.resource.NotSupportedException; import javax.resource.ResourceException; import javax.resource.cci.Connection; import javax.resource.cci.ConnectionFactory; import javax.resource.spi.LocalTransactionException; import org.springframework.beans.factory.InitializingBean; import org.springframework.transaction.CannotCreateTransactionException; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.support.AbstractPlatformTransactionManager; import org.springframework.transaction.support.DefaultTransactionStatus; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.ResourceTransactionManager; /** * {@link org.springframework.transaction.PlatformTransactionManager} implementation * that manages local transactions for a single CCI ConnectionFactory. * Binds a CCI Connection from the specified ConnectionFactory to the thread, * potentially allowing for one thread-bound Connection per ConnectionFactory. * * <p>Application code is required to retrieve the CCI Connection via * {@link ConnectionFactoryUtils#getConnection(ConnectionFactory)} instead of a standard * J2EE-style {@link ConnectionFactory#getConnection()} call. Spring classes such as * {@link org.springframework.jca.cci.core.CciTemplate} use this strategy implicitly. * If not used in combination with this transaction manager, the * {@link ConnectionFactoryUtils} lookup strategy behaves exactly like the native * DataSource lookup; it can thus be used in a portable fashion. * * <p>Alternatively, you can allow application code to work with the standard * J2EE lookup pattern {@link ConnectionFactory#getConnection()}, for example * for legacy code that is not aware of Spring at all. In that case, define a * {@link TransactionAwareConnectionFactoryProxy} for your target ConnectionFactory, * which will automatically participate in Spring-managed transactions. * * @author Thierry Templier * @author Juergen Hoeller * @since 1.2 * @see ConnectionFactoryUtils#getConnection(javax.resource.cci.ConnectionFactory) * @see ConnectionFactoryUtils#releaseConnection * @see TransactionAwareConnectionFactoryProxy * @see org.springframework.jca.cci.core.CciTemplate */ public class CciLocalTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean { private ConnectionFactory connectionFactory; /** * Create a new CciLocalTransactionManager instance. * A ConnectionFactory has to be set to be able to use it. * @see #setConnectionFactory */ public CciLocalTransactionManager() { } /** * Create a new CciLocalTransactionManager instance. * @param connectionFactory CCI ConnectionFactory to manage local transactions for */ public CciLocalTransactionManager(ConnectionFactory connectionFactory) { setConnectionFactory(connectionFactory); afterPropertiesSet(); } /** * Set the CCI ConnectionFactory that this instance should manage local * transactions for. */ public void setConnectionFactory(ConnectionFactory cf) { if (cf instanceof TransactionAwareConnectionFactoryProxy) { // If we got a TransactionAwareConnectionFactoryProxy, we need to perform transactions // for its underlying target ConnectionFactory, else JMS access code won't see // properly exposed transactions (i.e. transactions for the target ConnectionFactory). this.connectionFactory = ((TransactionAwareConnectionFactoryProxy) cf).getTargetConnectionFactory(); } else { this.connectionFactory = cf; } } /** * Return the CCI ConnectionFactory that this instance manages local * transactions for. */ public ConnectionFactory getConnectionFactory() { return this.connectionFactory; } public void afterPropertiesSet() { if (getConnectionFactory() == null) { throw new IllegalArgumentException("Property 'connectionFactory' is required"); } } public Object getResourceFactory() { return getConnectionFactory(); } protected Object doGetTransaction() { CciLocalTransactionObject txObject = new CciLocalTransactionObject(); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(getConnectionFactory()); txObject.setConnectionHolder(conHolder); return txObject; } protected boolean isExistingTransaction(Object transaction) { CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; // Consider a pre-bound connection as transaction. return (txObject.getConnectionHolder() != null); } protected void doBegin(Object transaction, TransactionDefinition definition) { CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; Connection con = null; try { con = getConnectionFactory().getConnection(); if (logger.isDebugEnabled()) { logger.debug("Acquired Connection [" + con + "] for local CCI transaction"); } txObject.setConnectionHolder(new ConnectionHolder(con)); txObject.getConnectionHolder().setSynchronizedWithTransaction(true); con.getLocalTransaction().begin(); int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } TransactionSynchronizationManager.bindResource(getConnectionFactory(), txObject.getConnectionHolder()); } catch (NotSupportedException ex) { ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); throw new CannotCreateTransactionException("CCI Connection does not support local transactions", ex); } catch (LocalTransactionException ex) { ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); throw new CannotCreateTransactionException("Could not begin local CCI transaction", ex); } catch (ResourceException ex) { ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); throw new TransactionSystemException("Unexpected failure on begin of CCI local transaction", ex); } } protected Object doSuspend(Object transaction) { CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; txObject.setConnectionHolder(null); return TransactionSynchronizationManager.unbindResource(getConnectionFactory()); } protected void doResume(Object transaction, Object suspendedResources) { ConnectionHolder conHolder = (ConnectionHolder) suspendedResources; TransactionSynchronizationManager.bindResource(getConnectionFactory(), conHolder); } protected boolean isRollbackOnly(Object transaction) throws TransactionException { CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; return txObject.getConnectionHolder().isRollbackOnly(); } protected void doCommit(DefaultTransactionStatus status) { CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction(); Connection con = txObject.getConnectionHolder().getConnection(); if (status.isDebug()) { logger.debug("Committing CCI local transaction on Connection [" + con + "]"); } try { con.getLocalTransaction().commit(); } catch (LocalTransactionException ex) { throw new TransactionSystemException("Could not commit CCI local transaction", ex); } catch (ResourceException ex) { throw new TransactionSystemException("Unexpected failure on commit of CCI local transaction", ex); } } protected void doRollback(DefaultTransactionStatus status) { CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction(); Connection con = txObject.getConnectionHolder().getConnection(); if (status.isDebug()) { logger.debug("Rolling back CCI local transaction on Connection [" + con + "]"); } try { con.getLocalTransaction().rollback(); } catch (LocalTransactionException ex) { throw new TransactionSystemException("Could not roll back CCI local transaction", ex); } catch (ResourceException ex) { throw new TransactionSystemException("Unexpected failure on rollback of CCI local transaction", ex); } } protected void doSetRollbackOnly(DefaultTransactionStatus status) { CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction(); if (status.isDebug()) { logger.debug("Setting CCI local transaction [" + txObject.getConnectionHolder().getConnection() + "] rollback-only"); } txObject.getConnectionHolder().setRollbackOnly(); } protected void doCleanupAfterCompletion(Object transaction) { CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; // Remove the connection holder from the thread. TransactionSynchronizationManager.unbindResource(getConnectionFactory()); txObject.getConnectionHolder().clear(); Connection con = txObject.getConnectionHolder().getConnection(); if (logger.isDebugEnabled()) { logger.debug("Releasing CCI Connection [" + con + "] after transaction"); } ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); } /** * CCI local transaction object, representing a ConnectionHolder. * Used as transaction object by CciLocalTransactionManager. * @see ConnectionHolder */ private static class CciLocalTransactionObject { private ConnectionHolder connectionHolder; public void setConnectionHolder(ConnectionHolder connectionHolder) { this.connectionHolder = connectionHolder; } public ConnectionHolder getConnectionHolder() { return connectionHolder; } } }