org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager Maven / Gradle / Ivy
/*
* Copyright 2005-2013 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
*
* https://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.ldap.transaction.compensating.manager;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy;
import org.springframework.ldap.transaction.compensating.UnbindOperationExecutor;
import org.springframework.ldap.transaction.compensating.support.DefaultTempEntryRenamingStrategy;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor;
import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder;
import org.springframework.transaction.compensating.support.CompensatingTransactionObject;
import org.springframework.transaction.compensating.support.DefaultCompensatingTransactionOperationManager;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
/**
* TransactionManager for managing LDAP transactions. Since transactions are not supported
* in the LDAP protocol, this class and its collaborators aim to provide
* compensating transactions instead. Should a transaction need to be rolled
* back, this TransactionManager will try to restore the original state using information
* recorded prior to each operation. The operation where the original state is restored is
* called a compensating operation.
*
* NOTE: The transactions provided by this TransactionManager are all client
* side and are by no means 'real' transactions, in the sense that we know them in the
* ordinary database world, e.g.:
*
* - Should the transaction failure be caused by a network failure, there is no way
* whatsoever that this TransactionManager can restore the database state. In this case,
* all possibilities for rollback will be utterly lost.
* - Transaction isolation is not provided, i.e. entries participating in a transaction
* for one client may very well participate in another transaction for another client at
* the same time. Should one of these transactions be rolled back, the outcome of this is
* undetermined, and may in the worst case result in total failure.
*
*
* While the points above should be noted and considered, the compensating transaction
* approach will be perfectly sufficient for all but the most unfortunate of
* circumstances. Considering that there currently is a total absence of server-side
* transaction support in the LDAP world, being able to mark operations as transactional
* in the same way as for relational database operations is surely a step forward.
*
* An LDAP transaction is tied to a {@link ContextSource}, to be supplied to the
* {@link #setContextSource(ContextSource)} method. While the actual ContextSource used by
* the target LdapTemplate instance needs to be of the type
* {@link TransactionAwareContextSourceProxy}, the ContextSource supplied to this class
* should be the actual target ContextSource.
*
* Using this TransactionManager along with {@link TransactionAwareContextSourceProxy},
* all modifying operations (bind, unbind, rebind, rename, modifyAttributes) in a
* transaction will be intercepted. Each modification has its corresponding
* {@link CompensatingTransactionOperationRecorder}, which collects the information
* necessary to perform a rollback and produces a
* {@link CompensatingTransactionOperationExecutor} which is then used to execute the
* actual operation and is later called for performing the commit or rollback.
*
* For several of the operations, performing a rollback is pretty straightforward. For
* example, in order to roll back a rename operation, it will only be required to rename
* the entry back to its original position. For other operations, however, it's a bit more
* complicated. An unbind operation is not possible to roll back by simply binding the
* entry back with the attributes retrieved from the original entry. It might not be
* possible to get all the information from the original entry. Consequently, the
* {@link UnbindOperationExecutor} will move the original entry to a temporary location in
* its performOperation() method. The commit() method will know that everything went well,
* so it will be OK to unbind the entry. The rollback operation will be to rename the
* entry back to its original location. The same behaviour is used for rebind()
* operations. The operation of calculating a temporary location for an entry is delegated
* to a {@link TempEntryRenamingStrategy} (default
* {@link DefaultTempEntryRenamingStrategy}), specified in
* {@link #setRenamingStrategy(TempEntryRenamingStrategy)}.
*
* The actual work of this Transaction Manager is delegated to a
* {@link ContextSourceTransactionManagerDelegate}. This is because the exact same logic
* needs to be used if we want to wrap a JDBC and LDAP transaction in the same logical
* transaction.
*
*
* @author Mattias Hellborg Arthursson
* @since 1.2
* @see ContextSourceAndDataSourceTransactionManager
* @see ContextSourceTransactionManagerDelegate
* @see DefaultCompensatingTransactionOperationManager
* @see TempEntryRenamingStrategy
* @see TransactionAwareContextSourceProxy
*/
public class ContextSourceTransactionManager extends AbstractPlatformTransactionManager implements InitializingBean {
private static final long serialVersionUID = 7138208218687237856L;
private ContextSourceTransactionManagerDelegate delegate = new ContextSourceTransactionManagerDelegate();
/*
* @see
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doBegin(
* java.lang.Object, org.springframework.transaction.TransactionDefinition)
*/
protected void doBegin(Object transaction, TransactionDefinition definition) {
this.delegate.doBegin(transaction, definition);
}
/*
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#
* doCleanupAfterCompletion(java.lang.Object)
*/
protected void doCleanupAfterCompletion(Object transaction) {
this.delegate.doCleanupAfterCompletion(transaction);
}
/*
* @see
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doCommit
* (org.springframework.transaction.support.DefaultTransactionStatus)
*/
protected void doCommit(DefaultTransactionStatus status) {
this.delegate.doCommit(status);
}
/*
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#
* doGetTransaction()
*/
protected Object doGetTransaction() {
return this.delegate.doGetTransaction();
}
/*
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#
* doRollback(org.springframework.transaction.support.DefaultTransactionStatus)
*/
protected void doRollback(DefaultTransactionStatus status) {
this.delegate.doRollback(status);
}
/**
* Get the ContextSource.
* @return the contextSource.
* @see ContextSourceTransactionManagerDelegate#getContextSource()
*/
public ContextSource getContextSource() {
return this.delegate.getContextSource();
}
/**
* Set the ContextSource.
* @param contextSource the ContextSource.
* @see ContextSourceTransactionManagerDelegate#setContextSource(ContextSource)
*/
public void setContextSource(ContextSource contextSource) {
this.delegate.setContextSource(contextSource);
}
/**
* Set the {@link TempEntryRenamingStrategy}.
* @param renamingStrategy the Renaming Strategy.
* @see ContextSourceTransactionManagerDelegate#setRenamingStrategy(TempEntryRenamingStrategy)
*/
public void setRenamingStrategy(TempEntryRenamingStrategy renamingStrategy) {
this.delegate.setRenamingStrategy(renamingStrategy);
}
public void afterPropertiesSet() throws Exception {
this.delegate.checkRenamingStrategy();
}
@Override
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
CompensatingTransactionObject txObject = (CompensatingTransactionObject) transaction;
return (txObject.getHolder() != null);
}
}