diff --git a/sandbox/src/main/java/org/springframework/ldap/transaction/core/ContextSourceAndDataSourceTransactionManager.java b/sandbox/src/main/java/org/springframework/ldap/transaction/core/ContextSourceAndDataSourceTransactionManager.java new file mode 100644 index 00000000..e36ec974 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ldap/transaction/core/ContextSourceAndDataSourceTransactionManager.java @@ -0,0 +1,203 @@ +package org.springframework.ldap.transaction.core; + +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.ldap.core.ContextSource; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionSuspensionNotSupportedException; +import org.springframework.transaction.support.DefaultTransactionStatus; + +/** + * A Transaction Manager to manage LDAP and JDBC operations within the same + * transaction. Note that even though the same logical transaction is used, this + * is not a JTA XA transaction; no two-phase commit will be performed, + * and thus commit and rollback may result in unexpected results. + * + * @author Mattias Arthursson + */ +public class ContextSourceAndDataSourceTransactionManager extends + DataSourceTransactionManager { + + private static final long serialVersionUID = 6832868697460384648L; + + private ContextSourceTransactionManagerDelegate ldapManagerDelegate = new ContextSourceTransactionManagerDelegate(); + + public ContextSourceAndDataSourceTransactionManager() { + super(); + // Override the default behaviour. + setNestedTransactionAllowed(false); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#isExistingTransaction(java.lang.Object) + */ + protected boolean isExistingTransaction(Object transaction) { + ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) transaction; + + return super.isExistingTransaction(actualTransactionObject + .getDataSourceTransactionObject()); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction() + */ + protected Object doGetTransaction() throws TransactionException { + Object dataSourceTransactionObject = super.doGetTransaction(); + Object contextSourceTransactionObject = ldapManagerDelegate + .doGetTransaction(); + + return new ContextSourceAndDataSourceTransactionObject( + contextSourceTransactionObject, dataSourceTransactionObject); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin(java.lang.Object, + * org.springframework.transaction.TransactionDefinition) + */ + protected void doBegin(Object transaction, TransactionDefinition definition) + throws TransactionException { + ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) transaction; + + super.doBegin(actualTransactionObject.getDataSourceTransactionObject(), + definition); + ldapManagerDelegate.doBegin(actualTransactionObject + .getLdapTransactionObject(), definition); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doCleanupAfterCompletion(java.lang.Object) + */ + protected void doCleanupAfterCompletion(Object transaction) { + ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) transaction; + + super.doCleanupAfterCompletion(actualTransactionObject + .getDataSourceTransactionObject()); + ldapManagerDelegate.doCleanupAfterCompletion(actualTransactionObject + .getLdapTransactionObject()); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doCommit(org.springframework.transaction.support.DefaultTransactionStatus) + */ + protected void doCommit(DefaultTransactionStatus status) + throws TransactionException { + + try { + super.doCommit(new DataSourceTransactionStatus(status + .getTransaction(), status.isNewTransaction(), status + .isNewSynchronization(), status.isReadOnly(), status + .isDebug(), status.getSuspendedResources())); + } catch (TransactionException ex) { + if(isRollbackOnCommitFailure()){ + // If we are to rollback on commit failure, just rethrow the + throw ex; + } + } + ldapManagerDelegate.doCommit(new ContextSourceTransactionStatus(status + .getTransaction(), status.isNewTransaction(), status + .isNewSynchronization(), status.isReadOnly(), status.isDebug(), + status.getSuspendedResources())); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus) + */ + protected void doRollback(DefaultTransactionStatus status) + throws TransactionException { + + super.doRollback(new DataSourceTransactionStatus(status + .getTransaction(), status.isNewTransaction(), status + .isNewSynchronization(), status.isReadOnly(), status.isDebug(), + status.getSuspendedResources())); + ldapManagerDelegate.doRollback(new ContextSourceTransactionStatus( + status.getTransaction(), status.isNewTransaction(), status + .isNewSynchronization(), status.isReadOnly(), status + .isDebug(), status.getSuspendedResources())); + } + + public ContextSource getContextSource() { + return ldapManagerDelegate.getContextSource(); + } + + public void setContextSource(ContextSource contextSource) { + ldapManagerDelegate.setContextSource(contextSource); + } + + protected void setRenamingStrategy( + TempEntryRenamingStrategy renamingStrategy) { + ldapManagerDelegate.setRenamingStrategy(renamingStrategy); + } + + private class ContextSourceAndDataSourceTransactionObject { + private Object ldapTransactionObject; + + private Object dataSourceTransactionObject; + + public ContextSourceAndDataSourceTransactionObject( + Object ldapTransactionObject, Object dataSourceTransactionObject) { + this.ldapTransactionObject = ldapTransactionObject; + this.dataSourceTransactionObject = dataSourceTransactionObject; + } + + public Object getDataSourceTransactionObject() { + return dataSourceTransactionObject; + } + + public Object getLdapTransactionObject() { + return ldapTransactionObject; + } + } + + protected Object doSuspend(Object transaction) throws TransactionException { + throw new TransactionSuspensionNotSupportedException( + "Transaction manager [" + getClass().getName() + + "] does not support transaction suspension"); + } + + protected void doResume(Object transaction, Object suspendedResources) + throws TransactionException { + throw new TransactionSuspensionNotSupportedException( + "Transaction manager [" + getClass().getName() + + "] does not support transaction suspension"); + } + + private class DataSourceTransactionStatus extends DefaultTransactionStatus { + + public DataSourceTransactionStatus(Object transaction, + boolean newTransaction, boolean newSynchronization, + boolean readOnly, boolean debug, Object suspendedResources) { + + super(((ContextSourceAndDataSourceTransactionObject) transaction) + .getDataSourceTransactionObject(), newTransaction, + newSynchronization, readOnly, debug, suspendedResources); + } + + } + + private class ContextSourceTransactionStatus extends + DefaultTransactionStatus { + + public ContextSourceTransactionStatus(Object transaction, + boolean newTransaction, boolean newSynchronization, + boolean readOnly, boolean debug, Object suspendedResources) { + + super(((ContextSourceAndDataSourceTransactionObject) transaction) + .getLdapTransactionObject(), newTransaction, + newSynchronization, readOnly, debug, suspendedResources); + } + + } + +} diff --git a/sandbox/src/main/java/org/springframework/ldap/transaction/core/ContextSourceTransactionManagerDelegate.java b/sandbox/src/main/java/org/springframework/ldap/transaction/core/ContextSourceTransactionManagerDelegate.java new file mode 100644 index 00000000..0d1f5d90 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ldap/transaction/core/ContextSourceTransactionManagerDelegate.java @@ -0,0 +1,148 @@ +package org.springframework.ldap.transaction.core; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.core.ContextSource; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.support.DefaultTransactionStatus; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * This delegate performs all the work for the + * {@link ContextSourceTransactionManager}. The work is delegated in order to + * be able to perform the exact same work for the LDAP part in + * {@link ContextSourceAndDataSourceTransactionManager}. + * + * @author Mattias Arthursson + * @see ContextSourceTransactionManager + * @see ContextSourceAndDataSourceTransactionManager + */ +public class ContextSourceTransactionManagerDelegate { + private static Log log = LogFactory + .getLog(ContextSourceTransactionManager.class); + + private ContextSource contextSource; + + private TempEntryRenamingStrategy renamingStrategy = new DefaultTempEntryRenamingStrategy(); + + /** + * Set the ContextSource to work on. Even though the actual ContextSource + * sent to the LdapTemplate instance should be a + * {@link TransactionAwareContextSourceProxy}, the one sent to this method + * should be the target of that proxy. If it is not, the target will be + * extracted and used instead. + * + * @param contextSource + * the ContextSource to work on. + */ + public void setContextSource(ContextSource contextSource) { + if (contextSource instanceof TransactionAwareContextSourceProxy) { + TransactionAwareContextSourceProxy proxy = (TransactionAwareContextSourceProxy) contextSource; + this.contextSource = proxy.getTarget(); + } else { + this.contextSource = contextSource; + } + } + + public ContextSource getContextSource() { + return contextSource; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doGetTransaction() + */ + public Object doGetTransaction() throws TransactionException { + DirContextHolder contextHolder = (DirContextHolder) TransactionSynchronizationManager + .getResource(this.contextSource); + ContextSourceTransactionObject txObject = new ContextSourceTransactionObject( + contextHolder); + return txObject; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doBegin(java.lang.Object, + * org.springframework.transaction.TransactionDefinition) + */ + public void doBegin(Object transaction, TransactionDefinition definition) + throws TransactionException { + ContextSourceTransactionObject txObject = (ContextSourceTransactionObject) transaction; + + if (txObject.getContextHolder() == null) { + DirContext newCtx = getContextSource().getReadOnlyContext(); + DirContextHolder contextHolder = new DirContextHolder(newCtx, + renamingStrategy); + + txObject.setContextHolder(contextHolder); + TransactionSynchronizationManager.bindResource(getContextSource(), + contextHolder); + } + } + + /* + * (non-Javadoc) + * + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doCommit(org.springframework.transaction.support.DefaultTransactionStatus) + */ + public void doCommit(DefaultTransactionStatus status) + throws TransactionException { + ContextSourceTransactionObject txObject = (ContextSourceTransactionObject) status + .getTransaction(); + txObject.getContextHolder().getTransactionDataManager().commit(); + + } + + /* + * (non-Javadoc) + * + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus) + */ + public void doRollback(DefaultTransactionStatus status) + throws TransactionException { + ContextSourceTransactionObject txObject = (ContextSourceTransactionObject) status + .getTransaction(); + txObject.getContextHolder().getTransactionDataManager().rollback(); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doCleanupAfterCompletion(java.lang.Object) + */ + public void doCleanupAfterCompletion(Object transaction) { + log.debug("Cleaning stored ContextHolder"); + TransactionSynchronizationManager.unbindResource(contextSource); + + ContextSourceTransactionObject txObject = (ContextSourceTransactionObject) transaction; + DirContext ctx = txObject.getContextHolder().getCtx(); + + try { + log.debug("Closing target context"); + ctx.close(); + } catch (NamingException e) { + e.printStackTrace(); + } + + txObject.getContextHolder().clear(); + } + + /** + * Set the {@link TempEntryRenamingStrategy} to be used when renaming + * temporary entries in unbind and rebind operations. Default value is a + * {@link DefaultTempEntryRenamingStrategy}. + * + * @param renamingStrategy + * the {@link TempEntryRenamingStrategy} to use. + */ + public void setRenamingStrategy(TempEntryRenamingStrategy renamingStrategy) { + this.renamingStrategy = renamingStrategy; + } + +}