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); + } + } +}