/*
* ModeShape (http://www.modeshape.org)
*
* 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.modeshape.jcr.txn;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.Transaction;
import org.junit.Test;
/**
* Unit test for {@link LocalTransactionManagerTest}
*
* @author Horia Chiorean (hchiorea@redhat.com)
*/
public class LocalTransactionManagerTest {
private final LocalTransactionManager txManager = new LocalTransactionManager();
private final TestSynchronization sync = new TestSynchronization();
@Test
public void shouldCreateNewTransactions() throws Exception {
assertEquals(Status.STATUS_NO_TRANSACTION, txManager.getStatus());
txManager.begin();
Transaction tx = txManager.getTransaction();
assertNotNull(tx);
assertEquals(Status.STATUS_ACTIVE, tx.getStatus());
tx.commit();
}
@Test(expected = NotSupportedException.class)
public void shouldNotSupportNestedTransactions() throws Exception {
try {
txManager.begin();
txManager.begin();
} finally {
LocalTransactionManager.clear();
}
}
@Test
public void shouldCommitTransaction() throws Exception {
txManager.begin();
Transaction tx = txManager.getTransaction();
assertEquals(Status.STATUS_ACTIVE, tx.getStatus());
assertEquals(Status.STATUS_ACTIVE, txManager.getStatus());
tx.registerSynchronization(sync);
txManager.commit();
sync.assertBeforeCompletion(true);
sync.assertAfterCompletion(true);
sync.assertAfterCompletionStatus(Status.STATUS_COMMITTED);
assertNull(txManager.getTransaction());
}
@Test
public void shouldRollbackTransaction() throws Exception {
txManager.begin();
Transaction tx = txManager.getTransaction();
tx.registerSynchronization(sync);
txManager.rollback();
sync.assertBeforeCompletion(true);
sync.assertAfterCompletion(true);
sync.assertAfterCompletionStatus(Status.STATUS_ROLLEDBACK);
assertNull(txManager.getTransaction());
}
@Test
public void shouldSupportRollbackOnlyTransactionsWithCommit() throws Exception {
txManager.begin();
Transaction tx = txManager.getTransaction();
tx.registerSynchronization(sync);
txManager.setRollbackOnly();
try {
tx.commit();
fail("Expected Rollback Exception");
} catch (RollbackException e) {
//expected
}
sync.assertBeforeCompletion(true);
sync.assertAfterCompletion(true);
sync.assertAfterCompletionStatus(Status.STATUS_ROLLEDBACK);
assertNull(txManager.getTransaction());
}
@Test
public void shouldSupportRollbackOnlyTransactionsWithRollback() throws Exception {
txManager.begin();
Transaction tx = txManager.getTransaction();
tx.registerSynchronization(sync);
txManager.setRollbackOnly();
tx.rollback();
sync.assertBeforeCompletion(true);
sync.assertAfterCompletion(true);
sync.assertAfterCompletionStatus(Status.STATUS_ROLLEDBACK);
assertNull(txManager.getTransaction());
}
@Test
public void shouldSuspendAndResumeTransaction() throws Exception {
txManager.begin();
Transaction tx = txManager.suspend();
assertNotNull(tx);
assertEquals(Status.STATUS_NO_TRANSACTION, txManager.getStatus());
assertNull(txManager.getTransaction());
txManager.resume(tx);
assertEquals(Status.STATUS_ACTIVE, txManager.getStatus());
tx.commit();
assertNull(txManager.getTransaction());
}
@Test
public void shouldConfineTransactionToThread() throws Exception {
txManager.begin();
Transaction tx = txManager.getTransaction();
tx.registerSynchronization(sync);
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
Callable<Void> task = () -> {
try {
tx.commit();
fail("Should not allow committing off another thread");
} catch (IllegalStateException e) {
//expected
}
txManager.begin();
TestSynchronization sync = new TestSynchronization();
txManager.getTransaction().registerSynchronization(sync);
txManager.commit();
sync.assertBeforeCompletion(true);
sync.assertAfterCompletion(true);
sync.assertAfterCompletionStatus(Status.STATUS_COMMITTED);
return null;
};
executorService.submit(task).get();
txManager.commit();
sync.assertBeforeCompletion(true);
sync.assertAfterCompletion(true);
sync.assertAfterCompletionStatus(Status.STATUS_COMMITTED);
assertNull(txManager.getTransaction());
} finally {
executorService.shutdown();
}
}
protected static class TestSynchronization implements Synchronization {
private AtomicBoolean beforeCompletion = new AtomicBoolean();
private AtomicBoolean afterCompletion = new AtomicBoolean();
private int afterCompletionStatus = -1;
@Override
public void beforeCompletion() {
beforeCompletion.compareAndSet(false, true);
}
@Override
public void afterCompletion(int status) {
afterCompletion.compareAndSet(false, true);
afterCompletionStatus = status;
}
protected void assertBeforeCompletion(boolean called) {
assertThat("beforeCompletion invocation failed", called, is(beforeCompletion.get()));
}
protected void assertAfterCompletion(boolean called) {
assertThat("afterCompletion invocation failed", called, is(afterCompletion.get()));
}
protected void assertAfterCompletionStatus(int status) {
assertThat("afterCompletion status is invalid", status, is(this.afterCompletionStatus));
}
}
}