-
Notifications
You must be signed in to change notification settings - Fork 566
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduces JtaConnection.java (#5905)
Signed-off-by: Laird Nelson <laird.nelson@oracle.com>
- Loading branch information
Showing
7 changed files
with
2,078 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
integrations/jdbc/jdbc/src/main/java/io/helidon/integrations/jdbc/SQLSupplier.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* Copyright (c) 2023 Oracle and/or its affiliates. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.helidon.integrations.jdbc; | ||
|
||
import java.sql.SQLException; | ||
|
||
/** | ||
* A useful functional interface whose implementations can perform work that may throw a {@link SQLException}. | ||
* | ||
* @param <T> the type of the object supplied | ||
* | ||
* @see #get() | ||
*/ | ||
@FunctionalInterface | ||
public interface SQLSupplier<T> { | ||
|
||
/** | ||
* Performs work and returns the result. | ||
* | ||
* @return the result of the work | ||
* | ||
* @exception SQLException if a database access error occurs | ||
*/ | ||
T get() throws SQLException; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
250 changes: 250 additions & 0 deletions
250
...ations/jta/jdbc/src/main/java/io/helidon/integrations/jta/jdbc/JtaAdaptingDataSource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
/* | ||
* Copyright (c) 2023 Oracle and/or its affiliates. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.helidon.integrations.jta.jdbc; | ||
|
||
import java.sql.Connection; | ||
import java.sql.SQLException; | ||
import java.util.Objects; | ||
|
||
import javax.sql.DataSource; | ||
import javax.sql.XAConnection; | ||
import javax.sql.XADataSource; | ||
|
||
import io.helidon.integrations.jdbc.AbstractDataSource; | ||
|
||
import jakarta.transaction.TransactionSynchronizationRegistry; | ||
|
||
/** | ||
* An {@link AbstractDataSource} that wraps another {@link DataSource} that might not behave correctly in the presence | ||
* of JTA transaction management, such as one supplied by any of several freely and commercially available connection | ||
* pools, and that makes such a non-JTA-aware {@link DataSource} behave as sensibly as possible in the presence of a | ||
* JTA-managed transaction. | ||
*/ | ||
public final class JtaAdaptingDataSource extends AbstractDataSource { | ||
|
||
|
||
/* | ||
* Instance fields. | ||
*/ | ||
|
||
|
||
private final AuthenticatedConnectionSupplier acs; | ||
|
||
private final UnauthenticatedConnectionSupplier uacs; | ||
|
||
|
||
/* | ||
* Constructors. | ||
*/ | ||
|
||
|
||
/** | ||
* Creates a new {@link JtaAdaptingDataSource} that wraps the supplied {@link DataSource} and helps its {@linkplain | ||
* DataSource#getConnection() connections} participate in XA transactions. | ||
* | ||
* <p>Behavior is left deliberately undefined if the supplied {@link DataSource}'s {@link | ||
* DataSource#getConnection()} or {@link DataSource#getConnection(String, String)} methods are implemented to return | ||
* or augment the return value of an invocation of the {@link javax.sql.PooledConnection#getConnection() | ||
* XAConnection#getConnection()} method. Less formally, and in general, this class is deliberately not designed to | ||
* work with JDBC constructs that are already XA-aware.</p> | ||
* | ||
* @param ts a {@link TransactionSupplier}; must not be {@code null} | ||
* | ||
* @param tsr a {@link TransactionSynchronizationRegistry}; must not be {@code null} | ||
* | ||
* @param interposedSynchronizations whether any {@link jakarta.transaction.Synchronization Synchronization}s | ||
* registered should be registered as interposed synchronizations; see {@link | ||
* TransactionSynchronizationRegistry#registerInterposedSynchronization(jakarta.transaction.Synchronization)} and | ||
* {@link jakarta.transaction.Transaction#registerSynchronization(jakarta.transaction.Synchronization)} | ||
* | ||
* @param ec an {@link ExceptionConverter}; may be {@code null} in which case a default implementation will be used | ||
* instead | ||
* | ||
* @param ds a {@link DataSource} that may not be XA-compliant; must not be {@code null}; normally supplied by a | ||
* connection pool implementation | ||
* | ||
* @param immediateEnlistment whether attempts to enlist new {@link Connection}s in a global transaction should be | ||
* made immediately upon {@link Connection} allocation | ||
* | ||
* @exception NullPointerException if {@code ts}, {@code tsr} or {@code ds} is {@code null} | ||
*/ | ||
public JtaAdaptingDataSource(TransactionSupplier ts, | ||
TransactionSynchronizationRegistry tsr, | ||
boolean interposedSynchronizations, | ||
ExceptionConverter ec, | ||
DataSource ds, | ||
boolean immediateEnlistment) { | ||
super(); | ||
Objects.requireNonNull(ts, "ts"); | ||
Objects.requireNonNull(tsr, "tsr"); | ||
if (ds instanceof XADataSource xads) { | ||
// Some connection pools supply XADataSource objects that pool XAConnections while also unconventionally | ||
// exposing them to end users, and also implement the javax.sql.DataSource interface by throwing exceptions | ||
// when its methods are invoked. Although XAConnections are not intended for end users, when logical | ||
// representations of pooled XAConnections *are* supplied to end users by such a connection pool, those | ||
// "borrowed" representations must be "returned" via their close() methods, counter to the documentation of | ||
// the (inherited) PooledConnection#close() method, which reads, in part: "An application never calls this | ||
// method directly; it is called by the connection pool module, or manager." As of this writing this branch | ||
// of this constructor implements this non-standard behavior. | ||
this.acs = | ||
(u, p) -> xa(ts, tsr, interposedSynchronizations, ec, xads.getXAConnection(u, p), immediateEnlistment, true); | ||
this.uacs = () -> xa(ts, tsr, interposedSynchronizations, ec, xads.getXAConnection(), immediateEnlistment, true); | ||
} else { | ||
Objects.requireNonNull(ds, "ds"); | ||
this.acs = | ||
(u, p) -> new JtaConnection(ts, tsr, interposedSynchronizations, ec, ds.getConnection(u, p), immediateEnlistment); | ||
this.uacs = () -> new JtaConnection(ts, tsr, interposedSynchronizations, ec, ds.getConnection(), immediateEnlistment); | ||
} | ||
} | ||
|
||
/** | ||
* Creates a new {@link JtaAdaptingDataSource} that adapts the supplied {@link XADataSource} and helps {@link | ||
* Connection}s it indirectly supplies (by way of its {@linkplain XADataSource#getXAConnection() associated | ||
* <code>XAConnection</code>}) participate in XA transactions. | ||
* | ||
* @param ts a {@link TransactionSupplier}; must not be {@code null} | ||
* | ||
* @param tsr a {@link TransactionSynchronizationRegistry}; must not be {@code null} | ||
* | ||
* @param interposedSynchronizations whether any {@link jakarta.transaction.Synchronization Synchronization}s | ||
* registered should be registered as interposed synchronizations; see {@link | ||
* TransactionSynchronizationRegistry#registerInterposedSynchronization(jakarta.transaction.Synchronization)} and | ||
* {@link jakarta.transaction.Transaction#registerSynchronization(jakarta.transaction.Synchronization)} | ||
* | ||
* @param ec an {@link ExceptionConverter}; may be {@code null} in which case a default implementation will be used | ||
* instead | ||
* | ||
* @param xads an {@link XADataSource} supplied by a connection pool implementation; must not be {@code null} | ||
* | ||
* @param immediateEnlistment whether attempts to enlist new {@link Connection}s in a global transaction should be | ||
* made immediately upon {@link Connection} allocation | ||
* | ||
* @param closeXac whether or not {@link XAConnection}s {@linkplain XADataSource#getXAConnection() supplied} by the | ||
* supplied {@link XADataSource} should be {@linkplain javax.sql.PooledConnection#close() closed} when {@linkplain | ||
* XAConnection#getConnection() their <code>Connection</code>}s are {@linkplain Connection#close() closed} (in a | ||
* non-standard fashion) | ||
* | ||
* @exception NullPointerException if {@code ts}, {@code tsr} or {@code xads} is {@code null} | ||
* | ||
* @deprecated This constructor exists only to handle certain XA-aware connection pools that allow an end-user | ||
* caller to "borrow" {@link XAConnection}s and to "return" them using their {@link | ||
* javax.sql.PooledConnection#close() close()} methods, a non-standard practice which is discouraged by the | ||
* documentation of {@link javax.sql.PooledConnection} (from which {@link XAConnection} inherits). For | ||
* such connection pools, {@link XAConnection}s that are "borrowed" must be returned in this manner to avoid leaks. | ||
* This constructor implements this behavior. Before using it, you should make sure that the connection pool in | ||
* question implementing or supplying the {@link XADataSource} has the behavior described above; normally an {@link | ||
* XAConnection} should not be used directly or closed by end-user code. | ||
*/ | ||
@Deprecated(since = "3.1.0") | ||
public JtaAdaptingDataSource(TransactionSupplier ts, | ||
TransactionSynchronizationRegistry tsr, | ||
boolean interposedSynchronizations, | ||
ExceptionConverter ec, | ||
XADataSource xads, | ||
boolean immediateEnlistment, | ||
boolean closeXac) { | ||
super(); | ||
Objects.requireNonNull(xads, "xads"); | ||
Objects.requireNonNull(ts, "ts"); | ||
Objects.requireNonNull(tsr, "tsr"); | ||
// Some connection pools supply XADataSource objects that pool XAConnections. In all likelihood, they should not | ||
// do this, given that XAConnections are supposed to be for the innards of connection pools, not for end users. | ||
// Nevertheless, when such XAConnections are pooled and logical representations of them are supplied by such a | ||
// connection pool, those representations must be closed, counter to the documentation of the (inherited) | ||
// PooledConnection#close() method, which reads, in part: "An application never calls this method directly; it | ||
// is called by the connection pool module, or manager." As of this writing this constructor permits this | ||
// non-standard behavior when closeXac is true. | ||
this.acs = | ||
(u, p) -> xa(ts, tsr, interposedSynchronizations, ec, xads.getXAConnection(u, p), immediateEnlistment, closeXac); | ||
this.uacs = () -> xa(ts, tsr, interposedSynchronizations, ec, xads.getXAConnection(), immediateEnlistment, closeXac); | ||
} | ||
|
||
@Override // DataSource | ||
public Connection getConnection(String username, String password) throws SQLException { | ||
return this.acs.getConnection(username, password); | ||
} | ||
|
||
@Override // DataSource | ||
public Connection getConnection() throws SQLException { | ||
return this.uacs.getConnection(); | ||
} | ||
|
||
|
||
/* | ||
* Static methods. | ||
*/ | ||
|
||
|
||
@Deprecated | ||
private static JtaConnection xa(TransactionSupplier ts, | ||
TransactionSynchronizationRegistry tsr, | ||
boolean interposedSynchronizations, | ||
ExceptionConverter ec, | ||
XAConnection xac, | ||
boolean immediateEnlistment, | ||
boolean closeXac) | ||
throws SQLException { | ||
if (closeXac) { | ||
// Some connection pools allow you to "borrow" XAConnections. XAConnections were never intended to be | ||
// exposed to end users in this fashion. To return a "borrowed" XAConnection, you invoke its close() method, | ||
// which violates the contract of PooledConnection#close(), which XAConnection inherits, whose documentation | ||
// reads: "An application never calls this method directly; it is called by the connection pool module, or | ||
// manager." This branch of this method implements this non-standard behavior, ensuring that both the | ||
// Connection and its sourcing XAConnection are closed appropriately. | ||
return new JtaConnection(ts, | ||
tsr, | ||
interposedSynchronizations, | ||
ec, | ||
xac.getConnection(), | ||
xac::getXAResource, | ||
immediateEnlistment) { | ||
@Override | ||
protected void onClose() throws SQLException { | ||
xac.close(); | ||
} | ||
}; | ||
} | ||
return | ||
new JtaConnection(ts, | ||
tsr, | ||
interposedSynchronizations, | ||
ec, | ||
xac.getConnection(), | ||
xac::getXAResource, | ||
immediateEnlistment); | ||
} | ||
|
||
|
||
/* | ||
* Inner and nested classes. | ||
*/ | ||
|
||
|
||
@FunctionalInterface | ||
private interface UnauthenticatedConnectionSupplier { | ||
|
||
Connection getConnection() throws SQLException; | ||
|
||
} | ||
|
||
@FunctionalInterface | ||
private interface AuthenticatedConnectionSupplier { | ||
|
||
Connection getConnection(String username, String password) throws SQLException; | ||
|
||
} | ||
|
||
} |
Oops, something went wrong.