diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/QuarkusTransaction.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/QuarkusTransaction.java index 0301cb6cd8049..9329ba9e68e4e 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/QuarkusTransaction.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/QuarkusTransaction.java @@ -9,8 +9,6 @@ import com.arjuna.ats.jta.UserTransaction; -import io.quarkus.arc.Arc; - /** * A simplified transaction interface. While broadly covering the same use cases as {@link jakarta.transaction.UserTransaction}, * this class is designed to be easier to use. The main features it offers over {@code UserTransaction} are: @@ -52,8 +50,7 @@ static void begin() { * @param options Options that apply to the new transaction */ static void begin(BeginOptions options) { - RequestScopedTransaction tx = Arc.container().instance(RequestScopedTransaction.class).get(); - tx.begin(options); + QuarkusTransactionImpl.begin(options); } /** diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/QuarkusTransactionImpl.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/QuarkusTransactionImpl.java index ebc8e5718dd49..0384ead6f5ccf 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/QuarkusTransactionImpl.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/QuarkusTransactionImpl.java @@ -218,6 +218,11 @@ private static void begin(RunOptionsBase options) { } } + static void begin(BeginOptions options) { + RequestScopedTransaction tx = Arc.container().instance(RequestScopedTransaction.class).get(); + tx.begin(options); + } + static void rollback() { try { getUserTransaction().rollback(); @@ -244,15 +249,14 @@ static void setRollbackOnly() { private static jakarta.transaction.UserTransaction getUserTransaction() { if (cachedUserTransaction == null) { - return cachedUserTransaction = com.arjuna.ats.jta.UserTransaction.userTransaction(); + return cachedUserTransaction = Arc.container().instance(UserTransaction.class).get(); } return cachedUserTransaction; } private static TransactionManager getTransactionManager() { if (cachedTransactionManager == null) { - return cachedTransactionManager = com.arjuna.ats.jta.TransactionManager - .transactionManager(); + return cachedTransactionManager = Arc.container().instance(TransactionManager.class).get(); } return cachedTransactionManager; } diff --git a/integration-tests/narayana-jta/pom.xml b/integration-tests/narayana-jta/pom.xml index 87beb6b3687e5..d993384757fb8 100644 --- a/integration-tests/narayana-jta/pom.xml +++ b/integration-tests/narayana-jta/pom.xml @@ -30,6 +30,11 @@ rest-assured test + + org.assertj + assertj-core + test + diff --git a/integration-tests/narayana-jta/src/main/java/io/quarkus/narayana/jta/TransactionBeanWithEvents.java b/integration-tests/narayana-jta/src/main/java/io/quarkus/narayana/jta/TransactionBeanWithEvents.java index f59bd0134e552..fa350636f2260 100644 --- a/integration-tests/narayana-jta/src/main/java/io/quarkus/narayana/jta/TransactionBeanWithEvents.java +++ b/integration-tests/narayana-jta/src/main/java/io/quarkus/narayana/jta/TransactionBeanWithEvents.java @@ -65,6 +65,18 @@ static int getRolledBack() { void doInTransaction(boolean isCommit) { log.debug("Running transactional bean method"); + try { + listenToCommitRollback(); + } catch (Exception e) { + throw new IllegalStateException("Cannot get transaction to register synchronization on bean call", e); + } + + if (!isCommit) { + throw new RuntimeException("Rollback here!"); + } + } + + void listenToCommitRollback() { try { tm.getTransaction().registerSynchronization(new Synchronization() { @Override @@ -85,10 +97,6 @@ public void afterCompletion(int status) { } catch (Exception e) { throw new IllegalStateException("Cannot get transaction to register synchronization on bean call", e); } - - if (!isCommit) { - throw new RuntimeException("Rollback here!"); - } } void transactionScopeActivated(@Observes @Initialized(TransactionScoped.class) final Object event, diff --git a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeQuarkusTransactionBeginCommitTest.java b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeQuarkusTransactionBeginCommitTest.java new file mode 100644 index 0000000000000..64bfba825274e --- /dev/null +++ b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeQuarkusTransactionBeginCommitTest.java @@ -0,0 +1,85 @@ +package io.quarkus.narayana.jta; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import jakarta.enterprise.context.ContextNotActiveException; +import jakarta.inject.Inject; +import jakarta.transaction.Transaction; +import jakarta.transaction.TransactionManager; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class TransactionScopeQuarkusTransactionBeginCommitTest { + + @Inject + TransactionManager tm; + + @Inject + TransactionScopedBean beanTransactional; + + @Inject + TransactionBeanWithEvents beanEvents; + + @Test + void transactionScopedInTransaction() throws Exception { + TransactionScopedBean.resetCounters(); + + QuarkusTransaction.begin(); + beanTransactional.setValue(42); + assertEquals(1, TransactionScopedBean.getInitializedCount(), "Expected @PostConstruct to be invoked"); + assertEquals(42, beanTransactional.getValue(), "Transaction scope did not save the value"); + Transaction suspendedTransaction = tm.suspend(); + + assertThrows(ContextNotActiveException.class, () -> { + beanTransactional.getValue(); + }, "Not expecting to have available TransactionScoped bean outside of the transaction"); + + QuarkusTransaction.begin(); + beanTransactional.setValue(1); + assertEquals(2, TransactionScopedBean.getInitializedCount(), "Expected @PostConstruct to be invoked"); + assertEquals(1, beanTransactional.getValue(), "Transaction scope did not save the value"); + QuarkusTransaction.commit(); + assertEquals(1, TransactionScopedBean.getPreDestroyCount(), "Expected @PreDestroy to be invoked"); + + assertThrows(ContextNotActiveException.class, () -> { + beanTransactional.getValue(); + }, "Not expecting to have available TransactionScoped bean outside of the transaction"); + + tm.resume(suspendedTransaction); + assertEquals(42, beanTransactional.getValue(), "Transaction scope did not resumed correctly"); + QuarkusTransaction.rollback(); + assertEquals(2, TransactionScopedBean.getPreDestroyCount(), "Expected @PreDestroy to be invoked"); + } + + @Test + void scopeEventsAreEmitted() { + TransactionBeanWithEvents.cleanCounts(); + + QuarkusTransaction.begin(); + beanEvents.listenToCommitRollback(); + QuarkusTransaction.commit(); + + assertEquals(1, TransactionBeanWithEvents.getInitialized(), "Expected @Initialized to be observed"); + assertEquals(1, TransactionBeanWithEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getDestroyed(), "Expected @Destroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getCommited(), "Expected commit to be called once"); + assertEquals(0, TransactionBeanWithEvents.getRolledBack(), "Expected no rollback"); + TransactionBeanWithEvents.cleanCounts(); + + QuarkusTransaction.begin(); + beanEvents.listenToCommitRollback(); + QuarkusTransaction.rollback(); + + assertEquals(1, TransactionBeanWithEvents.getInitialized(), "Expected @Initialized to be observed"); + assertEquals(1, TransactionBeanWithEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getDestroyed(), "Expected @Destroyed to be observed"); + assertEquals(0, TransactionBeanWithEvents.getCommited(), "Expected no commit"); + assertEquals(1, TransactionBeanWithEvents.getRolledBack(), "Expected rollback to be called once"); + TransactionBeanWithEvents.cleanCounts(); + } + +} diff --git a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeQuarkusTransactionRunnerTest.java b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeQuarkusTransactionRunnerTest.java new file mode 100644 index 0000000000000..fc7fbddf7d0f3 --- /dev/null +++ b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeQuarkusTransactionRunnerTest.java @@ -0,0 +1,84 @@ +package io.quarkus.narayana.jta; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import jakarta.enterprise.context.ContextNotActiveException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class TransactionScopeQuarkusTransactionRunnerTest { + + @Inject + TransactionScopedBean beanTransactional; + + @Inject + TransactionBeanWithEvents beanEvents; + + @Test + void transactionScopedInTransaction() { + TransactionScopedBean.resetCounters(); + + QuarkusTransaction.requiringNew().run(() -> { + beanTransactional.setValue(42); + assertEquals(1, TransactionScopedBean.getInitializedCount(), "Expected @PostConstruct to be invoked"); + assertEquals(42, beanTransactional.getValue(), "Transaction scope did not save the value"); + + QuarkusTransaction.suspendingExisting().run(() -> { + assertThrows(ContextNotActiveException.class, () -> { + beanTransactional.getValue(); + }, "Not expecting to have available TransactionScoped bean outside of the transaction"); + + QuarkusTransaction.requiringNew().run(() -> { + beanTransactional.setValue(1); + assertEquals(2, TransactionScopedBean.getInitializedCount(), "Expected @PostConstruct to be invoked"); + assertEquals(1, beanTransactional.getValue(), "Transaction scope did not save the value"); + }); + assertEquals(1, TransactionScopedBean.getPreDestroyCount(), "Expected @PreDestroy to be invoked"); + + assertThrows(ContextNotActiveException.class, () -> { + beanTransactional.getValue(); + }, "Not expecting to have available TransactionScoped bean outside of the transaction"); + }); + + assertEquals(42, beanTransactional.getValue(), "Transaction scope did not resumed correctly"); + }); + assertEquals(2, TransactionScopedBean.getPreDestroyCount(), "Expected @PreDestroy to be invoked"); + } + + @Test + void scopeEventsAreEmitted() { + TransactionBeanWithEvents.cleanCounts(); + + QuarkusTransaction.requiringNew().run(() -> { + beanEvents.listenToCommitRollback(); + }); + + assertEquals(1, TransactionBeanWithEvents.getInitialized(), "Expected @Initialized to be observed"); + assertEquals(1, TransactionBeanWithEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getDestroyed(), "Expected @Destroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getCommited(), "Expected commit to be called once"); + assertEquals(0, TransactionBeanWithEvents.getRolledBack(), "Expected no rollback"); + TransactionBeanWithEvents.cleanCounts(); + + assertThatThrownBy(() -> QuarkusTransaction.requiringNew().run(() -> { + beanEvents.listenToCommitRollback(); + throw new RuntimeException(); + })) + // expect runtime exception to rollback the call + .isInstanceOf(RuntimeException.class); + + assertEquals(1, TransactionBeanWithEvents.getInitialized(), "Expected @Initialized to be observed"); + assertEquals(1, TransactionBeanWithEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getDestroyed(), "Expected @Destroyed to be observed"); + assertEquals(0, TransactionBeanWithEvents.getCommited(), "Expected no commit"); + assertEquals(1, TransactionBeanWithEvents.getRolledBack(), "Expected rollback to be called once"); + TransactionBeanWithEvents.cleanCounts(); + } + +} diff --git a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeTransactionalTest.java b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeTransactionalTest.java new file mode 100644 index 0000000000000..c853bf2ab1c6e --- /dev/null +++ b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeTransactionalTest.java @@ -0,0 +1,42 @@ +package io.quarkus.narayana.jta; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class TransactionScopeTransactionalTest { + @Inject + TransactionBeanWithEvents beanEvents; + + @Test + void scopeEventsAreEmitted() { + TransactionBeanWithEvents.cleanCounts(); + + beanEvents.doInTransaction(true); + + assertEquals(1, TransactionBeanWithEvents.getInitialized(), "Expected @Initialized to be observed"); + assertEquals(1, TransactionBeanWithEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getDestroyed(), "Expected @Destroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getCommited(), "Expected commit to be called once"); + assertEquals(0, TransactionBeanWithEvents.getRolledBack(), "Expected no rollback"); + TransactionBeanWithEvents.cleanCounts(); + + assertThatThrownBy(() -> beanEvents.doInTransaction(false)) + // expect runtime exception to rollback the call + .isInstanceOf(RuntimeException.class); + + assertEquals(1, TransactionBeanWithEvents.getInitialized(), "Expected @Initialized to be observed"); + assertEquals(1, TransactionBeanWithEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getDestroyed(), "Expected @Destroyed to be observed"); + assertEquals(0, TransactionBeanWithEvents.getCommited(), "Expected no commit"); + assertEquals(1, TransactionBeanWithEvents.getRolledBack(), "Expected rollback to be called once"); + TransactionBeanWithEvents.cleanCounts(); + } + +} diff --git a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopedTest.java b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeUserTransactionTest.java similarity index 70% rename from integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopedTest.java rename to integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeUserTransactionTest.java index f726e27c1d851..8a1ffa2555d5c 100644 --- a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopedTest.java +++ b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopeUserTransactionTest.java @@ -14,7 +14,7 @@ import io.quarkus.test.junit.QuarkusTest; @QuarkusTest -class TransactionScopedTest { +class TransactionScopeUserTransactionTest { @Inject UserTransaction tx; @@ -62,22 +62,27 @@ void transactionScopedInTransaction() throws Exception { void scopeEventsAreEmitted() throws Exception { TransactionBeanWithEvents.cleanCounts(); - beanEvents.doInTransaction(true); - - try { - beanEvents.doInTransaction(false); - } catch (RuntimeException expected) { - // expect runtime exception to rollback the call - } - tx.begin(); + beanEvents.listenToCommitRollback(); tx.commit(); - assertEquals(3, TransactionBeanWithEvents.getInitialized(), "Expected @Initialized to be observed"); - assertEquals(3, TransactionBeanWithEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observer"); - assertEquals(3, TransactionBeanWithEvents.getDestroyed(), "Expected @Destroyed to be observer"); + assertEquals(1, TransactionBeanWithEvents.getInitialized(), "Expected @Initialized to be observed"); + assertEquals(1, TransactionBeanWithEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getDestroyed(), "Expected @Destroyed to be observed"); assertEquals(1, TransactionBeanWithEvents.getCommited(), "Expected commit to be called once"); + assertEquals(0, TransactionBeanWithEvents.getRolledBack(), "Expected no rollback"); + TransactionBeanWithEvents.cleanCounts(); + + tx.begin(); + beanEvents.listenToCommitRollback(); + tx.rollback(); + + assertEquals(1, TransactionBeanWithEvents.getInitialized(), "Expected @Initialized to be observed"); + assertEquals(1, TransactionBeanWithEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observed"); + assertEquals(1, TransactionBeanWithEvents.getDestroyed(), "Expected @Destroyed to be observed"); + assertEquals(0, TransactionBeanWithEvents.getCommited(), "Expected no commit"); assertEquals(1, TransactionBeanWithEvents.getRolledBack(), "Expected rollback to be called once"); + TransactionBeanWithEvents.cleanCounts(); } }