Skip to content

Commit 9b66eef

Browse files
committed
UserTransaction should fire context lifecycle events
- such as @initialized(TransactionScoped.class) - resolves quarkusio#28709
1 parent 0cf278a commit 9b66eef

File tree

8 files changed

+154
-75
lines changed

8 files changed

+154
-75
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

+10-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,20 @@
2018

2119
import org.jboss.logging.Logger;
2220

23-
import io.quarkus.arc.Unremovable;
24-
2521
/**
2622
* A delegating transaction manager which receives an instance of Narayana transaction manager
2723
* and delegates all calls to it.
2824
* On top of it the implementation adds the CDI events processing for {@link TransactionScoped}.
2925
*/
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);
26+
public class NotifyingTransactionManager extends TransactionScopedNotifier implements TransactionManager, Serializable {
3527

3628
private static final long serialVersionUID = 1598L;
3729

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;
30+
private static final Logger LOG = Logger.getLogger(NotifyingTransactionManager.class);
4731

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;
32+
private transient com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple delegate;
5533

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() {
34+
NotifyingTransactionManager() {
6835
delegate = (com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple) com.arjuna.ats.jta.TransactionManager
6936
.transactionManager();
7037
}
@@ -80,9 +47,7 @@ public CDIDelegatingTransactionManager() {
8047
@Override
8148
public void begin() throws NotSupportedException, SystemException {
8249
delegate.begin();
83-
if (this.transactionScopeInitialized != null) {
84-
this.transactionScopeInitialized.fire(this.getTransaction());
85-
}
50+
initialized(getTransaction());
8651
}
8752

8853
/**
@@ -97,16 +62,11 @@ public void begin() throws NotSupportedException, SystemException {
9762
@Override
9863
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException,
9964
IllegalStateException, SystemException {
100-
if (this.transactionScopeBeforeDestroyed != null) {
101-
this.transactionScopeBeforeDestroyed.fire(this.getTransaction());
102-
}
103-
65+
beforeDestroyed(this.getTransaction());
10466
try {
10567
delegate.commit();
10668
} finally {
107-
if (this.transactionScopeDestroyed != null) {
108-
this.transactionScopeDestroyed.fire(this.toString());
109-
}
69+
destroyed(toString());
11070
}
11171
}
11272

@@ -122,20 +82,16 @@ public void commit() throws RollbackException, HeuristicMixedException, Heuristi
12282
@Override
12383
public void rollback() throws IllegalStateException, SecurityException, SystemException {
12484
try {
125-
if (this.transactionScopeBeforeDestroyed != null) {
126-
this.transactionScopeBeforeDestroyed.fire(this.getTransaction());
127-
}
85+
beforeDestroyed(getTransaction());
12886
} catch (Throwable t) {
129-
log.error("Failed to fire @BeforeDestroyed(TransactionScoped.class)", t);
87+
LOG.error("Failed to fire @BeforeDestroyed(TransactionScoped.class)", t);
13088
}
13189

13290
try {
13391
delegate.rollback();
13492
} finally {
13593
//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-
}
94+
destroyed(toString());
13995
}
14096
}
14197

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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(delegate);
26+
}
27+
28+
@Override
29+
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException,
30+
IllegalStateException, SystemException {
31+
beforeDestroyed(delegate);
32+
try {
33+
delegate.commit();
34+
} finally {
35+
destroyed(toString());
36+
}
37+
}
38+
39+
@Override
40+
public void rollback() throws IllegalStateException, SecurityException, SystemException {
41+
try {
42+
beforeDestroyed(delegate);
43+
} catch (Throwable t) {
44+
LOG.error("Failed to fire @BeforeDestroyed(TransactionScoped.class)", t);
45+
}
46+
try {
47+
delegate.rollback();
48+
} finally {
49+
destroyed(toString());
50+
}
51+
}
52+
53+
@Override
54+
public void setRollbackOnly() throws IllegalStateException, SystemException {
55+
delegate.setRollbackOnly();
56+
}
57+
58+
@Override
59+
public int getStatus() throws SystemException {
60+
return delegate.getStatus();
61+
}
62+
63+
@Override
64+
public void setTransactionTimeout(int seconds) throws SystemException {
65+
delegate.setTransactionTimeout(seconds);
66+
}
67+
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.quarkus.narayana.jta.runtime;
2+
3+
import javax.enterprise.context.BeforeDestroyed;
4+
import javax.enterprise.context.Destroyed;
5+
import javax.enterprise.context.Initialized;
6+
import javax.enterprise.event.Event;
7+
import javax.transaction.TransactionScoped;
8+
9+
import io.quarkus.arc.Arc;
10+
11+
abstract class TransactionScopedNotifier {
12+
13+
private transient Event<Object> initialized;
14+
private transient Event<Object> beforeDestroyed;
15+
private transient Event<Object> destroyed;
16+
17+
void initialized(Object payload) {
18+
if (initialized == null) {
19+
initialized = Arc.container().beanManager().getEvent()
20+
.select(Initialized.Literal.of(TransactionScoped.class));
21+
}
22+
initialized.fire(payload);
23+
}
24+
25+
void beforeDestroyed(Object payload) {
26+
if (beforeDestroyed == null) {
27+
beforeDestroyed = Arc.container().beanManager().getEvent()
28+
.select(BeforeDestroyed.Literal.of(TransactionScoped.class));
29+
}
30+
beforeDestroyed.fire(payload);
31+
}
32+
33+
void destroyed(Object payload) {
34+
if (destroyed == null) {
35+
destroyed = Arc.container().beanManager().getEvent()
36+
.select(Destroyed.Literal.of(TransactionScoped.class));
37+
}
38+
destroyed.fire(payload);
39+
}
40+
41+
}

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);

integration-tests/narayana-jta/src/main/java/io/quarkus/narayana/jta/TransactionBeanWithEvents.java

+13-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import javax.transaction.TransactionManager;
1717
import javax.transaction.TransactionScoped;
1818
import javax.transaction.Transactional;
19+
import javax.transaction.UserTransaction;
1920

2021
import org.jboss.logging.Logger;
2122

@@ -110,10 +111,13 @@ void transactionScopeActivated(@Observes @Initialized(TransactionScoped.class) f
110111
log.error("Context on @Initialized has to be active");
111112
throw new IllegalStateException("Context on @Initialized has to be active");
112113
}
113-
if (!(event instanceof Transaction)) {
114-
log.error("@Initialized scope expects event payload being the " + Transaction.class.getName());
114+
if (!(event instanceof Transaction) && !(event instanceof UserTransaction)) {
115+
log.error(
116+
"@Initialized scope expects event payload being the " + Transaction.class.getName() + " or "
117+
+ UserTransaction.class.getName());
115118
throw new IllegalStateException(
116-
"@Initialized scope expects event payload being the " + Transaction.class.getName());
119+
"@Initialized scope expects event payload being the " + Transaction.class.getName() + " or "
120+
+ UserTransaction.class.getName());
117121
}
118122

119123
initializedCount++;
@@ -130,17 +134,19 @@ void transactionScopePreDestroy(@Observes @BeforeDestroyed(TransactionScoped.cla
130134
try {
131135
ctx = beanManager.getContext(TransactionScoped.class);
132136
} catch (Exception e) {
133-
log.error("Context on @Initialized is not available");
137+
log.error("Context on @BeforeDestroyed is not available");
134138
throw e;
135139
}
136140
if (!ctx.isActive()) {
137141
log.error("Context on @BeforeDestroyed has to be active");
138142
throw new IllegalStateException("Context on @BeforeDestroyed has to be active");
139143
}
140-
if (!(event instanceof Transaction)) {
141-
log.error("@Initialized scope expects event payload being the " + Transaction.class.getName());
144+
if (!(event instanceof Transaction) && !(event instanceof UserTransaction)) {
145+
log.error("@BeforeDestroyed scope expects event payload being the " + Transaction.class.getName() + " or "
146+
+ UserTransaction.class.getName());
142147
throw new IllegalStateException(
143-
"@Initialized scope expects event payload being the " + Transaction.class.getName());
148+
"@BeforeDestroyed scope expects event payload being the " + Transaction.class.getName() + " or "
149+
+ UserTransaction.class.getName());
144150
}
145151

146152
beforeDestroyedCount++;

integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionScopedTest.java

+10-7
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ void transactionScopedInTransaction() throws Exception {
5959
}
6060

6161
@Test
62-
void scopeEventsAreEmitted() {
63-
beanEvents.cleanCounts();
62+
void scopeEventsAreEmitted() throws Exception {
63+
TransactionBeanWithEvents.cleanCounts();
6464

6565
beanEvents.doInTransaction(true);
6666

@@ -70,11 +70,14 @@ void scopeEventsAreEmitted() {
7070
// expect runtime exception to rollback the call
7171
}
7272

73-
assertEquals(2, beanEvents.getInitialized(), "Expected @Initialized to be observed");
74-
assertEquals(2, beanEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observer");
75-
assertEquals(2, beanEvents.getDestroyed(), "Expected @Destroyed to be observer");
76-
assertEquals(1, beanEvents.getCommited(), "Expected commit to be called once");
77-
assertEquals(1, beanEvents.getRolledBack(), "Expected rollback to be called once");
73+
tx.begin();
74+
tx.commit();
75+
76+
assertEquals(3, TransactionBeanWithEvents.getInitialized(), "Expected @Initialized to be observed");
77+
assertEquals(3, TransactionBeanWithEvents.getBeforeDestroyed(), "Expected @BeforeDestroyed to be observer");
78+
assertEquals(3, TransactionBeanWithEvents.getDestroyed(), "Expected @Destroyed to be observer");
79+
assertEquals(1, TransactionBeanWithEvents.getCommited(), "Expected commit to be called once");
80+
assertEquals(1, TransactionBeanWithEvents.getRolledBack(), "Expected rollback to be called once");
7881
}
7982

8083
}

0 commit comments

Comments
 (0)