/*
* 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();
}
@Override
protected Object doGetTransaction() {
CciLocalTransactionObject txObject = new CciLocalTransactionObject();
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(getConnectionFactory());
txObject.setConnectionHolder(conHolder);
return txObject;
}
@Override
protected boolean isExistingTransaction(Object transaction) {
CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction;
// Consider a pre-bound connection as transaction.
return (txObject.getConnectionHolder() != null);
}
@Override
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);
}
}
@Override
protected Object doSuspend(Object transaction) {
CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction;
txObject.setConnectionHolder(null);
return TransactionSynchronizationManager.unbindResource(getConnectionFactory());
}
@Override
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();
}
@Override
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);
}
}
@Override
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);
}
}
@Override
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();
}
@Override
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;
}
}
}
|