mirror of
https://github.com/jlengrand/quarkus.git
synced 2026-03-10 08:41:22 +00:00
[#3878] adding transaction scope into JTA extension
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
89
integration-tests/narayana-jta/pom.xml
Normal file
89
integration-tests/narayana-jta/pom.xml
Normal 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>
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user