This is an automated email from the ASF dual-hosted git repository.

rmaucher pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit f9ed585145fcb13604e691d2e77d0aba8e5959cb
Author: opencode <[email protected]>
AuthorDate: Mon Oct 26 12:55:06 2026 +0100

    Fail fast when an XA resource lacks an XA data source
    
    A resource of type javax.sql.XADataSource did not make the pool XA
    capable on its own: XA connections are created by the data source
    named by the dataSource or dataSourceJNDI attribute, and a pool
    configured with only driverClassName and url pools plain JDBC
    connections regardless of the XADataSource class the factory picks
    from the resource type. The documentation, however, presented the
    resource type alone as enabling XA, so this configuration silently
    produced a pool that looked and behaved exactly like a non-XA pool;
    the missing XA support only surfaced later, when a transaction
    manager requested an XA connection and the borrowed plain connection
    was closed and rejected with a generic message that named neither
    the cause nor the fix.
    
    XADataSource now re-declares both getXAConnection methods (also
    removing the fragile invisible implementation of the interface
    inherited from a superclass that does not declare it) and refuses
    the request up front, before a connection is borrowed and thrown
    away, with a message that names the missing dataSource or
    dataSourceJNDI configuration - or the class of the configured data
    source when it does not implement javax.sql.XADataSource. Working
    configurations are unaffected: the check is the same condition
    PooledConnection applies when deciding whether to create XA
    connections. The documentation now states the requirement, and two
    regression tests pin the actionable failures for a driver/URL-backed
    pool and for a non-XA backing data source.
---
 modules/jdbc-pool/doc/jdbc-pool.xml                |   9 +-
 .../org/apache/tomcat/jdbc/pool/XADataSource.java  |  64 ++++++++++++
 .../apache/tomcat/jdbc/pool/TestXaConnection.java  | 109 +++++++++++++++++++++
 webapps/docs/changelog.xml                         |  16 +++
 4 files changed, 197 insertions(+), 1 deletion(-)

diff --git a/modules/jdbc-pool/doc/jdbc-pool.xml 
b/modules/jdbc-pool/doc/jdbc-pool.xml
index 7842fe40bb..481270535b 100644
--- a/modules/jdbc-pool/doc/jdbc-pool.xml
+++ b/modules/jdbc-pool/doc/jdbc-pool.xml
@@ -105,7 +105,9 @@
           This is achieved using the <code>suspectTimeout</code> 
attribute.</li>
       <li>Connections can be retrieved from a <code>java.sql.Driver</code>, 
<code>javax.sql.DataSource</code> or <code>javax.sql.XADataSource</code>
           This is achieved using the <code>dataSource</code> and 
<code>dataSourceJNDI</code> attributes.</li>
-      <li>XA connection support</li>
+      <li>XA connection support. XA connections are provided by the data 
source named by the
+          <code>dataSource</code> or <code>dataSourceJNDI</code> attribute, 
which must implement
+          <code>javax.sql.XADataSource</code>.</li>
     </ol>
 
 
@@ -166,6 +168,11 @@
       <attribute name="type" required="true">
         <p>Type should always be <code>javax.sql.DataSource</code> or 
<code>javax.sql.XADataSource</code></p>
         <p>Depending on the type a 
<code>org.apache.tomcat.jdbc.pool.DataSource</code> or a 
<code>org.apache.tomcat.jdbc.pool.XADataSource</code> will be created.</p>
+        <p>Note that the <code>javax.sql.XADataSource</code> type only 
provides XA connections if the pool is backed
+        by an XA data source: the <code>dataSource</code> or 
<code>dataSourceJNDI</code> attribute must name a
+        <code>javax.sql.XADataSource</code> provided by the JDBC driver. A 
pool configured with
+        <code>driverClassName</code> and <code>url</code> pools plain JDBC 
connections, and a request for an XA
+        connection from such a pool fails with an explicit error.</p>
       </attribute>
     </attributes>
   </subsection>
diff --git 
a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/XADataSource.java 
b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/XADataSource.java
index 427368746d..3abc905a65 100644
--- 
a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/XADataSource.java
+++ 
b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/XADataSource.java
@@ -16,8 +16,22 @@
  */
 package org.apache.tomcat.jdbc.pool;
 
+import java.sql.SQLException;
+
+import javax.sql.XAConnection;
+
 /**
  * XA-enabled data source for distributed transaction support.
+ * <p>
+ * Note that XA support is not provided by this class itself: XA
+ * connections are obtained from the data source configured through the
+ * {@code dataSource} or {@code dataSourceJNDI} attribute, which must
+ * implement {@link javax.sql.XADataSource} (see
+ * {@link PoolConfiguration#setDataSource(Object)}). A pool configured
+ * with only {@code driverClassName} and {@code url} pools plain JDBC
+ * connections; a request for an XA connection from such a pool fails
+ * with an explicit message naming the missing configuration rather
+ * than later, inside the transaction manager.
  */
 public class XADataSource extends DataSource implements javax.sql.XADataSource 
{
 
@@ -36,4 +50,54 @@ public class XADataSource extends DataSource implements 
javax.sql.XADataSource {
         super(poolProperties);
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This implementation first checks that the pool can provide XA
+     * connections at all, and fails with an explicit configuration hint
+     * when it cannot.
+     */
+    @Override
+    public XAConnection getXAConnection() throws SQLException {
+        requireXABacking();
+        return super.getXAConnection();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This implementation first checks that the pool can provide XA
+     * connections at all, and fails with an explicit configuration hint
+     * when it cannot.
+     * @param username The user name
+     * @param password The password
+     */
+    @Override
+    public XAConnection getXAConnection(String username, String password) 
throws SQLException {
+        requireXABacking();
+        return super.getXAConnection(username, password);
+    }
+
+    /**
+     * Verifies that the pool is backed by an XA data source, matching
+     * the condition under which {@link PooledConnection} creates XA
+     * connections.
+     * @throws SQLException if the pool cannot hold XA connections, with
+     *         a message naming the configuration that is missing
+     */
+    private void requireXABacking() throws SQLException {
+        Object ds = getPoolProperties().getDataSource();
+        if (ds instanceof javax.sql.XADataSource) {
+            return;
+        }
+        if (ds == null) {
+            throw new SQLException("The connection pool [" + getName() + "] 
cannot provide XA connections: it is"
+                    + " configured with a JDBC driver and URL (or the 
'dataSourceJNDI' lookup did not resolve to a"
+                    + " data source), so the pool holds plain JDBC 
connections. Set the 'dataSource' or"
+                    + " 'dataSourceJNDI' attribute to a javax.sql.XADataSource 
provided by the JDBC driver.");
+        }
+        throw new SQLException("The connection pool [" + getName() + "] cannot 
provide XA connections: the data"
+                + " source configured through the 'dataSource' or 
'dataSourceJNDI' attribute ("
+                + ds.getClass().getName() + ") does not implement 
javax.sql.XADataSource.");
+    }
 }
diff --git 
a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestXaConnection.java
 
b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestXaConnection.java
index 500f159918..4908181b33 100644
--- 
a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestXaConnection.java
+++ 
b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestXaConnection.java
@@ -168,4 +168,113 @@ class TestXaConnection extends JdbcPoolBaseTest {
         assertThrows(SQLException.class, ds::getXAConnection,
                 "a non-XA pool must refuse XA connections");
     }
+
+    @Test
+    void driverUrlBackedXaPoolFailsXaRequestWithActionableMessage() {
+        // The documented "<Resource type=javax.sql.XADataSource .../>"
+        // shape with only driver/URL: the marker class cannot make the
+        // pooled connections XA-capable, so the request must fail with an
+        // explicit hint at the missing configuration instead of a generic
+        // refusal that only reads as such after the connection was already
+        // borrowed, closed and thrown away.
+        XADataSource ds = new XADataSource();
+        ds.setUrl(getUrl());
+        ds.setDriverClassName("org.h2.Driver");
+        ds.setUsername("sa");
+        ds.setPassword("");
+        ds.getPoolProperties().setJmxEnabled(false);
+        ds.getPoolProperties().setInitialSize(1);
+        ds.getPoolProperties().setMinIdle(0);
+        manage(ds);
+
+        SQLException e = assertThrows(SQLException.class, ds::getXAConnection,
+                "a driver/URL backed XA pool must refuse XA connections");
+        assertTrue(e.getMessage().contains("dataSource"),
+                "failure must name the missing dataSource/dataSourceJNDI 
configuration, was: "
+                        + e.getMessage());
+        SQLException e2 = assertThrows(SQLException.class, () -> 
ds.getXAConnection("sa", ""),
+                "the credential variant must refuse the same way");
+        assertTrue(e2.getMessage().contains("dataSource"),
+                "failure must name the missing dataSource/dataSourceJNDI 
configuration, was: "
+                        + e2.getMessage());
+    }
+
+    @Test
+    void nonXaBackedDataSourceFailsXaRequestWithActionableMessage() {
+        // A pool backed through the dataSource attribute by a data source
+        // that is not XA-capable must also fail with the configuration hint.
+        XADataSource ds = new XADataSource();
+        ds.getPoolProperties().setJmxEnabled(false);
+        ds.getPoolProperties().setInitialSize(1);
+        ds.getPoolProperties().setMinIdle(0);
+        ds.getPoolProperties().setDataSource(new PlainDataSource(getUrl()));
+        manage(ds);
+
+        SQLException e = assertThrows(SQLException.class, ds::getXAConnection,
+                "a pool backed by a non-XA data source must refuse XA 
connections");
+        assertTrue(e.getMessage().contains("PlainDataSource"),
+                "failure must name the offending data source, was: " + 
e.getMessage());
+        assertTrue(e.getMessage().contains("javax.sql.XADataSource"),
+                "failure must name the missing XA data source type, was: " + 
e.getMessage());
+    }
+
+    /**
+     * A plain, non-XA data source, as one might reference by mistake
+     * through the dataSource or dataSourceJNDI attribute.
+     */
+    private static final class PlainDataSource implements javax.sql.DataSource 
{
+
+        private final org.h2.jdbcx.JdbcDataSource delegate = new 
org.h2.jdbcx.JdbcDataSource();
+
+        PlainDataSource(String url) {
+            delegate.setUrl(url);
+            delegate.setUser("sa");
+            delegate.setPassword("");
+        }
+
+        @Override
+        public Connection getConnection() throws SQLException {
+            return delegate.getConnection();
+        }
+
+        @Override
+        public Connection getConnection(String username, String password) 
throws SQLException {
+            return delegate.getConnection(username, password);
+        }
+
+        @Override
+        public java.io.PrintWriter getLogWriter() throws SQLException {
+            return delegate.getLogWriter();
+        }
+
+        @Override
+        public void setLogWriter(java.io.PrintWriter out) throws SQLException {
+            delegate.setLogWriter(out);
+        }
+
+        @Override
+        public void setLoginTimeout(int seconds) throws SQLException {
+            delegate.setLoginTimeout(seconds);
+        }
+
+        @Override
+        public int getLoginTimeout() throws SQLException {
+            return delegate.getLoginTimeout();
+        }
+
+        @Override
+        public java.util.logging.Logger getParentLogger() throws 
java.sql.SQLFeatureNotSupportedException {
+            return delegate.getParentLogger();
+        }
+
+        @Override
+        public <T> T unwrap(Class<T> iface) throws SQLException {
+            return delegate.unwrap(iface);
+        }
+
+        @Override
+        public boolean isWrapperFor(Class<?> iface) throws SQLException {
+            return iface.isInstance(this) || delegate.isWrapperFor(iface);
+        }
+    }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 1cc573601e..698cdd83e2 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -827,6 +827,22 @@
         always empty. Fair queueing remains available through
         <code>FairBlockingQueue</code>. (remm)
       </fix>
+      <fix>
+        Make the JDBC pool fail fast and explicitly when a resource of
+        type <code>javax.sql.XADataSource</code> is not actually backed
+        by an XA data source. XA connections are created by the data
+        source named by the <code>dataSource</code> or
+        <code>dataSourceJNDI</code> attribute, but the documentation
+        presented the resource type alone as enabling XA, so the common
+        <code>driverClassName</code> and <code>url</code> configuration
+        silently produced a pool of plain JDBC connections that looked
+        and behaved exactly like a non-XA pool; the missing XA support
+        only surfaced later, as an opaque generic failure inside the
+        transaction manager when it requested an XA connection. Such a
+        request now fails immediately with a message that names the
+        missing configuration, and the documentation now states the
+        requirement. (remm)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Other">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to