diff --git a/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java b/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java index 77f699d9..ab25e9f2 100644 --- a/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java +++ b/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java @@ -83,7 +83,7 @@ public class ContextSourceTransactionManagerDelegate extends * @see org.springframework.transaction.compensating.support.AbstractCompensatingTransactionManagerDelegate#getNewHolder() */ protected CompensatingTransactionHolderSupport getNewHolder() { - DirContext newCtx = getContextSource().getReadOnlyContext(); + DirContext newCtx = getContextSource().getReadWriteContext(); DirContextHolder contextHolder = new DirContextHolder( new DefaultCompensatingTransactionOperationManager( new LdapCompensatingTransactionOperationFactory( diff --git a/core/src/main/java/org/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java b/core/src/main/java/org/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java index 6288d2fe..96618db6 100644 --- a/core/src/main/java/org/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java +++ b/core/src/main/java/org/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java @@ -17,6 +17,9 @@ package org.springframework.transaction.compensating.support; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.NamingException; +import org.springframework.ldap.support.LdapUtils; +import org.springframework.transaction.CannotCreateTransactionException; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.support.DefaultTransactionStatus; @@ -32,99 +35,100 @@ import org.springframework.transaction.support.TransactionSynchronizationManager */ public abstract class AbstractCompensatingTransactionManagerDelegate { - private static Log log = LogFactory - .getLog(AbstractCompensatingTransactionManagerDelegate.class); + private static Log log = LogFactory.getLog(AbstractCompensatingTransactionManagerDelegate.class); - /** - * Close the target resource - the implementation specific resource held in - * the specified {@link CompensatingTransactionHolderSupport}. - * - * @param transactionHolderSupport - * the {@link CompensatingTransactionHolderSupport} that holds - * the transaction specific target resource. - */ - protected abstract void closeTargetResource( - CompensatingTransactionHolderSupport transactionHolderSupport); + /** + * Close the target resource - the implementation specific resource held in + * the specified {@link CompensatingTransactionHolderSupport}. + * + * @param transactionHolderSupport the + * {@link CompensatingTransactionHolderSupport} that holds the transaction + * specific target resource. + */ + protected abstract void closeTargetResource(CompensatingTransactionHolderSupport transactionHolderSupport); - /** - * Get a new implementation specific - * {@link CompensatingTransactionHolderSupport} instance. - * - * @return a new {@link CompensatingTransactionHolderSupport} instance. - */ - protected abstract CompensatingTransactionHolderSupport getNewHolder(); + /** + * Get a new implementation specific + * {@link CompensatingTransactionHolderSupport} instance. + * + * @return a new {@link CompensatingTransactionHolderSupport} instance. + */ + protected abstract CompensatingTransactionHolderSupport getNewHolder(); - /** - * Get the key (normally, a DataSource or similar) that should be used for - * transaction synchronization. - * - * @return the transaction synchronization key - */ - protected abstract Object getTransactionSynchronizationKey(); + /** + * Get the key (normally, a DataSource or similar) that should be used for + * transaction synchronization. + * + * @return the transaction synchronization key + */ + protected abstract Object getTransactionSynchronizationKey(); - /* - * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction() - */ - public Object doGetTransaction() throws TransactionException { - CompensatingTransactionHolderSupport holder = (CompensatingTransactionHolderSupport) TransactionSynchronizationManager - .getResource(getTransactionSynchronizationKey()); - CompensatingTransactionObject txObject = new CompensatingTransactionObject( - holder); - return txObject; - } + /* + * @seeorg.springframework.jdbc.datasource.DataSourceTransactionManager# + * doGetTransaction() + */ + public Object doGetTransaction() throws TransactionException { + CompensatingTransactionHolderSupport holder = (CompensatingTransactionHolderSupport) TransactionSynchronizationManager + .getResource(getTransactionSynchronizationKey()); + CompensatingTransactionObject txObject = new CompensatingTransactionObject(holder); + return txObject; + } - /* - * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin(java.lang.Object, - * org.springframework.transaction.TransactionDefinition) - */ - public void doBegin(Object transaction, TransactionDefinition definition) - throws TransactionException { - CompensatingTransactionObject txObject = (CompensatingTransactionObject) transaction; + /* + * @see + * org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin + * (java.lang.Object, org.springframework.transaction.TransactionDefinition) + */ + public void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { + try { + CompensatingTransactionObject txObject = (CompensatingTransactionObject) transaction; + if (txObject.getHolder() == null) { + CompensatingTransactionHolderSupport contextHolder = getNewHolder(); + txObject.setHolder(contextHolder); - if (txObject.getHolder() == null) { - CompensatingTransactionHolderSupport contextHolder = getNewHolder(); - txObject.setHolder(contextHolder); + TransactionSynchronizationManager.bindResource(getTransactionSynchronizationKey(), contextHolder); + } + } + catch (NamingException e) { + throw new CannotCreateTransactionException("Could not create DirContext instance for transaction", e); + } + } - TransactionSynchronizationManager.bindResource( - getTransactionSynchronizationKey(), contextHolder); - } - } + /* + * @see + * org.springframework.jdbc.datasource.DataSourceTransactionManager#doCommit + * (org.springframework.transaction.support.DefaultTransactionStatus) + */ + public void doCommit(DefaultTransactionStatus status) throws TransactionException { + CompensatingTransactionObject txObject = (CompensatingTransactionObject) status.getTransaction(); + txObject.getHolder().getTransactionOperationManager().commit(); - /* - * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doCommit(org.springframework.transaction.support.DefaultTransactionStatus) - */ - public void doCommit(DefaultTransactionStatus status) - throws TransactionException { - CompensatingTransactionObject txObject = (CompensatingTransactionObject) status - .getTransaction(); - txObject.getHolder().getTransactionOperationManager().commit(); + } - } + /* + * @see + * org.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback + * (org.springframework.transaction.support.DefaultTransactionStatus) + */ + public void doRollback(DefaultTransactionStatus status) throws TransactionException { + CompensatingTransactionObject txObject = (CompensatingTransactionObject) status.getTransaction(); + txObject.getHolder().getTransactionOperationManager().rollback(); + } - /* - * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus) - */ - public void doRollback(DefaultTransactionStatus status) - throws TransactionException { - CompensatingTransactionObject txObject = (CompensatingTransactionObject) status - .getTransaction(); - txObject.getHolder().getTransactionOperationManager().rollback(); - } + /* + * @seeorg.springframework.jdbc.datasource.DataSourceTransactionManager# + * doCleanupAfterCompletion(java.lang.Object) + */ + public void doCleanupAfterCompletion(Object transaction) { + log.debug("Cleaning stored transaction synchronization"); + TransactionSynchronizationManager.unbindResource(getTransactionSynchronizationKey()); - /* - * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doCleanupAfterCompletion(java.lang.Object) - */ - public void doCleanupAfterCompletion(Object transaction) { - log.debug("Cleaning stored transaction synchronization"); - TransactionSynchronizationManager - .unbindResource(getTransactionSynchronizationKey()); + CompensatingTransactionObject txObject = (CompensatingTransactionObject) transaction; + CompensatingTransactionHolderSupport transactionHolderSupport = (CompensatingTransactionHolderSupport) txObject + .getHolder(); - CompensatingTransactionObject txObject = (CompensatingTransactionObject) transaction; - CompensatingTransactionHolderSupport transactionHolderSupport = (CompensatingTransactionHolderSupport) txObject - .getHolder(); + closeTargetResource(transactionHolderSupport); - closeTargetResource(transactionHolderSupport); - - txObject.getHolder().clear(); - } + txObject.getHolder().clear(); + } } \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerTest.java b/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerTest.java index 3a044c1d..7c2d6fa0 100644 --- a/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerTest.java +++ b/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerTest.java @@ -16,186 +16,239 @@ package org.springframework.ldap.transaction.compensating.manager; +import java.sql.Connection; +import java.sql.SQLException; + import javax.naming.directory.DirContext; +import javax.sql.DataSource; import junit.framework.TestCase; import org.easymock.MockControl; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.ldap.UncategorizedLdapException; import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; -import org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager; -import org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy; +import org.springframework.transaction.CannotCreateTransactionException; +import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.compensating.CompensatingTransactionOperationManager; import org.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport; import org.springframework.transaction.compensating.support.CompensatingTransactionObject; +import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.DefaultTransactionStatus; import org.springframework.transaction.support.TransactionSynchronizationManager; public class ContextSourceTransactionManagerTest extends TestCase { - private MockControl contextSourceControl; + private MockControl contextSourceControl; - private ContextSource contextSourceMock; + private ContextSource contextSourceMock; - private MockControl contextControl; + private MockControl contextControl; - private DirContext contextMock; + private DirContext contextMock; - private ContextSourceTransactionManager tested; + private ContextSourceTransactionManager tested; - private MockControl transactionDefinitionControl; + private MockControl transactionDefinitionControl; - private MockControl transactionDataManagerControl; + private MockControl transactionDataManagerControl; - private CompensatingTransactionOperationManager transactionDataManagerMock; + private CompensatingTransactionOperationManager transactionDataManagerMock; - private TransactionDefinition transactionDefinitionMock; + private TransactionDefinition transactionDefinitionMock; - private MockControl renamingStrategyControl; + private MockControl renamingStrategyControl; - private TempEntryRenamingStrategy renamingStrategyMock; + private TempEntryRenamingStrategy renamingStrategyMock; - protected void setUp() throws Exception { - super.setUp(); - if (TransactionSynchronizationManager.isSynchronizationActive()) { - TransactionSynchronizationManager.clearSynchronization(); - } + protected void setUp() throws Exception { + super.setUp(); + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clearSynchronization(); + } - contextSourceControl = MockControl.createControl(ContextSource.class); - contextSourceMock = (ContextSource) contextSourceControl.getMock(); + contextSourceControl = MockControl.createControl(ContextSource.class); + contextSourceMock = (ContextSource) contextSourceControl.getMock(); - contextControl = MockControl.createControl(DirContext.class); - contextMock = (DirContext) contextControl.getMock(); + contextControl = MockControl.createControl(DirContext.class); + contextMock = (DirContext) contextControl.getMock(); - transactionDefinitionControl = MockControl - .createControl(TransactionDefinition.class); - transactionDefinitionMock = (TransactionDefinition) transactionDefinitionControl - .getMock(); + transactionDefinitionControl = MockControl.createControl(TransactionDefinition.class); + transactionDefinitionMock = (TransactionDefinition) transactionDefinitionControl.getMock(); - transactionDataManagerControl = MockControl - .createControl(CompensatingTransactionOperationManager.class); - transactionDataManagerMock = (CompensatingTransactionOperationManager) transactionDataManagerControl - .getMock(); + transactionDataManagerControl = MockControl.createControl(CompensatingTransactionOperationManager.class); + transactionDataManagerMock = (CompensatingTransactionOperationManager) transactionDataManagerControl.getMock(); - renamingStrategyControl = MockControl - .createControl(TempEntryRenamingStrategy.class); - renamingStrategyMock = (TempEntryRenamingStrategy) renamingStrategyControl - .getMock(); + renamingStrategyControl = MockControl.createControl(TempEntryRenamingStrategy.class); + renamingStrategyMock = (TempEntryRenamingStrategy) renamingStrategyControl.getMock(); - tested = new ContextSourceTransactionManager(); - tested.setContextSource(contextSourceMock); - tested.setRenamingStrategy(renamingStrategyMock); - } + tested = new ContextSourceTransactionManager(); + tested.setContextSource(contextSourceMock); + tested.setRenamingStrategy(renamingStrategyMock); + } - protected void tearDown() throws Exception { - super.tearDown(); + protected void tearDown() throws Exception { + super.tearDown(); - contextControl = null; - contextMock = null; + contextControl = null; + contextMock = null; - contextSourceControl = null; - contextSourceMock = null; + contextSourceControl = null; + contextSourceMock = null; - transactionDefinitionControl = null; - transactionDefinitionMock = null; + transactionDefinitionControl = null; + transactionDefinitionMock = null; - transactionDataManagerControl = null; - transactionDataManagerMock = null; + transactionDataManagerControl = null; + transactionDataManagerMock = null; - renamingStrategyControl = null; - renamingStrategyMock = null; + renamingStrategyControl = null; + renamingStrategyMock = null; - if (TransactionSynchronizationManager.isSynchronizationActive()) { - TransactionSynchronizationManager.clearSynchronization(); - } - } + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clearSynchronization(); + } + } - public void testDoGetTransaction() { - Object result = tested.doGetTransaction(); + public void testDoGetTransaction() { + Object result = tested.doGetTransaction(); - assertNotNull(result); - assertTrue(result instanceof CompensatingTransactionObject); - CompensatingTransactionObject transactionObject = (CompensatingTransactionObject) result; - assertNull(transactionObject.getHolder()); - } + assertNotNull(result); + assertTrue(result instanceof CompensatingTransactionObject); + CompensatingTransactionObject transactionObject = (CompensatingTransactionObject) result; + assertNull(transactionObject.getHolder()); + } - public void testDoGetTransactionTransactionActive() { - CompensatingTransactionHolderSupport expectedContextHolder = new DirContextHolder( - null, null); - TransactionSynchronizationManager.bindResource(contextSourceMock, - expectedContextHolder); - Object result = tested.doGetTransaction(); - assertSame(expectedContextHolder, - ((CompensatingTransactionObject) result).getHolder()); - } + public void testDoGetTransactionTransactionActive() { + CompensatingTransactionHolderSupport expectedContextHolder = new DirContextHolder(null, null); + TransactionSynchronizationManager.bindResource(contextSourceMock, expectedContextHolder); + Object result = tested.doGetTransaction(); + assertSame(expectedContextHolder, ((CompensatingTransactionObject) result).getHolder()); + } - public void testDoBegin() { - contextSourceControl.expectAndReturn(contextSourceMock - .getReadOnlyContext(), contextMock); + public void testDoBegin() { + contextSourceControl.expectAndReturn(contextSourceMock.getReadWriteContext(), contextMock); - contextSourceControl.replay(); + contextSourceControl.replay(); - CompensatingTransactionObject expectedTransactionObject = new CompensatingTransactionObject( - null); - tested.doBegin(expectedTransactionObject, transactionDefinitionMock); + CompensatingTransactionObject expectedTransactionObject = new CompensatingTransactionObject(null); + tested.doBegin(expectedTransactionObject, transactionDefinitionMock); - contextSourceControl.verify(); + contextSourceControl.verify(); - DirContextHolder foundContextHolder = (DirContextHolder) TransactionSynchronizationManager - .getResource(contextSourceMock); - assertSame(contextMock, foundContextHolder.getCtx()); - } + DirContextHolder foundContextHolder = (DirContextHolder) TransactionSynchronizationManager + .getResource(contextSourceMock); + assertSame(contextMock, foundContextHolder.getCtx()); + } - public void testDoCommit() { - } + public void testDoCommit() { + } - public void testDoRollback() { + public void testDoRollback() { - DirContextHolder expectedContextHolder = new DirContextHolder(null, - contextMock); - expectedContextHolder - .setTransactionOperationManager(transactionDataManagerMock); - TransactionSynchronizationManager.bindResource(contextSourceMock, - expectedContextHolder); + DirContextHolder expectedContextHolder = new DirContextHolder(null, contextMock); + expectedContextHolder.setTransactionOperationManager(transactionDataManagerMock); + TransactionSynchronizationManager.bindResource(contextSourceMock, expectedContextHolder); - transactionDataManagerMock.rollback(); - transactionDataManagerControl.replay(); - CompensatingTransactionObject transactionObject = new CompensatingTransactionObject( - null); - transactionObject.setHolder(expectedContextHolder); - tested.doRollback(new DefaultTransactionStatus(transactionObject, - false, false, false, false, null)); - transactionDataManagerControl.verify(); - } + transactionDataManagerMock.rollback(); + transactionDataManagerControl.replay(); + CompensatingTransactionObject transactionObject = new CompensatingTransactionObject(null); + transactionObject.setHolder(expectedContextHolder); + tested.doRollback(new DefaultTransactionStatus(transactionObject, false, false, false, false, null)); + transactionDataManagerControl.verify(); + } - public void testDoCleanupAfterCompletion() throws Exception { - DirContextHolder expectedContextHolder = new DirContextHolder(null, - contextMock); - TransactionSynchronizationManager.bindResource(contextSourceMock, - expectedContextHolder); + public void testDoCleanupAfterCompletion() throws Exception { + DirContextHolder expectedContextHolder = new DirContextHolder(null, contextMock); + TransactionSynchronizationManager.bindResource(contextSourceMock, expectedContextHolder); - contextMock.close(); - contextControl.replay(); + contextMock.close(); + contextControl.replay(); - tested.doCleanupAfterCompletion(new CompensatingTransactionObject( - expectedContextHolder)); + tested.doCleanupAfterCompletion(new CompensatingTransactionObject(expectedContextHolder)); - contextControl.verify(); - assertNull(TransactionSynchronizationManager - .getResource(contextSourceMock)); - assertNull(expectedContextHolder.getTransactionOperationManager()); - } + contextControl.verify(); + assertNull(TransactionSynchronizationManager.getResource(contextSourceMock)); + assertNull(expectedContextHolder.getTransactionOperationManager()); + } - public void testSetContextSource_Proxy() { - TransactionAwareContextSourceProxy proxy = new TransactionAwareContextSourceProxy( - contextSourceMock); + public void testSetContextSource_Proxy() { + TransactionAwareContextSourceProxy proxy = new TransactionAwareContextSourceProxy(contextSourceMock); - // Perform test - tested.setContextSource(proxy); - ContextSource result = tested.getContextSource(); + // Perform test + tested.setContextSource(proxy); + ContextSource result = tested.getContextSource(); - // Verify result - assertSame(contextSourceMock, result); - } + // Verify result + assertSame(contextSourceMock, result); + } + + public void testTransactionSuspension_UnconnectableDataSource() throws Exception { + MockControl connectionControl = MockControl.createControl(Connection.class); + Connection connectionMock = (Connection) connectionControl.getMock(); + MockControl dataSourceControl=MockControl.createControl(DataSource.class); + DataSource dataSourceMock = (DataSource) dataSourceControl.getMock(); + + dataSourceControl.expectAndReturn(dataSourceMock.getConnection(), connectionMock); + connectionControl.expectAndReturn(connectionMock.getAutoCommit(), false); + connectionMock.rollback(); + + MockControl unconnectableContextSourceControl = MockControl.createControl(ContextSource.class); + ContextSource unconnectableContextSourceMock = (ContextSource) unconnectableContextSourceControl.getMock(); + + UncategorizedLdapException connectException = new UncategorizedLdapException("dummy"); + unconnectableContextSourceControl.expectAndThrow(unconnectableContextSourceMock.getReadWriteContext(), connectException); + + connectionControl.replay(); + dataSourceControl.replay(); + unconnectableContextSourceControl.replay(); + + try { + // Create an outer transaction + final PlatformTransactionManager txMgrOuter = new DataSourceTransactionManager(dataSourceMock); + + final TransactionStatus txOuter = txMgrOuter.getTransaction(new DefaultTransactionDefinition()); + + try { + // Create inner transaction (not nested, though: unrelated data + // source) + final ContextSourceTransactionManager txMgrInner = new ContextSourceTransactionManager(); + txMgrInner.setContextSource(unconnectableContextSourceMock); + + final TransactionStatus txInner = txMgrInner.getTransaction(new DefaultTransactionDefinition( + TransactionDefinition.PROPAGATION_REQUIRES_NEW)); + + try { + // Do something with the connection that succeeds or fails + // (but we dont get this far) + // etc, etc... + + txMgrInner.commit(txInner); + } + catch (Exception e) { + txMgrInner.rollback(txInner); + throw e; + } + + txMgrOuter.commit(txOuter); + } + catch (Exception e) { + txMgrOuter.rollback(txOuter); + throw e; + } + + fail("Exception should be thrown"); + } + catch (CannotCreateTransactionException expected) { + assertSame("Should be thrown exception", connectException, expected.getCause()); + } + + connectionControl.verify(); + dataSourceControl.verify(); + unconnectableContextSourceControl.verify(); + + } }