diff --git a/src/main/java/org/springframework/data/transaction/ChainedTransactionManager.java b/src/main/java/org/springframework/data/transaction/ChainedTransactionManager.java
new file mode 100644
index 000000000..324ae5ee2
--- /dev/null
+++ b/src/main/java/org/springframework/data/transaction/ChainedTransactionManager.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2011-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
+ *
+ * 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.data.transaction;
+
+import static java.util.Arrays.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.transaction.CannotCreateTransactionException;
+import org.springframework.transaction.HeuristicCompletionException;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.UnexpectedRollbackException;
+import org.springframework.util.Assert;
+
+/**
+ * {@link PlatformTransactionManager} implementation that orchestrates transaction creation, commits and rollbacks to a
+ * list of delegates. Using this implementation assumes that errors causing a transaction rollback will usually happen
+ * before the transaction completion or during the commit of the most inner {@link PlatformTransactionManager}.
+ *
+ * The configured instances will start transactions in the order given and commit/rollback in reverse order,
+ * which means the {@link PlatformTransactionManager} most likely to break the transaction should be the last
+ * in the list configured. A {@link PlatformTransactionManager} throwing an exception during commit will automatically
+ * cause the remaining transaction managers to roll back instead of committing.
+ *
+ * @author Michael Hunger
+ * @author Oliver Gierke
+ * @since 1.6
+ */
+public class ChainedTransactionManager implements PlatformTransactionManager {
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(ChainedTransactionManager.class);
+
+ private final List transactionManagers;
+ private final SynchronizationManager synchronizationManager;
+
+ /**
+ * Creates a new {@link ChainedTransactionManager} delegating to the given {@link PlatformTransactionManager}s.
+ *
+ * @param transactionManagers must not be {@literal null} or empty.
+ */
+ public ChainedTransactionManager(PlatformTransactionManager... transactionManagers) {
+ this(SpringTransactionSynchronizationManager.INSTANCE, transactionManagers);
+ }
+
+ /**
+ * Creates a new {@link ChainedTransactionManager} using the given {@link SynchronizationManager} and
+ * {@link PlatformTransactionManager}s.
+ *
+ * @param synchronizationManager must not be {@literal null}.
+ * @param transactionManagers must not be {@literal null} or empty.
+ */
+ ChainedTransactionManager(SynchronizationManager synchronizationManager,
+ PlatformTransactionManager... transactionManagers) {
+
+ Assert.notNull(synchronizationManager, "SynchronizationManager must not be null!");
+ Assert.notNull(transactionManagers, "Transaction managers must not be null!");
+ Assert.isTrue(transactionManagers.length > 0, "At least one PlatformTransactionManager must be given!");
+
+ this.synchronizationManager = synchronizationManager;
+ this.transactionManagers = asList(transactionManagers);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.PlatformTransactionManager#getTransaction(org.springframework.transaction.TransactionDefinition)
+ */
+ public MultiTransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
+
+ MultiTransactionStatus mts = new MultiTransactionStatus(transactionManagers.get(0));
+
+ if (!synchronizationManager.isSynchronizationActive()) {
+ synchronizationManager.initSynchronization();
+ mts.setNewSynchonization();
+ }
+
+ try {
+
+ for (PlatformTransactionManager transactionManager : transactionManagers) {
+ mts.registerTransactionManager(definition, transactionManager);
+ }
+
+ } catch (Exception ex) {
+
+ Map transactionStatuses = mts.getTransactionStatuses();
+
+ for (PlatformTransactionManager transactionManager : transactionManagers) {
+ try {
+ if (transactionStatuses.get(transactionManager) != null) {
+ transactionManager.rollback(transactionStatuses.get(transactionManager));
+ }
+ } catch (Exception ex2) {
+ LOGGER.warn("Rollback exception (" + transactionManager + ") " + ex2.getMessage(), ex2);
+ }
+ }
+
+ if (mts.isNewSynchonization()) {
+ synchronizationManager.clearSynchronization();
+ }
+
+ throw new CannotCreateTransactionException(ex.getMessage(), ex);
+ }
+
+ return mts;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.PlatformTransactionManager#commit(org.springframework.transaction.TransactionStatus)
+ */
+ public void commit(TransactionStatus status) throws TransactionException {
+
+ MultiTransactionStatus multiTransactionStatus = (MultiTransactionStatus) status;
+
+ boolean commit = true;
+ Exception commitException = null;
+ PlatformTransactionManager commitExceptionTransactionManager = null;
+
+ for (PlatformTransactionManager transactionManager : reverse(transactionManagers)) {
+
+ if (commit) {
+
+ try {
+ multiTransactionStatus.commit(transactionManager);
+ } catch (Exception ex) {
+ commit = false;
+ commitException = ex;
+ commitExceptionTransactionManager = transactionManager;
+ }
+
+ } else {
+
+ // after unsucessfull commit we must try to rollback remaining transaction managers
+
+ try {
+ multiTransactionStatus.rollback(transactionManager);
+ } catch (Exception ex) {
+ LOGGER.warn("Rollback exception (after commit) (" + transactionManager + ") " + ex.getMessage(), ex);
+ }
+ }
+ }
+
+ if (multiTransactionStatus.isNewSynchonization()) {
+ synchronizationManager.clearSynchronization();
+ }
+
+ if (commitException != null) {
+ boolean firstTransactionManagerFailed = commitExceptionTransactionManager == getLastTransactionManager();
+ int transactionState = firstTransactionManagerFailed ? HeuristicCompletionException.STATE_ROLLED_BACK
+ : HeuristicCompletionException.STATE_MIXED;
+ throw new HeuristicCompletionException(transactionState, commitException);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.PlatformTransactionManager#rollback(org.springframework.transaction.TransactionStatus)
+ */
+ public void rollback(TransactionStatus status) throws TransactionException {
+
+ Exception rollbackException = null;
+ PlatformTransactionManager rollbackExceptionTransactionManager = null;
+
+ MultiTransactionStatus multiTransactionStatus = (MultiTransactionStatus) status;
+
+ for (PlatformTransactionManager transactionManager : reverse(transactionManagers)) {
+ try {
+ multiTransactionStatus.rollback(transactionManager);
+ } catch (Exception ex) {
+ if (rollbackException == null) {
+ rollbackException = ex;
+ rollbackExceptionTransactionManager = transactionManager;
+ } else {
+ LOGGER.warn("Rollback exception (" + transactionManager + ") " + ex.getMessage(), ex);
+ }
+ }
+ }
+
+ if (multiTransactionStatus.isNewSynchonization()) {
+ synchronizationManager.clearSynchronization();
+ }
+
+ if (rollbackException != null) {
+ throw new UnexpectedRollbackException("Rollback exception, originated at (" + rollbackExceptionTransactionManager
+ + ") " + rollbackException.getMessage(), rollbackException);
+ }
+ }
+
+ private Iterable reverse(Collection collection) {
+
+ List list = new ArrayList(collection);
+ Collections.reverse(list);
+ return list;
+ }
+
+ private PlatformTransactionManager getLastTransactionManager() {
+ return transactionManagers.get(lastTransactionManagerIndex());
+ }
+
+ private int lastTransactionManagerIndex() {
+ return transactionManagers.size() - 1;
+ }
+}
diff --git a/src/main/java/org/springframework/data/transaction/MultiTransactionStatus.java b/src/main/java/org/springframework/data/transaction/MultiTransactionStatus.java
new file mode 100644
index 000000000..3cb603ce0
--- /dev/null
+++ b/src/main/java/org/springframework/data/transaction/MultiTransactionStatus.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2011-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
+ *
+ * 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.data.transaction;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.util.Assert;
+
+/**
+ * {@link TransactionStatus} implementation to orchestrate {@link TransactionStatus} instances for multiple
+ * {@link PlatformTransactionManager} instances.
+ *
+ * @author Michael Hunger
+ * @author Oliver Gierke
+ * @since 1.6
+ */
+class MultiTransactionStatus implements TransactionStatus {
+
+ private final PlatformTransactionManager mainTransactionManager;
+ private final Map transactionStatuses = Collections
+ .synchronizedMap(new HashMap());
+
+ private boolean newSynchonization;
+
+ /**
+ * Creates a new {@link MultiTransactionStatus} for the given {@link PlatformTransactionManager}.
+ *
+ * @param mainTransactionManager must not be {@literal null}.
+ */
+ public MultiTransactionStatus(PlatformTransactionManager mainTransactionManager) {
+
+ Assert.notNull(mainTransactionManager, "TransactionManager must not be null!");
+ this.mainTransactionManager = mainTransactionManager;
+ }
+
+ public Map getTransactionStatuses() {
+ return transactionStatuses;
+ }
+
+ public void setNewSynchonization() {
+ this.newSynchonization = true;
+ }
+
+ public boolean isNewSynchonization() {
+ return newSynchonization;
+ }
+
+ public void registerTransactionManager(TransactionDefinition definition, PlatformTransactionManager transactionManager) {
+ getTransactionStatuses().put(transactionManager, transactionManager.getTransaction(definition));
+ }
+
+ public void commit(PlatformTransactionManager transactionManager) {
+ TransactionStatus transactionStatus = getTransactionStatus(transactionManager);
+ transactionManager.commit(transactionStatus);
+ }
+
+ /**
+ * Rolls back the {@link TransactionStatus} registered for the given {@link PlatformTransactionManager}.
+ *
+ * @param transactionManager must not be {@literal null}.
+ */
+ public void rollback(PlatformTransactionManager transactionManager) {
+ transactionManager.rollback(getTransactionStatus(transactionManager));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
+ */
+ public boolean isRollbackOnly() {
+ return getMainTransactionStatus().isRollbackOnly();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.TransactionStatus#isCompleted()
+ */
+ public boolean isCompleted() {
+ return getMainTransactionStatus().isCompleted();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.TransactionStatus#isNewTransaction()
+ */
+ public boolean isNewTransaction() {
+ return getMainTransactionStatus().isNewTransaction();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.TransactionStatus#hasSavepoint()
+ */
+ public boolean hasSavepoint() {
+ return getMainTransactionStatus().hasSavepoint();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.TransactionStatus#setRollbackOnly()
+ */
+ public void setRollbackOnly() {
+ for (TransactionStatus ts : transactionStatuses.values()) {
+ ts.setRollbackOnly();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.SavepointManager#createSavepoint()
+ */
+ public Object createSavepoint() throws TransactionException {
+
+ SavePoints savePoints = new SavePoints();
+
+ for (TransactionStatus transactionStatus : transactionStatuses.values()) {
+ savePoints.save(transactionStatus);
+ }
+ return savePoints;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.SavepointManager#rollbackToSavepoint(java.lang.Object)
+ */
+ public void rollbackToSavepoint(Object savepoint) throws TransactionException {
+ SavePoints savePoints = (SavePoints) savepoint;
+ savePoints.rollback();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.SavepointManager#releaseSavepoint(java.lang.Object)
+ */
+ public void releaseSavepoint(Object savepoint) throws TransactionException {
+ ((SavePoints) savepoint).release();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.TransactionStatus#flush()
+ */
+ public void flush() {
+ for (TransactionStatus transactionStatus : transactionStatuses.values()) {
+ transactionStatus.flush();
+ }
+ }
+
+ private TransactionStatus getMainTransactionStatus() {
+ return transactionStatuses.get(mainTransactionManager);
+ }
+
+ private TransactionStatus getTransactionStatus(PlatformTransactionManager transactionManager) {
+ return this.getTransactionStatuses().get(transactionManager);
+ }
+
+ private static class SavePoints {
+
+ private final Map savepoints = new HashMap();
+
+ private void addSavePoint(TransactionStatus status, Object savepoint) {
+
+ Assert.notNull(status, "TransactionStatus must not be null!");
+ this.savepoints.put(status, savepoint);
+ }
+
+ private void save(TransactionStatus transactionStatus) {
+ Object savepoint = transactionStatus.createSavepoint();
+ addSavePoint(transactionStatus, savepoint);
+ }
+
+ public void rollback() {
+ for (TransactionStatus transactionStatus : savepoints.keySet()) {
+ transactionStatus.rollbackToSavepoint(savepointFor(transactionStatus));
+ }
+ }
+
+ private Object savepointFor(TransactionStatus transactionStatus) {
+ return savepoints.get(transactionStatus);
+ }
+
+ public void release() {
+ for (TransactionStatus transactionStatus : savepoints.keySet()) {
+ transactionStatus.releaseSavepoint(savepointFor(transactionStatus));
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/transaction/SpringTransactionSynchronizationManager.java b/src/main/java/org/springframework/data/transaction/SpringTransactionSynchronizationManager.java
new file mode 100644
index 000000000..96f6236e7
--- /dev/null
+++ b/src/main/java/org/springframework/data/transaction/SpringTransactionSynchronizationManager.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011-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
+ *
+ * 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.data.transaction;
+
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+/**
+ * {@link SynchronizationManager} delegating calls to Spring's {@link TransactionSynchronizationManager}.
+ *
+ * @author Michael Hunger
+ * @author Oliver Gierke
+ * @since 1.6
+ */
+enum SpringTransactionSynchronizationManager implements SynchronizationManager {
+
+ INSTANCE;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.transaction.SynchronizationManager#initSynchronization()
+ */
+ public void initSynchronization() {
+ TransactionSynchronizationManager.initSynchronization();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.transaction.SynchronizationManager#isSynchronizationActive()
+ */
+ public boolean isSynchronizationActive() {
+ return TransactionSynchronizationManager.isSynchronizationActive();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.transaction.SynchronizationManager#clearSynchronization()
+ */
+ public void clearSynchronization() {
+ TransactionSynchronizationManager.clear();
+ }
+}
diff --git a/src/main/java/org/springframework/data/transaction/SynchronizationManager.java b/src/main/java/org/springframework/data/transaction/SynchronizationManager.java
new file mode 100644
index 000000000..227b616c6
--- /dev/null
+++ b/src/main/java/org/springframework/data/transaction/SynchronizationManager.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011-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
+ *
+ * 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.data.transaction;
+
+/**
+ * Strategy interface to allow providing a dedicated synchronization mechanism.
+ *
+ * @author Michael Hunger
+ * @author Oliver Gierke
+ * @see SpringTransactionSynchronizationManager
+ * @since 1.6
+ */
+interface SynchronizationManager {
+
+ void initSynchronization();
+
+ boolean isSynchronizationActive();
+
+ void clearSynchronization();
+}
diff --git a/src/main/java/org/springframework/data/transaction/package-info.java b/src/main/java/org/springframework/data/transaction/package-info.java
new file mode 100644
index 000000000..be8e155a3
--- /dev/null
+++ b/src/main/java/org/springframework/data/transaction/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Contains advanced support for transactions, e.g. a best-effort delegating transaction manager.
+ *
+ * @see org.springframework.data.transaction.ChainedTransactionManager
+ */
+package org.springframework.data.transaction;
\ No newline at end of file
diff --git a/src/test/java/org/springframework/data/transaction/ChainedTransactionManagerTests.java b/src/test/java/org/springframework/data/transaction/ChainedTransactionManagerTests.java
new file mode 100644
index 000000000..254bf1a13
--- /dev/null
+++ b/src/test/java/org/springframework/data/transaction/ChainedTransactionManagerTests.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2011-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
+ *
+ * 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.data.transaction;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import static org.springframework.data.transaction.ChainedTransactionManagerTests.TestPlatformTransactionManager.*;
+import static org.springframework.data.transaction.ChainedTransactionManagerTests.TransactionManagerMatcher.*;
+import static org.springframework.transaction.HeuristicCompletionException.*;
+
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.junit.Test;
+import org.springframework.transaction.HeuristicCompletionException;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.UnexpectedRollbackException;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+
+/**
+ * Integration tests for {@link ChainedTransactionManager}.
+ *
+ * @author Michael Hunger
+ * @author Oliver Gierke
+ * @since 1.6
+ */
+public class ChainedTransactionManagerTests {
+
+ ChainedTransactionManager tm;
+
+ @Test
+ public void shouldCompleteSuccessfully() throws Exception {
+
+ PlatformTransactionManager transactionManager = createNonFailingTransactionManager("single");
+ setupTransactionManagers(transactionManager);
+
+ createAndCommitTransaction();
+
+ assertThat(transactionManager, isCommitted());
+ }
+
+ @Test
+ public void shouldThrowRolledBackExceptionForSingleTMFailure() throws Exception {
+
+ setupTransactionManagers(createFailingTransactionManager("single"));
+
+ try {
+ createAndCommitTransaction();
+ fail("Didn't throw the expected exception");
+ } catch (HeuristicCompletionException e) {
+ assertEquals(HeuristicCompletionException.STATE_ROLLED_BACK, e.getOutcomeState());
+ }
+ }
+
+ @Test
+ public void shouldCommitAllRegisteredTransactionManagers() {
+
+ PlatformTransactionManager first = createNonFailingTransactionManager("first");
+ PlatformTransactionManager second = createNonFailingTransactionManager("second");
+
+ setupTransactionManagers(first, second);
+ createAndCommitTransaction();
+
+ assertThat(first, isCommitted());
+ assertThat(second, isCommitted());
+ }
+
+ @Test
+ public void shouldCommitInReverseOrder() {
+
+ PlatformTransactionManager first = createNonFailingTransactionManager("first");
+ PlatformTransactionManager second = createNonFailingTransactionManager("second");
+
+ setupTransactionManagers(first, second);
+ createAndCommitTransaction();
+
+ assertThat("second tm commited before first ", commitTime(first) >= commitTime(second), is(true));
+ }
+
+ @Test
+ public void shouldThrowMixedRolledBackExceptionForNonFirstTMFailure() throws Exception {
+
+ setupTransactionManagers(TestPlatformTransactionManager.createFailingTransactionManager("first"),
+ createNonFailingTransactionManager("second"));
+
+ try {
+ createAndCommitTransaction();
+ fail("Didn't throw the expected exception");
+ } catch (HeuristicCompletionException e) {
+ assertHeuristicException(HeuristicCompletionException.STATE_MIXED, e.getOutcomeState());
+ }
+ }
+
+ @Test
+ public void shouldRollbackAllTransactionManagers() throws Exception {
+
+ PlatformTransactionManager first = createNonFailingTransactionManager("first");
+ PlatformTransactionManager second = createNonFailingTransactionManager("second");
+
+ setupTransactionManagers(first, second);
+ createAndRollbackTransaction();
+
+ assertThat(first, wasRolledback());
+ assertThat(second, wasRolledback());
+
+ }
+
+ @Test(expected = UnexpectedRollbackException.class)
+ public void shouldThrowExceptionOnFailingRollback() throws Exception {
+ PlatformTransactionManager first = createFailingTransactionManager("first");
+ setupTransactionManagers(first);
+ createAndRollbackTransaction();
+ }
+
+ private void setupTransactionManagers(PlatformTransactionManager... transactionManagers) {
+ tm = new ChainedTransactionManager(new TestSynchronizationManager(), transactionManagers);
+ }
+
+ private void createAndRollbackTransaction() {
+ MultiTransactionStatus transaction = tm.getTransaction(new DefaultTransactionDefinition());
+ tm.rollback(transaction);
+ }
+
+ private void createAndCommitTransaction() {
+ MultiTransactionStatus transaction = tm.getTransaction(new DefaultTransactionDefinition());
+ tm.commit(transaction);
+ }
+
+ private static void assertHeuristicException(int expected, int actual) {
+ assertThat(getStateString(actual), is(getStateString(expected)));
+ }
+
+ private static Long commitTime(PlatformTransactionManager transactionManager) {
+ return ((TestPlatformTransactionManager) transactionManager).getCommitTime();
+ }
+
+ static class TestSynchronizationManager implements SynchronizationManager {
+
+ private boolean synchronizationActive;
+
+ public void initSynchronization() {
+ synchronizationActive = true;
+ }
+
+ public boolean isSynchronizationActive() {
+ return synchronizationActive;
+ }
+
+ public void clearSynchronization() {
+ synchronizationActive = false;
+ }
+ }
+
+ static class TestPlatformTransactionManager implements org.springframework.transaction.PlatformTransactionManager {
+
+ private final String name;
+ private Long commitTime;
+ private Long rollbackTime;
+
+ public TestPlatformTransactionManager(String name) {
+ this.name = name;
+ }
+
+ @Factory
+ static PlatformTransactionManager createFailingTransactionManager(String name) {
+ return new TestPlatformTransactionManager(name + "-failing") {
+ @Override
+ public void commit(TransactionStatus status) throws TransactionException {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public void rollback(TransactionStatus status) throws TransactionException {
+ throw new RuntimeException();
+ }
+ };
+ }
+
+ @Factory
+ static PlatformTransactionManager createNonFailingTransactionManager(String name) {
+ return new TestPlatformTransactionManager(name + "-non-failing");
+ }
+
+ @Override
+ public String toString() {
+ return name + (isCommitted() ? " (committed) " : " (not committed)");
+ }
+
+ public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
+ return new TestTransactionStatus(definition);
+ }
+
+ public void commit(TransactionStatus status) throws TransactionException {
+ commitTime = System.currentTimeMillis();
+ }
+
+ public void rollback(TransactionStatus status) throws TransactionException {
+ rollbackTime = System.currentTimeMillis();
+ }
+
+ public boolean isCommitted() {
+ return commitTime != null;
+ }
+
+ public boolean wasRolledBack() {
+ return rollbackTime != null;
+ }
+
+ public Long getCommitTime() {
+ return commitTime;
+ }
+
+ static class TestTransactionStatus implements org.springframework.transaction.TransactionStatus {
+
+ public TestTransactionStatus(TransactionDefinition definition) {
+ }
+
+ public boolean isNewTransaction() {
+ return false;
+ }
+
+ public boolean hasSavepoint() {
+ return false;
+ }
+
+ public void setRollbackOnly() {
+
+ }
+
+ public boolean isRollbackOnly() {
+ return false;
+ }
+
+ public void flush() {
+
+ }
+
+ public boolean isCompleted() {
+ return false;
+ }
+
+ public Object createSavepoint() throws TransactionException {
+ return null;
+ }
+
+ public void rollbackToSavepoint(Object savepoint) throws TransactionException {
+
+ }
+
+ public void releaseSavepoint(Object savepoint) throws TransactionException {
+
+ }
+ }
+ }
+
+ static class TransactionManagerMatcher extends
+ org.hamcrest.TypeSafeMatcher {
+
+ private final boolean commitCheck;
+
+ public TransactionManagerMatcher(boolean commitCheck) {
+ this.commitCheck = commitCheck;
+ }
+
+ @Override
+ public boolean matchesSafely(PlatformTransactionManager platformTransactionManager) {
+ TestPlatformTransactionManager ptm = (TestPlatformTransactionManager) platformTransactionManager;
+ if (commitCheck) {
+ return ptm.isCommitted();
+ } else {
+ return ptm.wasRolledBack();
+ }
+
+ }
+
+ public void describeTo(Description description) {
+ description.appendText("that a " + (commitCheck ? "committed" : "rolled-back") + " TransactionManager");
+ }
+
+ @Factory
+ public static TransactionManagerMatcher isCommitted() {
+ return new TransactionManagerMatcher(true);
+ }
+
+ @Factory
+ public static TransactionManagerMatcher wasRolledback() {
+ return new TransactionManagerMatcher(false);
+ }
+ }
+}