Skip to content

Commit ccee4c6

Browse files
authored
Refactors JtaConnection to allow status enforcement by JTA implementation (helidon-io#8479)
* Permits connection enlistment in a global transaction when the state is STATUS_MARKED_ROLLBACK Signed-off-by: Laird Nelson <laird.nelson@oracle.com> * Squashable commit; refactoring, general edits Signed-off-by: Laird Nelson <laird.nelson@oracle.com> * Squashable commit; refactoring, general edits Signed-off-by: Laird Nelson <laird.nelson@oracle.com> * Squashable commit; refactoring, general edits Signed-off-by: Laird Nelson <laird.nelson@oracle.com> * Squashable commit; refactoring, general edits Signed-off-by: Laird Nelson <laird.nelson@oracle.com> * Squashable commit; refactoring, general edits; added and reorganized tests Signed-off-by: Laird Nelson <laird.nelson@oracle.com> --------- Signed-off-by: Laird Nelson <laird.nelson@oracle.com>
1 parent 9de8334 commit ccee4c6

File tree

7 files changed

+1072
-367
lines changed

7 files changed

+1072
-367
lines changed

integrations/cdi/jpa-cdi/pom.xml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@
3030
<name>Helidon CDI Integrations JPA</name>
3131

3232
<properties>
33-
<doclint>-syntax</doclint>
34-
<eclipselink.skip>false</eclipselink.skip>
35-
<hibernate.skip>false</hibernate.skip>
36-
<spotbugs.exclude>etc/spotbugs/exclude.xml</spotbugs.exclude>
33+
<doclint>-syntax</doclint>
34+
<eclipselink.skip>false</eclipselink.skip>
35+
<helidon.jta.immediateEnlistment>false</helidon.jta.immediateEnlistment>
36+
<helidon.jta.interposedSynchronizations>true</helidon.jta.interposedSynchronizations>
37+
<helidon.jta.preemptiveEnlistmentChecks>true</helidon.jta.preemptiveEnlistmentChecks>
38+
<hibernate.skip>false</hibernate.skip>
39+
<spotbugs.exclude>etc/spotbugs/exclude.xml</spotbugs.exclude>
3740
</properties>
3841

3942
<dependencies>
@@ -291,6 +294,9 @@
291294
<reportNameSuffix>eclipselink</reportNameSuffix>
292295
<skip>${eclipselink.skip}</skip>
293296
<systemPropertyVariables>
297+
<helidon.jta.immediateEnlistment>${helidon.jta.immediateEnlistment}</helidon.jta.immediateEnlistment>
298+
<helidon.jta.interposedSynchronizations>${helidon.jta.interposedSynchronizations}</helidon.jta.interposedSynchronizations>
299+
<helidon.jta.preemptiveEnlistmentChecks>${helidon.jta.preemptiveEnlistmentChecks}</helidon.jta.preemptiveEnlistmentChecks>
294300
<java.util.logging.config.file>${project.basedir}/src/test/logging.properties</java.util.logging.config.file>
295301
</systemPropertyVariables>
296302
<testClassesDirectory>${project.build.directory}/eclipselink/test-classes</testClassesDirectory>

integrations/cdi/jpa-cdi/src/main/java/io/helidon/integrations/cdi/jpa/JtaAdaptingDataSourceProvider.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -82,6 +82,8 @@ final class JtaAdaptingDataSourceProvider implements PersistenceUnitInfoBean.Dat
8282

8383
private final boolean immediateEnlistment;
8484

85+
private final boolean preemptiveEnlistmentChecks;
86+
8587
private final ExceptionConverter exceptionConverter;
8688

8789

@@ -118,6 +120,8 @@ final class JtaAdaptingDataSourceProvider implements PersistenceUnitInfoBean.Dat
118120
Boolean.parseBoolean(System.getProperty("helidon.jta.interposedSynchronizations", "true"));
119121
this.immediateEnlistment =
120122
Boolean.parseBoolean(System.getProperty("helidon.jta.immediateEnlistment", "false"));
123+
this.preemptiveEnlistmentChecks =
124+
Boolean.parseBoolean(System.getProperty("helidon.jta.preemptiveEnlistmentChecks", "true"));
121125
Instance<ExceptionConverter> i = objects.select(ExceptionConverter.class);
122126
this.exceptionConverter = i.isUnsatisfied() ? null : i.get();
123127
}
@@ -167,7 +171,8 @@ private JtaAdaptingDataSource getDefaultJtaDataSource() {
167171
this.interposedSynchronizations,
168172
this.exceptionConverter,
169173
this.objects.select(DataSource.class).get(),
170-
this.immediateEnlistment));
174+
this.immediateEnlistment,
175+
this.preemptiveEnlistmentChecks));
171176
}
172177

173178
private JtaAdaptingDataSource getNamedJtaDataSource(String name) {
@@ -179,7 +184,8 @@ private JtaAdaptingDataSource getNamedJtaDataSource(String name) {
179184
this.exceptionConverter,
180185
this.objects.select(DataSource.class,
181186
NamedLiteral.of(n)).get(),
182-
this.immediateEnlistment));
187+
this.immediateEnlistment,
188+
this.preemptiveEnlistmentChecks));
183189
}
184190

185191
private DataSource getNamedNonJtaDataSource(String name) {

integrations/jta/jdbc/src/main/java/io/helidon/integrations/jta/jdbc/JtaAdaptingDataSource.java

Lines changed: 151 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -80,13 +80,59 @@ public final class JtaAdaptingDataSource extends AbstractDataSource {
8080
* made immediately upon {@link Connection} allocation
8181
*
8282
* @exception NullPointerException if {@code ts}, {@code tsr} or {@code ds} is {@code null}
83+
*
84+
* @see #JtaAdaptingDataSource(TransactionSupplier, TransactionSynchronizationRegistry, boolean, ExceptionConverter,
85+
* DataSource, boolean, boolean)
8386
*/
8487
public JtaAdaptingDataSource(TransactionSupplier ts,
8588
TransactionSynchronizationRegistry tsr,
8689
boolean interposedSynchronizations,
8790
ExceptionConverter ec,
8891
DataSource ds,
8992
boolean immediateEnlistment) {
93+
this(ts, tsr, interposedSynchronizations, ec, ds, immediateEnlistment, true);
94+
}
95+
96+
/**
97+
* Creates a new {@link JtaAdaptingDataSource} that wraps the supplied {@link DataSource} and helps its {@linkplain
98+
* DataSource#getConnection() connections} participate in XA transactions.
99+
*
100+
* <p>Behavior is left deliberately undefined if the supplied {@link DataSource}'s {@link
101+
* DataSource#getConnection()} or {@link DataSource#getConnection(String, String)} methods are implemented to return
102+
* or augment the return value of an invocation of the {@link javax.sql.PooledConnection#getConnection()
103+
* XAConnection#getConnection()} method. Less formally, and in general, this class is deliberately not designed to
104+
* work with JDBC constructs that are already XA-aware.</p>
105+
*
106+
* @param ts a {@link TransactionSupplier}; must not be {@code null}
107+
*
108+
* @param tsr a {@link TransactionSynchronizationRegistry}; must not be {@code null}
109+
*
110+
* @param interposedSynchronizations whether any {@link jakarta.transaction.Synchronization Synchronization}s
111+
* registered should be registered as interposed synchronizations; see {@link
112+
* TransactionSynchronizationRegistry#registerInterposedSynchronization(jakarta.transaction.Synchronization)} and
113+
* {@link jakarta.transaction.Transaction#registerSynchronization(jakarta.transaction.Synchronization)}
114+
*
115+
* @param ec an {@link ExceptionConverter}; may be {@code null} in which case a default implementation will be used
116+
* instead
117+
*
118+
* @param ds a {@link DataSource} that may not be XA-compliant; must not be {@code null}; normally supplied by a
119+
* connection pool implementation
120+
*
121+
* @param immediateEnlistment whether attempts to enlist new {@link Connection}s in a global transaction should be
122+
* made immediately upon {@link Connection} allocation
123+
*
124+
* @param preemptiveEnlistmentChecks whether early checks will be made to see if an enlistment attempt will succeed,
125+
* or whether enlistment validation will be performed by the JTA implementation
126+
*
127+
* @exception NullPointerException if {@code ts}, {@code tsr} or {@code ds} is {@code null}
128+
*/
129+
public JtaAdaptingDataSource(TransactionSupplier ts,
130+
TransactionSynchronizationRegistry tsr,
131+
boolean interposedSynchronizations,
132+
ExceptionConverter ec,
133+
DataSource ds,
134+
boolean immediateEnlistment,
135+
boolean preemptiveEnlistmentChecks) {
90136
super();
91137
Objects.requireNonNull(ts, "ts");
92138
Objects.requireNonNull(tsr, "tsr");
@@ -99,9 +145,24 @@ public JtaAdaptingDataSource(TransactionSupplier ts,
99145
// the (inherited) PooledConnection#close() method, which reads, in part: "An application never calls this
100146
// method directly; it is called by the connection pool module, or manager." As of this writing this branch
101147
// of this constructor implements this non-standard behavior.
102-
this.acs =
103-
(u, p) -> xa(ts, tsr, interposedSynchronizations, ec, xads.getXAConnection(u, p), immediateEnlistment, true);
104-
this.uacs = () -> xa(ts, tsr, interposedSynchronizations, ec, xads.getXAConnection(), immediateEnlistment, true);
148+
this.acs = (u, p) ->
149+
xa(ts,
150+
tsr,
151+
interposedSynchronizations,
152+
ec,
153+
xads.getXAConnection(u, p),
154+
immediateEnlistment,
155+
preemptiveEnlistmentChecks,
156+
true);
157+
this.uacs = () ->
158+
xa(ts,
159+
tsr,
160+
interposedSynchronizations,
161+
ec,
162+
xads.getXAConnection(),
163+
immediateEnlistment,
164+
preemptiveEnlistmentChecks,
165+
true);
105166
} else {
106167
Objects.requireNonNull(ds, "ds");
107168
this.acs =
@@ -156,6 +217,59 @@ public JtaAdaptingDataSource(TransactionSupplier ts,
156217
XADataSource xads,
157218
boolean immediateEnlistment,
158219
boolean closeXac) {
220+
this(ts, tsr, interposedSynchronizations, ec, xads, immediateEnlistment, true, closeXac);
221+
}
222+
223+
/**
224+
* Creates a new {@link JtaAdaptingDataSource} that adapts the supplied {@link XADataSource} and helps {@link
225+
* Connection}s it indirectly supplies (by way of its {@linkplain XADataSource#getXAConnection() associated
226+
* <code>XAConnection</code>}) participate in XA transactions.
227+
*
228+
* @param ts a {@link TransactionSupplier}; must not be {@code null}
229+
*
230+
* @param tsr a {@link TransactionSynchronizationRegistry}; must not be {@code null}
231+
*
232+
* @param interposedSynchronizations whether any {@link jakarta.transaction.Synchronization Synchronization}s
233+
* registered should be registered as interposed synchronizations; see {@link
234+
* TransactionSynchronizationRegistry#registerInterposedSynchronization(jakarta.transaction.Synchronization)} and
235+
* {@link jakarta.transaction.Transaction#registerSynchronization(jakarta.transaction.Synchronization)}
236+
*
237+
* @param ec an {@link ExceptionConverter}; may be {@code null} in which case a default implementation will be used
238+
* instead
239+
*
240+
* @param xads an {@link XADataSource} supplied by a connection pool implementation; must not be {@code null}
241+
*
242+
* @param immediateEnlistment whether attempts to enlist new {@link Connection}s in a global transaction should be
243+
* made immediately upon {@link Connection} allocation
244+
*
245+
* @param preemptiveEnlistmentChecks whether early checks will be made to see if an enlistment attempt will succeed,
246+
* or whether enlistment validation will be performed by the JTA implementation
247+
*
248+
* @param closeXac whether or not {@link XAConnection}s {@linkplain XADataSource#getXAConnection() supplied} by the
249+
* supplied {@link XADataSource} should be {@linkplain javax.sql.PooledConnection#close() closed} when {@linkplain
250+
* XAConnection#getConnection() their <code>Connection</code>}s are {@linkplain Connection#close() closed} (in a
251+
* non-standard fashion)
252+
*
253+
* @exception NullPointerException if {@code ts}, {@code tsr} or {@code xads} is {@code null}
254+
*
255+
* @deprecated This constructor exists only to handle certain XA-aware connection pools that allow an end-user
256+
* caller to "borrow" {@link XAConnection}s and to "return" them using their {@link
257+
* javax.sql.PooledConnection#close() close()} methods, a non-standard practice which is discouraged by the
258+
* documentation of {@link javax.sql.PooledConnection} (from which {@link XAConnection} inherits). For
259+
* such connection pools, {@link XAConnection}s that are "borrowed" must be returned in this manner to avoid leaks.
260+
* This constructor implements this behavior. Before using it, you should make sure that the connection pool in
261+
* question implementing or supplying the {@link XADataSource} has the behavior described above; normally an {@link
262+
* XAConnection} should not be used directly or closed by end-user code.
263+
*/
264+
@Deprecated(since = "3.1.0")
265+
public JtaAdaptingDataSource(TransactionSupplier ts,
266+
TransactionSynchronizationRegistry tsr,
267+
boolean interposedSynchronizations,
268+
ExceptionConverter ec,
269+
XADataSource xads,
270+
boolean immediateEnlistment,
271+
boolean preemptiveEnlistmentChecks,
272+
boolean closeXac) {
159273
super();
160274
Objects.requireNonNull(xads, "xads");
161275
Objects.requireNonNull(ts, "ts");
@@ -168,8 +282,24 @@ public JtaAdaptingDataSource(TransactionSupplier ts,
168282
// is called by the connection pool module, or manager." As of this writing this constructor permits this
169283
// non-standard behavior when closeXac is true.
170284
this.acs =
171-
(u, p) -> xa(ts, tsr, interposedSynchronizations, ec, xads.getXAConnection(u, p), immediateEnlistment, closeXac);
172-
this.uacs = () -> xa(ts, tsr, interposedSynchronizations, ec, xads.getXAConnection(), immediateEnlistment, closeXac);
285+
(u, p) ->
286+
xa(ts,
287+
tsr,
288+
interposedSynchronizations,
289+
ec,
290+
xads.getXAConnection(u, p),
291+
immediateEnlistment,
292+
preemptiveEnlistmentChecks,
293+
closeXac);
294+
this.uacs = () ->
295+
xa(ts,
296+
tsr,
297+
interposedSynchronizations,
298+
ec,
299+
xads.getXAConnection(),
300+
immediateEnlistment,
301+
preemptiveEnlistmentChecks,
302+
closeXac);
173303
}
174304

175305
@Override // DataSource
@@ -189,12 +319,14 @@ public Connection getConnection() throws SQLException {
189319

190320

191321
@Deprecated
322+
@SuppressWarnings("ParameterNumber")
192323
private static JtaConnection xa(TransactionSupplier ts,
193324
TransactionSynchronizationRegistry tsr,
194325
boolean interposedSynchronizations,
195326
ExceptionConverter ec,
196327
XAConnection xac,
197328
boolean immediateEnlistment,
329+
boolean preemptiveEnlistmentChecks,
198330
boolean closeXac)
199331
throws SQLException {
200332
if (closeXac) {
@@ -204,13 +336,16 @@ private static JtaConnection xa(TransactionSupplier ts,
204336
// reads: "An application never calls this method directly; it is called by the connection pool module, or
205337
// manager." This branch of this method implements this non-standard behavior, ensuring that both the
206338
// Connection and its sourcing XAConnection are closed appropriately.
207-
return new JtaConnection(ts,
208-
tsr,
209-
interposedSynchronizations,
210-
ec,
211-
xac.getConnection(),
212-
xac::getXAResource,
213-
immediateEnlistment) {
339+
return
340+
new JtaConnection(ts,
341+
tsr,
342+
interposedSynchronizations,
343+
ec,
344+
xac.getConnection(),
345+
xac::getXAResource,
346+
null, // no Xid consumer
347+
immediateEnlistment,
348+
preemptiveEnlistmentChecks) {
214349
@Override
215350
protected void onClose() throws SQLException {
216351
xac.close();
@@ -224,7 +359,9 @@ protected void onClose() throws SQLException {
224359
ec,
225360
xac.getConnection(),
226361
xac::getXAResource,
227-
immediateEnlistment);
362+
null, // no Xid consumer
363+
immediateEnlistment,
364+
preemptiveEnlistmentChecks);
228365
}
229366

230367

0 commit comments

Comments
 (0)