Skip to content

Commit 7ade80d

Browse files
mkoubatmihalac
authored andcommitted
UserTransaction should fire context lifecycle events
- such as @initialized(TransactionScoped.class) - resolves quarkusio#28709
1 parent 435a3e9 commit 7ade80d

File tree

8 files changed

+215
-76
lines changed

8 files changed

+215
-76
lines changed

extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java

-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
5151
import io.quarkus.deployment.logging.LogCleanupFilterBuildItem;
5252
import io.quarkus.gizmo.ClassCreator;
53-
import io.quarkus.narayana.jta.runtime.CDIDelegatingTransactionManager;
5453
import io.quarkus.narayana.jta.runtime.NarayanaJtaProducers;
5554
import io.quarkus.narayana.jta.runtime.NarayanaJtaRecorder;
5655
import io.quarkus.narayana.jta.runtime.TransactionManagerConfiguration;
@@ -85,7 +84,6 @@ public void build(NarayanaJtaRecorder recorder,
8584
recorder.handleShutdown(shutdownContextBuildItem, transactions);
8685
feature.produce(new FeatureBuildItem(Feature.NARAYANA_JTA));
8786
additionalBeans.produce(new AdditionalBeanBuildItem(NarayanaJtaProducers.class));
88-
additionalBeans.produce(new AdditionalBeanBuildItem(CDIDelegatingTransactionManager.class));
8987
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf("io.quarkus.narayana.jta.RequestScopedTransaction"));
9088

9189
runtimeInit.produce(new RuntimeInitializedClassBuildItem(

extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaProducers.java

+10-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import javax.enterprise.inject.Produces;
66
import javax.inject.Singleton;
77
import javax.transaction.TransactionSynchronizationRegistry;
8+
import javax.transaction.UserTransaction;
89

910
import org.jboss.tm.JBossXATerminator;
1011
import org.jboss.tm.XAResourceRecoveryRegistry;
@@ -13,7 +14,6 @@
1314
import com.arjuna.ats.internal.jbossatx.jta.jca.XATerminator;
1415
import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple;
1516
import com.arjuna.ats.jbossatx.jta.RecoveryManagerService;
16-
import com.arjuna.ats.jta.UserTransaction;
1717

1818
import io.quarkus.arc.Unremovable;
1919

@@ -28,8 +28,15 @@ public UserTransactionRegistry userTransactionRegistry() {
2828

2929
@Produces
3030
@ApplicationScoped
31-
public javax.transaction.UserTransaction userTransaction() {
32-
return UserTransaction.userTransaction();
31+
public UserTransaction userTransaction() {
32+
return new NotifyingUserTransaction(com.arjuna.ats.jta.UserTransaction.userTransaction());
33+
}
34+
35+
@Produces
36+
@Unremovable
37+
@Singleton
38+
public javax.transaction.TransactionManager transactionManager() {
39+
return new NotifyingTransactionManager();
3340
}
3441

3542
@Produces

extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/CDIDelegatingTransactionManager.java extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NotifyingTransactionManager.java

+13-54
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
import javax.enterprise.context.Destroyed;
77
import javax.enterprise.context.Initialized;
88
import javax.enterprise.event.Event;
9-
import javax.inject.Inject;
10-
import javax.inject.Singleton;
119
import javax.transaction.HeuristicMixedException;
1210
import javax.transaction.HeuristicRollbackException;
1311
import javax.transaction.InvalidTransactionException;
@@ -20,51 +18,22 @@
2018

2119
import org.jboss.logging.Logger;
2220

23-
import io.quarkus.arc.Unremovable;
21+
import io.quarkus.narayana.jta.runtime.TransactionScopedNotifier.TransactionId;
2422

2523
/**
2624
* A delegating transaction manager which receives an instance of Narayana transaction manager
2725
* and delegates all calls to it.
2826
* On top of it the implementation adds the CDI events processing for {@link TransactionScoped}.
2927
*/
30-
@Singleton
31-
@Unremovable // used by Arc for transactional observers
32-
public class CDIDelegatingTransactionManager implements TransactionManager, Serializable {
33-
34-
private static final Logger log = Logger.getLogger(CDIDelegatingTransactionManager.class);
28+
public class NotifyingTransactionManager extends TransactionScopedNotifier implements TransactionManager, Serializable {
3529

3630
private static final long serialVersionUID = 1598L;
3731

38-
private final transient com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple delegate;
39-
40-
/**
41-
* An {@link Event} that can {@linkplain Event#fire(Object) fire}
42-
* {@link Transaction}s when the {@linkplain TransactionScoped transaction scope} is initialized.
43-
*/
44-
@Inject
45-
@Initialized(TransactionScoped.class)
46-
Event<Transaction> transactionScopeInitialized;
32+
private static final Logger LOG = Logger.getLogger(NotifyingTransactionManager.class);
4733

48-
/**
49-
* An {@link Event} that can {@linkplain Event#fire(Object) fire}
50-
* {@link Object}s before the {@linkplain TransactionScoped transaction scope} is destroyed.
51-
*/
52-
@Inject
53-
@BeforeDestroyed(TransactionScoped.class)
54-
Event<Object> transactionScopeBeforeDestroyed;
34+
private transient com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple delegate;
5535

56-
/**
57-
* An {@link Event} that can {@linkplain Event#fire(Object) fire}
58-
* {@link Object}s when the {@linkplain TransactionScoped transaction scope} is destroyed.
59-
*/
60-
@Inject
61-
@Destroyed(TransactionScoped.class)
62-
Event<Object> transactionScopeDestroyed;
63-
64-
/**
65-
* Delegating transaction manager call to com.arjuna.ats.jta.{@link com.arjuna.ats.jta.TransactionManager}
66-
*/
67-
public CDIDelegatingTransactionManager() {
36+
NotifyingTransactionManager() {
6837
delegate = (com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple) com.arjuna.ats.jta.TransactionManager
6938
.transactionManager();
7039
}
@@ -80,9 +49,7 @@ public CDIDelegatingTransactionManager() {
8049
@Override
8150
public void begin() throws NotSupportedException, SystemException {
8251
delegate.begin();
83-
if (this.transactionScopeInitialized != null) {
84-
this.transactionScopeInitialized.fire(this.getTransaction());
85-
}
52+
initialized(getTransactionId());
8653
}
8754

8855
/**
@@ -97,16 +64,12 @@ public void begin() throws NotSupportedException, SystemException {
9764
@Override
9865
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException,
9966
IllegalStateException, SystemException {
100-
if (this.transactionScopeBeforeDestroyed != null) {
101-
this.transactionScopeBeforeDestroyed.fire(this.getTransaction());
102-
}
103-
67+
TransactionId id = getTransactionId();
68+
beforeDestroyed(id);
10469
try {
10570
delegate.commit();
10671
} finally {
107-
if (this.transactionScopeDestroyed != null) {
108-
this.transactionScopeDestroyed.fire(this.toString());
109-
}
72+
destroyed(id);
11073
}
11174
}
11275

@@ -121,21 +84,17 @@ public void commit() throws RollbackException, HeuristicMixedException, Heuristi
12184
*/
12285
@Override
12386
public void rollback() throws IllegalStateException, SecurityException, SystemException {
87+
TransactionId id = getTransactionId();
12488
try {
125-
if (this.transactionScopeBeforeDestroyed != null) {
126-
this.transactionScopeBeforeDestroyed.fire(this.getTransaction());
127-
}
89+
beforeDestroyed(id);
12890
} catch (Throwable t) {
129-
log.error("Failed to fire @BeforeDestroyed(TransactionScoped.class)", t);
91+
LOG.error("Failed to fire @BeforeDestroyed(TransactionScoped.class)", t);
13092
}
131-
13293
try {
13394
delegate.rollback();
13495
} finally {
13596
//we don't need a catch block here, if this one fails we just let the exception propagate
136-
if (this.transactionScopeDestroyed != null) {
137-
this.transactionScopeDestroyed.fire(this.toString());
138-
}
97+
destroyed(id);
13998
}
14099
}
141100

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package io.quarkus.narayana.jta.runtime;
2+
3+
import javax.transaction.HeuristicMixedException;
4+
import javax.transaction.HeuristicRollbackException;
5+
import javax.transaction.NotSupportedException;
6+
import javax.transaction.RollbackException;
7+
import javax.transaction.SystemException;
8+
import javax.transaction.UserTransaction;
9+
10+
import org.jboss.logging.Logger;
11+
12+
public class NotifyingUserTransaction extends TransactionScopedNotifier implements UserTransaction {
13+
14+
private static final Logger LOG = Logger.getLogger(NotifyingUserTransaction.class);
15+
16+
private final UserTransaction delegate;
17+
18+
public NotifyingUserTransaction(UserTransaction delegate) {
19+
this.delegate = delegate;
20+
}
21+
22+
@Override
23+
public void begin() throws NotSupportedException, SystemException {
24+
delegate.begin();
25+
initialized(getTransactionId());
26+
}
27+
28+
@Override
29+
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException,
30+
IllegalStateException, SystemException {
31+
TransactionId id = getTransactionId();
32+
beforeDestroyed(id);
33+
try {
34+
delegate.commit();
35+
} finally {
36+
destroyed(id);
37+
}
38+
}
39+
40+
@Override
41+
public void rollback() throws IllegalStateException, SecurityException, SystemException {
42+
TransactionId id = getTransactionId();
43+
try {
44+
beforeDestroyed(id);
45+
} catch (Throwable t) {
46+
LOG.error("Failed to fire @BeforeDestroyed(TransactionScoped.class)", t);
47+
}
48+
try {
49+
delegate.rollback();
50+
} finally {
51+
destroyed(id);
52+
}
53+
}
54+
55+
@Override
56+
public void setRollbackOnly() throws IllegalStateException, SystemException {
57+
delegate.setRollbackOnly();
58+
}
59+
60+
@Override
61+
public int getStatus() throws SystemException {
62+
return delegate.getStatus();
63+
}
64+
65+
@Override
66+
public void setTransactionTimeout(int seconds) throws SystemException {
67+
delegate.setTransactionTimeout(seconds);
68+
}
69+
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package io.quarkus.narayana.jta.runtime;
2+
3+
import java.util.Objects;
4+
5+
import javax.enterprise.context.BeforeDestroyed;
6+
import javax.enterprise.context.Destroyed;
7+
import javax.enterprise.context.Initialized;
8+
import javax.enterprise.event.Event;
9+
import javax.transaction.SystemException;
10+
import javax.transaction.TransactionScoped;
11+
12+
import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple;
13+
14+
import io.quarkus.arc.Arc;
15+
16+
public abstract class TransactionScopedNotifier {
17+
18+
private transient Event<TransactionId> initialized;
19+
private transient Event<TransactionId> beforeDestroyed;
20+
private transient Event<TransactionId> destroyed;
21+
22+
void initialized(TransactionId transactionId) {
23+
if (initialized == null) {
24+
initialized = Arc.container().beanManager().getEvent()
25+
.select(TransactionId.class, Initialized.Literal.of(TransactionScoped.class));
26+
}
27+
initialized.fire(transactionId);
28+
}
29+
30+
void beforeDestroyed(TransactionId transactionId) {
31+
if (beforeDestroyed == null) {
32+
beforeDestroyed = Arc.container().beanManager().getEvent()
33+
.select(TransactionId.class, BeforeDestroyed.Literal.of(TransactionScoped.class));
34+
}
35+
beforeDestroyed.fire(transactionId);
36+
}
37+
38+
void destroyed(TransactionId transactionId) {
39+
if (destroyed == null) {
40+
destroyed = Arc.container().beanManager().getEvent()
41+
.select(TransactionId.class, Destroyed.Literal.of(TransactionScoped.class));
42+
}
43+
destroyed.fire(transactionId);
44+
}
45+
46+
TransactionId getTransactionId() throws SystemException {
47+
try {
48+
return new TransactionId(TransactionImple.getTransaction().toString());
49+
} catch (Exception e) {
50+
throw new SystemException("The transaction is not active!");
51+
}
52+
}
53+
54+
// we use this wrapper because if we fire an event with string payload then any "@Observes String payload" would be notified
55+
public static final class TransactionId {
56+
57+
private final String value;
58+
59+
public TransactionId(String value) {
60+
this.value = value;
61+
}
62+
63+
@Override
64+
public String toString() {
65+
return value;
66+
}
67+
68+
@Override
69+
public int hashCode() {
70+
return Objects.hash(value);
71+
}
72+
73+
@Override
74+
public boolean equals(Object obj) {
75+
if (this == obj) {
76+
return true;
77+
}
78+
if (obj == null) {
79+
return false;
80+
}
81+
if (getClass() != obj.getClass()) {
82+
return false;
83+
}
84+
TransactionId other = (TransactionId) obj;
85+
return Objects.equals(value, other.value);
86+
}
87+
88+
}
89+
90+
}

extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TransactionalInterceptorBase.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import com.arjuna.ats.jta.logging.jtaLogger;
2828

2929
import io.quarkus.arc.runtime.InterceptorBindings;
30-
import io.quarkus.narayana.jta.runtime.CDIDelegatingTransactionManager;
30+
import io.quarkus.narayana.jta.runtime.NotifyingTransactionManager;
3131
import io.quarkus.narayana.jta.runtime.TransactionConfiguration;
3232
import io.quarkus.transaction.annotations.Rollback;
3333
import io.smallrye.mutiny.Multi;
@@ -110,7 +110,7 @@ protected Object invokeInOurTx(InvocationContext ic, TransactionManager tm, Runn
110110

111111
int timeoutConfiguredForMethod = getTransactionTimeoutFromAnnotation(ic);
112112

113-
int currentTmTimeout = ((CDIDelegatingTransactionManager) transactionManager).getTransactionTimeout();
113+
int currentTmTimeout = ((NotifyingTransactionManager) transactionManager).getTransactionTimeout();
114114

115115
if (timeoutConfiguredForMethod > 0) {
116116
tm.setTransactionTimeout(timeoutConfiguredForMethod);

0 commit comments

Comments
 (0)