/*
* 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.assertj.core.api.Assertions.*;
import static org.springframework.data.transaction.ChainedTransactionManagerTests.TestPlatformTransactionManager.*;
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 {
TestPlatformTransactionManager transactionManager = createNonFailingTransactionManager("single");
setupTransactionManagers(transactionManager);
createAndCommitTransaction();
assertThat(transactionManager).matches(TestPlatformTransactionManager::isCommitted);
}
@Test
public void shouldThrowRolledBackExceptionForSingleTMFailure() throws Exception {
setupTransactionManagers(createFailingTransactionManager("single"));
assertThatExceptionOfType(HeuristicCompletionException.class)//
.isThrownBy(this::createAndCommitTransaction)//
.matches(e -> e.getOutcomeState() == HeuristicCompletionException.STATE_ROLLED_BACK);
}
@Test
public void shouldCommitAllRegisteredTransactionManagers() {
TestPlatformTransactionManager first = createNonFailingTransactionManager("first");
TestPlatformTransactionManager second = createNonFailingTransactionManager("second");
setupTransactionManagers(first, second);
createAndCommitTransaction();
assertThat(first).matches(TestPlatformTransactionManager::isCommitted);
assertThat(second).matches(TestPlatformTransactionManager::isCommitted);
}
@Test
public void shouldCommitInReverseOrder() {
TestPlatformTransactionManager first = createNonFailingTransactionManager("first");
TestPlatformTransactionManager second = createNonFailingTransactionManager("second");
setupTransactionManagers(first, second);
createAndCommitTransaction();
assertThat(second.getCommitTime()).isLessThanOrEqualTo(first.getCommitTime());
}
@Test
public void shouldThrowMixedRolledBackExceptionForNonFirstTMFailure() throws Exception {
setupTransactionManagers(TestPlatformTransactionManager.createFailingTransactionManager("first"),
createNonFailingTransactionManager("second"));
assertThatExceptionOfType(HeuristicCompletionException.class)//
.isThrownBy(this::createAndCommitTransaction)//
.matches(e -> e.getOutcomeState() == HeuristicCompletionException.STATE_MIXED);
}
@Test
public void shouldRollbackAllTransactionManagers() throws Exception {
TestPlatformTransactionManager first = createNonFailingTransactionManager("first");
TestPlatformTransactionManager second = createNonFailingTransactionManager("second");
setupTransactionManagers(first, second);
createAndRollbackTransaction();
assertThat(first).matches(TestPlatformTransactionManager::wasRolledBack);
assertThat(second).matches(TestPlatformTransactionManager::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);
}
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 TestPlatformTransactionManager 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 TestPlatformTransactionManager 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 {
}
}
}
}