[#3878] adding transaction scope into JTA extension

This commit is contained in:
Ondra Chaloupka
2019-10-18 14:33:09 +02:00
parent 72b87d5c0b
commit 5ebd1e102e
11 changed files with 763 additions and 36 deletions

View File

@@ -4,7 +4,7 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
import java.util.Properties;
import javax.inject.Inject;
import javax.transaction.TransactionScoped;
import com.arjuna.ats.internal.arjuna.coordinator.CheckedActionFactoryImple;
import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple;
@@ -14,6 +14,8 @@ import com.arjuna.ats.jta.common.JTAEnvironmentBean;
import com.arjuna.common.util.propertyservice.PropertiesFactory;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.ContextRegistrarBuildItem;
import io.quarkus.arc.processor.ContextRegistrar;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
@@ -22,9 +24,11 @@ import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.substrate.RuntimeInitializedClassBuildItem;
import io.quarkus.deployment.builditem.substrate.SubstrateSystemPropertyBuildItem;
import io.quarkus.narayana.jta.runtime.CDIDelegatingTransactionManager;
import io.quarkus.narayana.jta.runtime.NarayanaJtaProducers;
import io.quarkus.narayana.jta.runtime.NarayanaJtaRecorder;
import io.quarkus.narayana.jta.runtime.TransactionManagerConfiguration;
import io.quarkus.narayana.jta.runtime.context.TransactionContext;
import io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorMandatory;
import io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorNever;
import io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorNotSupported;
@@ -34,31 +38,27 @@ import io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorSuppo
class NarayanaJtaProcessor {
@Inject
BuildProducer<AdditionalBeanBuildItem> additionalBeans;
@Inject
BuildProducer<ReflectiveClassBuildItem> reflectiveClass;
@Inject
BuildProducer<RuntimeInitializedClassBuildItem> runtimeInit;
@BuildStep()
public SubstrateSystemPropertyBuildItem substrateSystemPropertyBuildItem() {
return new SubstrateSystemPropertyBuildItem("CoordinatorEnvironmentBean.transactionStatusManagerEnable",
String.valueOf(transactions.enableTransactionStatusManager));
}
/**
* The transactions configuration.
*/
TransactionManagerConfiguration transactions;
@BuildStep
public SubstrateSystemPropertyBuildItem substrateSystemPropertyBuildItem() {
return new SubstrateSystemPropertyBuildItem("CoordinatorEnvironmentBean.transactionStatusManagerEnable",
String.valueOf(transactions.enableTransactionStatusManager));
}
@BuildStep(providesCapabilities = Capabilities.TRANSACTIONS)
@Record(RUNTIME_INIT)
public void build(NarayanaJtaRecorder recorder, BuildProducer<FeatureBuildItem> feature) {
public void build(NarayanaJtaRecorder recorder,
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<RuntimeInitializedClassBuildItem> runtimeInit,
BuildProducer<FeatureBuildItem> feature) {
feature.produce(new FeatureBuildItem(FeatureBuildItem.NARAYANA_JTA));
additionalBeans.produce(new AdditionalBeanBuildItem(NarayanaJtaProducers.class));
additionalBeans.produce(new AdditionalBeanBuildItem(CDIDelegatingTransactionManager.class));
runtimeInit.produce(new RuntimeInitializedClassBuildItem(
"com.arjuna.ats.internal.jta.resources.arjunacore.CommitMarkableResourceRecord"));
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, JTAEnvironmentBean.class.getName(),
@@ -84,4 +84,17 @@ class NarayanaJtaProcessor {
recorder.setNodeName(transactions);
recorder.setDefaultTimeout(transactions);
}
@BuildStep
public void transactionContext(
BuildProducer<ContextRegistrarBuildItem> contextRegistry) {
contextRegistry.produce(new ContextRegistrarBuildItem(new ContextRegistrar() {
@Override
public void register(RegistrationContext registrationContext) {
registrationContext.configure(TransactionScoped.class).normal().contextClass(TransactionContext.class).done();
}
}));
}
}

View File

@@ -0,0 +1,186 @@
package io.quarkus.narayana.jta.runtime;
import java.io.Serializable;
import javax.enterprise.context.BeforeDestroyed;
import javax.enterprise.context.Destroyed;
import javax.enterprise.context.Initialized;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionScoped;
/**
* A delegating transaction manager which receives an instance of Narayana transaction manager
* and delegates all calls to it.
* On top of it the implementation adds the CDI events processing for {@link TransactionScoped}.
*/
@Singleton
public class CDIDelegatingTransactionManager implements TransactionManager, Serializable {
private static final long serialVersionUID = 1598L;
private final transient com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple delegate;
/**
* An {@link Event} that can {@linkplain Event#fire(Object) fire}
* {@link Transaction}s when the {@linkplain TransactionScoped transaction scope} is initialized.
*/
@Inject
@Initialized(TransactionScoped.class)
private Event<Transaction> transactionScopeInitialized;
/**
* An {@link Event} that can {@linkplain Event#fire(Object) fire}
* {@link Object}s before the {@linkplain TransactionScoped transaction scope} is destroyed.
*/
@Inject
@BeforeDestroyed(TransactionScoped.class)
private Event<Object> transactionScopeBeforeDestroyed;
/**
* An {@link Event} that can {@linkplain Event#fire(Object) fire}
* {@link Object}s when the {@linkplain TransactionScoped transaction scope} is destroyed.
*/
@Inject
@Destroyed(TransactionScoped.class)
private Event<Object> transactionScopeDestroyed;
/**
* Delegating transaction manager call to com.arjuna.ats.jta.{@link com.arjuna.ats.jta.TransactionManager}
*/
public CDIDelegatingTransactionManager() {
delegate = (com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple) com.arjuna.ats.jta.TransactionManager
.transactionManager();
}
/**
* Overrides {@link TransactionManager#begin()} to
* additionally {@linkplain Event#fire(Object) fire} an {@link Object}
* representing the {@linkplain Initialized initialization}
* of the {@linkplain TransactionScoped transaction scope}.
*
* @see TransactionManager#begin()
*/
@Override
public void begin() throws NotSupportedException, SystemException {
delegate.begin();
if (this.transactionScopeInitialized != null) {
this.transactionScopeInitialized.fire(this.getTransaction());
}
}
/**
* Overrides {@link TransactionManager#commit()} to
* additionally {@linkplain Event#fire(Object) fire} an {@link Object}
* representing the {@linkplain BeforeDestroyed before destruction} and
* the {@linkplain Destroyed destruction}
* of the {@linkplain TransactionScoped transaction scope}.
*
* @see TransactionManager#commit()
*/
@Override
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException,
IllegalStateException, SystemException {
if (this.transactionScopeBeforeDestroyed != null) {
this.transactionScopeBeforeDestroyed.fire(this.getTransaction());
}
try {
delegate.commit();
} finally {
if (this.transactionScopeDestroyed != null) {
this.transactionScopeDestroyed.fire(this.toString());
}
}
}
/**
* Overrides {@link TransactionManager#rollback()} to
* additionally {@linkplain Event#fire(Object) fire} an {@link Object}
* representing the {@linkplain BeforeDestroyed before destruction} and
* the {@linkplain Destroyed destruction}
* of the {@linkplain TransactionScoped transaction scope}.
*
* @see TransactionManager#rollback()
*/
@Override
public void rollback() throws IllegalStateException, SecurityException, SystemException {
if (this.transactionScopeBeforeDestroyed != null) {
this.transactionScopeBeforeDestroyed.fire(this.getTransaction());
}
try {
delegate.rollback();
} finally {
if (this.transactionScopeDestroyed != null) {
this.transactionScopeDestroyed.fire(this.toString());
}
}
}
/**
* {@inheritDoc}
*/
@Override
public int getStatus() throws SystemException {
return delegate.getStatus();
}
/**
* {@inheritDoc}
*/
@Override
public Transaction getTransaction() throws SystemException {
return delegate.getTransaction();
}
/**
* {@inheritDoc}
*/
@Override
public void resume(Transaction transaction) throws InvalidTransactionException, IllegalStateException, SystemException {
delegate.resume(transaction);
}
/**
* {@inheritDoc}
*/
@Override
public void setRollbackOnly() throws IllegalStateException, SystemException {
delegate.setRollbackOnly();
}
/**
* {@inheritDoc}
*/
@Override
public void setTransactionTimeout(int seconds) throws SystemException {
delegate.setTransactionTimeout(seconds);
}
/**
* Returns transaction timeout in seconds.
*
* @return transaction timeout set currently
* @throws SystemException on an undefined error
*/
public int getTransactionTimeout() throws SystemException {
return delegate.getTimeout();
}
/**
* {@inheritDoc}
*/
@Override
public Transaction suspend() throws SystemException {
return delegate.suspend();
}
}

View File

@@ -3,7 +3,6 @@ package io.quarkus.narayana.jta.runtime;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import javax.inject.Singleton;
import javax.transaction.TransactionSynchronizationRegistry;
import org.jboss.tm.JBossXATerminator;
@@ -11,18 +10,13 @@ import org.jboss.tm.XAResourceRecoveryRegistry;
import org.jboss.tm.usertx.UserTransactionRegistry;
import com.arjuna.ats.internal.jbossatx.jta.jca.XATerminator;
import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple;
import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple;
import com.arjuna.ats.jbossatx.jta.RecoveryManagerService;
import com.arjuna.ats.jta.TransactionManager;
import com.arjuna.ats.jta.UserTransaction;
@Dependent
public class NarayanaJtaProducers {
private static final javax.transaction.UserTransaction USER_TRANSACTION = UserTransaction.userTransaction();
private static final com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple TRANSACTION_MANAGER = (TransactionManagerImple) TransactionManager
.transactionManager();
@Produces
@ApplicationScoped
@@ -48,12 +42,6 @@ public class NarayanaJtaProducers {
return new TransactionSynchronizationRegistryImple();
}
@Produces
@Singleton
public javax.transaction.TransactionManager transactionManager() {
return TRANSACTION_MANAGER;
}
@Produces
@ApplicationScoped
public JBossXATerminator xaTerminator() {

View File

@@ -0,0 +1,216 @@
package io.quarkus.narayana.jta.runtime.context;
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionScoped;
import javax.transaction.TransactionSynchronizationRegistry;
import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple;
import io.quarkus.arc.ContextInstanceHandle;
import io.quarkus.arc.ContextInstanceHandleImpl;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.InjectableContext;
/**
* {@link javax.enterprise.context.spi.Context} class which defines the {@link TransactionScoped} context.
*/
public class TransactionContext implements InjectableContext {
// marker object to be put as a key for SynchronizationRegistry to gather all beans created in the scope
private static final Object TRANSACTION_CONTEXT_MARKER = new Object();
private final TransactionSynchronizationRegistry transactionSynchronizationRegistry = new TransactionSynchronizationRegistryImple();
private final TransactionManager transactionManager = com.arjuna.ats.jta.TransactionManager.transactionManager();
@Override
public void destroy() {
if (!isActive()) {
return;
}
TransactionContextState contextState = (TransactionContextState) transactionSynchronizationRegistry
.getResource(TRANSACTION_CONTEXT_MARKER);
if (contextState == null) {
return;
}
contextState.destroy();
}
@Override
public void destroy(Contextual<?> contextual) {
if (!isActive()) {
return;
}
TransactionContextState contextState = (TransactionContextState) transactionSynchronizationRegistry
.getResource(TRANSACTION_CONTEXT_MARKER);
if (contextState == null) {
return;
}
contextState.remove(contextual);
}
@Override
public ContextState getState() {
if (!isActive()) {
throw new ContextNotActiveException("No active transaction on the current thread");
}
ContextState result;
TransactionContextState contextState = (TransactionContextState) transactionSynchronizationRegistry
.getResource(TRANSACTION_CONTEXT_MARKER);
if (contextState == null) {
result = new TransactionContextState<>();
} else {
result = contextState;
}
return result;
}
@Override
public Class<? extends Annotation> getScope() {
return TransactionScoped.class;
}
@Override
@SuppressWarnings("unchecked")
public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext) {
if (!isActive()) {
throw new ContextNotActiveException();
}
if (contextual == null) {
throw new IllegalArgumentException("Contextual parameter must not be null");
}
TransactionContextState<T> contextState;
contextState = (TransactionContextState<T>) transactionSynchronizationRegistry
.getResource(TRANSACTION_CONTEXT_MARKER);
if (contextState == null) {
contextState = new TransactionContextState<>();
transactionSynchronizationRegistry.putResource(TRANSACTION_CONTEXT_MARKER, contextState);
}
ContextInstanceHandle<T> instanceHandle = contextState.get(contextual);
if (instanceHandle != null) {
return instanceHandle.get();
} else if (creationalContext != null) {
T createdInstance = contextual.create(creationalContext);
instanceHandle = new ContextInstanceHandleImpl<>((InjectableBean<T>) contextual, createdInstance,
creationalContext);
contextState.put(contextual, instanceHandle);
return createdInstance;
} else {
return null;
}
}
@Override
public <T> T get(Contextual<T> contextual) {
return get(contextual, null);
}
/**
* The transaction scoped context is active when a transaction is active.
*/
@Override
public boolean isActive() {
Transaction transaction = getCurrentTransaction();
if (transaction == null) {
return false;
}
try {
int currentStatus = transaction.getStatus();
return currentStatus == Status.STATUS_ACTIVE ||
currentStatus == Status.STATUS_MARKED_ROLLBACK ||
currentStatus == Status.STATUS_PREPARED ||
currentStatus == Status.STATUS_UNKNOWN ||
currentStatus == Status.STATUS_PREPARING ||
currentStatus == Status.STATUS_COMMITTING ||
currentStatus == Status.STATUS_ROLLING_BACK;
} catch (SystemException e) {
throw new RuntimeException("Error getting the status of the current transaction", e);
}
}
private Transaction getCurrentTransaction() {
try {
return transactionManager.getTransaction();
} catch (SystemException e) {
throw new RuntimeException("Error getting the current transaction", e);
}
}
/**
* Representing of the context state. It's a container for all available beans in the context.
* It's filled during bean usage and cleared on destroy.
*/
private static class TransactionContextState<T> implements ContextState {
private final ConcurrentMap<Contextual<T>, ContextInstanceHandle<T>> mapBeanToInstanceHandle = new ConcurrentHashMap<>();
/**
* Put the contextual bean and its handle to the container.
*
* @param bean bean to be added
* @param handle handle for the bean which incorporates the bean, contextual instance and the context
*/
void put(Contextual<T> bean, ContextInstanceHandle<T> handle) {
mapBeanToInstanceHandle.put(bean, handle);
}
/**
* Remove the bean from the container.
*
* @param bean contextual bean instance
*/
void remove(Contextual<T> bean) {
mapBeanToInstanceHandle.remove(bean);
}
/**
* Retrieve the bean saved in the container.
*
* @param bean retrieving the bean from the container, otherwise {@code null} is returned
*/
ContextInstanceHandle<T> get(Contextual<T> bean) {
return mapBeanToInstanceHandle.get(bean);
}
/**
* Destroying all the beans in the container and clearing the container.
*/
void destroy() {
for (ContextInstanceHandle<T> handle : mapBeanToInstanceHandle.values()) {
handle.destroy();
}
mapBeanToInstanceHandle.clear();
}
/**
* Method required by the {@link io.quarkus.arc.InjectableContext.ContextState} interface
* which is then used to get state of the scope in method {@link InjectableContext#getState()}
*
* @return list of context bean and the bean instances which are available in the container
*/
@Override
public Map<InjectableBean<?>, Object> getContextualInstances() {
return mapBeanToInstanceHandle.values().stream()
.collect(Collectors.toMap(ContextInstanceHandle::getBean, ContextInstanceHandle::get));
}
}
}

View File

@@ -19,10 +19,10 @@ import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams;
import org.jboss.tm.usertx.client.ServerVMClientUserTransaction;
import org.reactivestreams.Publisher;
import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple;
import com.arjuna.ats.jta.logging.jtaLogger;
import io.quarkus.arc.runtime.InterceptorBindings;
import io.quarkus.narayana.jta.runtime.CDIDelegatingTransactionManager;
import io.quarkus.narayana.jta.runtime.TransactionConfiguration;
import io.smallrye.reactive.converters.ReactiveTypeConverter;
import io.smallrye.reactive.converters.Registry;
@@ -97,7 +97,7 @@ public abstract class TransactionalInterceptorBase implements Serializable {
throws Exception {
TransactionConfiguration configAnnotation = getTransactionConfiguration(ic);
int currentTmTimeout = ((TransactionManagerImple) transactionManager).getTimeout();
int currentTmTimeout = ((CDIDelegatingTransactionManager) transactionManager).getTransactionTimeout();
if (configAnnotation != null && configAnnotation.timeout() != TransactionConfiguration.UNSET_TIMEOUT) {
tm.setTransactionTimeout(configAnnotation.timeout());
}
@@ -309,7 +309,6 @@ public abstract class TransactionalInterceptorBase implements Serializable {
}
protected boolean setUserTransactionAvailable(boolean available) {
boolean previousUserTransactionAvailability = ServerVMClientUserTransaction.isAvailable();
ServerVMClientUserTransaction.setAvailability(available);

View File

@@ -0,0 +1,89 @@
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<parent>
<artifactId>quarkus-integration-tests-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>quarkus-integration-test-narayana-jta</artifactId>
<name>Quarkus - Integration Tests - Narayana JTA CDI</name>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-narayana-jta</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native-image</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<id>native-image</id>
<goals>
<goal>native-image</goal>
</goals>
<configuration>
<reportErrorsAtRuntime>false</reportErrorsAtRuntime>
<cleanupServer>true</cleanupServer>
<enableHttpUrlHandler>true</enableHttpUrlHandler>
<graalvmHome>${graalvmHome}</graalvmHome>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemProperties>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
</systemProperties>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,151 @@
package io.quarkus.narayana.jta;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.BeforeDestroyed;
import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.context.Destroyed;
import javax.enterprise.context.Initialized;
import javax.enterprise.context.spi.Context;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionScoped;
import javax.transaction.Transactional;
import org.jboss.logging.Logger;
@ApplicationScoped
public class TransactionBeanWithEvents {
private static final Logger log = Logger.getLogger(TransactionBeanWithEvents.class);
private static int initializedCount, beforeDestroyedCount, destroyedCount;
private static int commitCount, rollbackCount;
@Inject
private TransactionManager tm;
static int getInitialized() {
return initializedCount;
}
static int getBeforeDestroyed() {
return beforeDestroyedCount;
}
static int getDestroyed() {
return destroyedCount;
}
static int getCommited() {
return commitCount;
}
static int getRolledBack() {
return rollbackCount;
}
@Transactional
void doInTransaction(boolean isCommit) {
log.debug("Running transactional bean method");
try {
tm.getTransaction().registerSynchronization(new Synchronization() {
@Override
public void beforeCompletion() {
}
@Override
public void afterCompletion(int status) {
if (status == Status.STATUS_ROLLEDBACK) {
rollbackCount++;
} else if (status == Status.STATUS_COMMITTED) {
commitCount++;
} else {
throw new IllegalStateException("Expected commit or rollback on transaction synchronization callback");
}
}
});
} 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,
final BeanManager beanManager) throws SystemException {
Transaction tx = tm.getTransaction();
if (tx == null) {
log.error("@Intialized expects an active transaction");
throw new IllegalStateException("@Intialized expects an active transaction");
}
if (tx.getStatus() != Status.STATUS_ACTIVE) {
log.error("@Initialized expects transaction is Status.STATUS_ACTIVE");
throw new IllegalStateException("@Initialized expects transaction is Status.STATUS_ACTIVE");
}
Context ctx = null;
try {
ctx = beanManager.getContext(TransactionScoped.class);
} catch (Exception e) {
log.error("Context on @Initialized is not available");
throw e;
}
if (!ctx.isActive()) {
log.error("Context on @Initialized has to be active");
throw new IllegalStateException("Context on @Initialized has to be active");
}
if (!(event instanceof Transaction)) {
log.error("@Intialized scope expects event payload being the " + Transaction.class.getName());
throw new IllegalStateException("@Intialized scope expects event payload being the " + Transaction.class.getName());
}
initializedCount++;
}
void transactionScopePreDestroy(@Observes @BeforeDestroyed(TransactionScoped.class) final Object event,
final BeanManager beanManager) throws SystemException {
Transaction tx = tm.getTransaction();
if (tx == null) {
log.error("@BeforeDestroyed expects an active transaction");
throw new IllegalStateException("@BeforeDestroyed expects an active transaction");
}
Context ctx = null;
try {
ctx = beanManager.getContext(TransactionScoped.class);
} catch (Exception e) {
log.error("Context on @Initialized is not available");
throw e;
}
if (!ctx.isActive()) {
log.error("Context on @BeforeDestroyed has to be active");
throw new IllegalStateException("Context on @BeforeDestroyed has to be active");
}
if (!(event instanceof Transaction)) {
log.error("@Intialized scope expects event payload being the " + Transaction.class.getName());
throw new IllegalStateException("@Intialized scope expects event payload being the " + Transaction.class.getName());
}
beforeDestroyedCount++;
}
void transactionScopeDestroyed(@Observes @Destroyed(TransactionScoped.class) final Object event,
final BeanManager beanManager) throws SystemException {
Transaction tx = tm.getTransaction();
if (tx != null)
throw new IllegalStateException("@Destroyed expects no transaction");
try {
Context ctx = beanManager.getContext(TransactionScoped.class);
throw new IllegalStateException("No bean in context expected but it's " + ctx);
} catch (final ContextNotActiveException expected) {
}
destroyedCount++;
}
}

View File

@@ -0,0 +1,16 @@
package io.quarkus.narayana.jta;
import javax.transaction.TransactionScoped;
@TransactionScoped
public class TransactionScopedBean {
private int value = 0;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}

View File

@@ -0,0 +1,72 @@
package io.quarkus.narayana.jta;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import javax.enterprise.context.ContextNotActiveException;
import javax.inject.Inject;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
class TransactionScopedTest {
@Inject
private UserTransaction tx;
@Inject
private TransactionManager tm;
@Inject
private TransactionScopedBean beanTransactional;
@Inject
private TransactionBeanWithEvents beanEvents;
@Test
void transactionScopedInTransaction() throws Exception {
tx.begin();
beanTransactional.setValue(42);
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");
tx.begin();
beanTransactional.setValue(1);
assertEquals(1, beanTransactional.getValue(), "Transaction scope did not save the value");
tx.commit();
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");
tx.rollback();
}
@Test
void scopeEventsAreEmitted() {
beanEvents.doInTransaction(true);
try {
beanEvents.doInTransaction(false);
} catch (RuntimeException expected) {
// expect runtime exception to rollback the call
}
assertEquals(2, beanEvents.getInitialized(), "Expected @Initialized to be observed");
assertEquals(2, beanEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observer");
assertEquals(2, beanEvents.getDestroyed(), "Expected @Destroyed to be observer");
assertEquals(1, beanEvents.getCommited(), "Expected commit to be called once");
assertEquals(1, beanEvents.getRolledBack(), "Expected rollback to be called once");
}
}

View File

@@ -77,6 +77,7 @@
<module>kotlin</module>
<module>mongodb-panache</module>
<module>narayana-stm</module>
<module>narayana-jta</module>
<module>elytron-security-jdbc</module>
</modules>

View File

@@ -29,10 +29,6 @@
<dependenciesToScan>
<dependency>org.eclipse.microprofile.context-propagation:microprofile-context-propagation-tck</dependency>
</dependenciesToScan>
<!-- To be removed once #3878 is implemented-->
<excludes>
<exclude>**/JTACDITest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>