LocalXAConnectionFactory.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.dbcp2.managed;

  18. import java.sql.Connection;
  19. import java.sql.SQLException;
  20. import java.util.Objects;

  21. import javax.transaction.TransactionManager;
  22. import javax.transaction.TransactionSynchronizationRegistry;
  23. import javax.transaction.xa.XAException;
  24. import javax.transaction.xa.XAResource;
  25. import javax.transaction.xa.Xid;

  26. import org.apache.commons.dbcp2.ConnectionFactory;

  27. /**
  28.  * An implementation of XAConnectionFactory which manages non-XA connections in XA transactions. A non-XA connection
  29.  * commits and rolls back as part of the XA transaction, but is not recoverable since the connection does not implement
  30.  * the 2-phase protocol.
  31.  *
  32.  * @since 2.0
  33.  */
  34. public class LocalXAConnectionFactory implements XAConnectionFactory {

  35.     /**
  36.      * LocalXAResource is a fake XAResource for non-XA connections. When a transaction is started the connection
  37.      * auto-commit is turned off. When the connection is committed or rolled back, the commit or rollback method is
  38.      * called on the connection and then the original auto-commit value is restored.
  39.      * <p>
  40.      * The LocalXAResource also respects the connection read-only setting. If the connection is read-only the commit
  41.      * method will not be called, and the prepare method returns the XA_RDONLY.
  42.      * </p>
  43.      * <p>
  44.      * It is assumed that the wrapper around a managed connection disables the setAutoCommit(), commit(), rollback() and
  45.      * setReadOnly() methods while a transaction is in progress.
  46.      * </p>
  47.      *
  48.      * @since 2.0
  49.      */
  50.     protected static class LocalXAResource implements XAResource {
  51.         private static final Xid[] EMPTY_XID_ARRAY = {};
  52.         private final Connection connection;
  53.         private Xid currentXid; // @GuardedBy("this")
  54.         private boolean originalAutoCommit; // @GuardedBy("this")

  55.         /**
  56.          * Constructs a new instance for a given connection.
  57.          *
  58.          * @param localTransaction A connection.
  59.          */
  60.         public LocalXAResource(final Connection localTransaction) {
  61.             this.connection = localTransaction;
  62.         }

  63.         private Xid checkCurrentXid() throws XAException {
  64.             if (this.currentXid == null) {
  65.                 throw new XAException("There is no current transaction");
  66.             }
  67.             return currentXid;
  68.         }

  69.         /**
  70.          * Commits the transaction and restores the original auto commit setting.
  71.          *
  72.          * @param xid
  73.          *            the id of the transaction branch for this connection
  74.          * @param flag
  75.          *            ignored
  76.          * @throws XAException
  77.          *             if connection.commit() throws an SQLException
  78.          */
  79.         @Override
  80.         public synchronized void commit(final Xid xid, final boolean flag) throws XAException {
  81.             Objects.requireNonNull(xid, "xid");
  82.             if (!checkCurrentXid().equals(xid)) {
  83.                 throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid);
  84.             }

  85.             try {
  86.                 // make sure the connection isn't already closed
  87.                 if (connection.isClosed()) {
  88.                     throw new XAException("Connection is closed");
  89.                 }

  90.                 // A read only connection should not be committed
  91.                 if (!connection.isReadOnly()) {
  92.                     connection.commit();
  93.                 }
  94.             } catch (final SQLException e) {
  95.                 throw (XAException) new XAException().initCause(e);
  96.             } finally {
  97.                 try {
  98.                     connection.setAutoCommit(originalAutoCommit);
  99.                 } catch (final SQLException ignored) {
  100.                     // ignored
  101.                 }
  102.                 this.currentXid = null;
  103.             }
  104.         }

  105.         /**
  106.          * This method does nothing.
  107.          *
  108.          * @param xid
  109.          *            the id of the transaction branch for this connection
  110.          * @param flag
  111.          *            ignored
  112.          * @throws XAException
  113.          *             if the connection is already enlisted in another transaction
  114.          */
  115.         @Override
  116.         public synchronized void end(final Xid xid, final int flag) throws XAException {
  117.             Objects.requireNonNull(xid, "xid");
  118.             if (!checkCurrentXid().equals(xid)) {
  119.                 throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid);
  120.             }

  121.             // This notification tells us that the application server is done using this
  122.             // connection for the time being. The connection is still associated with an
  123.             // open transaction, so we must still wait for the commit or rollback method
  124.         }

  125.         /**
  126.          * Clears the currently associated transaction if it is the specified xid.
  127.          *
  128.          * @param xid
  129.          *            the id of the transaction to forget
  130.          */
  131.         @Override
  132.         public synchronized void forget(final Xid xid) {
  133.             if (xid != null && xid.equals(currentXid)) {
  134.                 currentXid = null;
  135.             }
  136.         }

  137.         /**
  138.          * Always returns 0 since we have no way to set a transaction timeout on a JDBC connection.
  139.          *
  140.          * @return always 0
  141.          */
  142.         @Override
  143.         public int getTransactionTimeout() {
  144.             return 0;
  145.         }

  146.         /**
  147.          * Gets the current xid of the transaction branch associated with this XAResource.
  148.          *
  149.          * @return the current xid of the transaction branch associated with this XAResource.
  150.          */
  151.         public synchronized Xid getXid() {
  152.             return currentXid;
  153.         }

  154.         /**
  155.          * Returns true if the specified XAResource == this XAResource.
  156.          *
  157.          * @param xaResource
  158.          *            the XAResource to test
  159.          * @return true if the specified XAResource == this XAResource; false otherwise
  160.          */
  161.         @Override
  162.         public boolean isSameRM(final XAResource xaResource) {
  163.             return this == xaResource;
  164.         }

  165.         /**
  166.          * This method does nothing since the LocalXAConnection does not support two-phase-commit. This method will
  167.          * return XAResource.XA_RDONLY if the connection isReadOnly(). This assumes that the physical connection is
  168.          * wrapped with a proxy that prevents an application from changing the read-only flag while enrolled in a
  169.          * transaction.
  170.          *
  171.          * @param xid
  172.          *            the id of the transaction branch for this connection
  173.          * @return XAResource.XA_RDONLY if the connection.isReadOnly(); XAResource.XA_OK otherwise
  174.          */
  175.         @Override
  176.         public synchronized int prepare(final Xid xid) {
  177.             // if the connection is read-only, then the resource is read-only
  178.             // NOTE: this assumes that the outer proxy throws an exception when application code
  179.             // attempts to set this in a transaction
  180.             try {
  181.                 if (connection.isReadOnly()) {
  182.                     // update the auto commit flag
  183.                     connection.setAutoCommit(originalAutoCommit);

  184.                     // tell the transaction manager we are read only
  185.                     return XA_RDONLY;
  186.                 }
  187.             } catch (final SQLException ignored) {
  188.                 // no big deal
  189.             }

  190.             // this is a local (one phase) only connection, so we can't prepare
  191.             return XA_OK;
  192.         }

  193.         /**
  194.          * Always returns a zero length Xid array. The LocalXAConnectionFactory cannot support recovery, so no xids
  195.          * will ever be found.
  196.          *
  197.          * @param flag
  198.          *            ignored since recovery is not supported
  199.          * @return always a zero length Xid array.
  200.          */
  201.         @Override
  202.         public Xid[] recover(final int flag) {
  203.             return EMPTY_XID_ARRAY;
  204.         }

  205.         /**
  206.          * Rolls back the transaction and restores the original auto commit setting.
  207.          *
  208.          * @param xid
  209.          *            the id of the transaction branch for this connection
  210.          * @throws XAException
  211.          *             if connection.rollback() throws an SQLException
  212.          */
  213.         @Override
  214.         public synchronized void rollback(final Xid xid) throws XAException {
  215.             Objects.requireNonNull(xid, "xid");
  216.             if (!checkCurrentXid().equals(xid)) {
  217.                 throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid);
  218.             }

  219.             try {
  220.                 connection.rollback();
  221.             } catch (final SQLException e) {
  222.                 throw (XAException) new XAException().initCause(e);
  223.             } finally {
  224.                 try {
  225.                     connection.setAutoCommit(originalAutoCommit);
  226.                 } catch (final SQLException ignored) {
  227.                     // Ignored.
  228.                 }
  229.                 this.currentXid = null;
  230.             }
  231.         }

  232.         /**
  233.          * Always returns false since we have no way to set a transaction timeout on a JDBC connection.
  234.          *
  235.          * @param transactionTimeout
  236.          *            ignored since we have no way to set a transaction timeout on a JDBC connection
  237.          * @return always false
  238.          */
  239.         @Override
  240.         public boolean setTransactionTimeout(final int transactionTimeout) {
  241.             return false;
  242.         }

  243.         /**
  244.          * Signals that a connection has been enrolled in a transaction. This method saves off the current auto
  245.          * commit flag, and then disables auto commit. The original auto commit setting is restored when the transaction
  246.          * completes.
  247.          *
  248.          * @param xid
  249.          *            the id of the transaction branch for this connection
  250.          * @param flag
  251.          *            either XAResource.TMNOFLAGS or XAResource.TMRESUME
  252.          * @throws XAException
  253.          *             if the connection is already enlisted in another transaction, or if auto-commit could not be
  254.          *             disabled
  255.          */
  256.         @Override
  257.         public synchronized void start(final Xid xid, final int flag) throws XAException {
  258.             if (flag == TMNOFLAGS) {
  259.                 // first time in this transaction

  260.                 // make sure we aren't already in another tx
  261.                 if (this.currentXid != null) {
  262.                     throw new XAException("Already enlisted in another transaction with xid " + xid);
  263.                 }

  264.                 // save off the current auto commit flag, so it can be restored after the transaction completes
  265.                 try {
  266.                     originalAutoCommit = connection.getAutoCommit();
  267.                 } catch (final SQLException ignored) {
  268.                     // no big deal, just assume it was off
  269.                     originalAutoCommit = true;
  270.                 }

  271.                 // update the auto commit flag
  272.                 try {
  273.                     connection.setAutoCommit(false);
  274.                 } catch (final SQLException e) {
  275.                     throw (XAException) new XAException("Count not turn off auto commit for a XA transaction")
  276.                             .initCause(e);
  277.                 }

  278.                 this.currentXid = xid;
  279.             } else if (flag == TMRESUME) {
  280.                 if (!xid.equals(this.currentXid)) {
  281.                     throw new XAException("Attempting to resume in different transaction: expected " + this.currentXid
  282.                             + ", but was " + xid);
  283.                 }
  284.             } else {
  285.                 throw new XAException("Unknown start flag " + flag);
  286.             }
  287.         }
  288.     }
  289.     private final TransactionRegistry transactionRegistry;

  290.     private final ConnectionFactory connectionFactory;

  291.     /**
  292.      * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database connections.
  293.      * The connections are enlisted into transactions using the specified transaction manager.
  294.      *
  295.      * @param transactionManager
  296.      *            the transaction manager in which connections will be enlisted
  297.      * @param connectionFactory
  298.      *            the connection factory from which connections will be retrieved
  299.      */
  300.     public LocalXAConnectionFactory(final TransactionManager transactionManager,
  301.             final ConnectionFactory connectionFactory) {
  302.         this(transactionManager, null, connectionFactory);
  303.     }

  304.     /**
  305.      * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database connections.
  306.      * The connections are enlisted into transactions using the specified transaction manager.
  307.      *
  308.      * @param transactionManager
  309.      *            the transaction manager in which connections will be enlisted
  310.      * @param transactionSynchronizationRegistry
  311.      *            the optional TSR to register synchronizations with
  312.      * @param connectionFactory
  313.      *            the connection factory from which connections will be retrieved
  314.      * @since 2.8.0
  315.      */
  316.     public LocalXAConnectionFactory(final TransactionManager transactionManager,
  317.             final TransactionSynchronizationRegistry transactionSynchronizationRegistry,
  318.             final ConnectionFactory connectionFactory) {
  319.         Objects.requireNonNull(transactionManager, "transactionManager");
  320.         Objects.requireNonNull(connectionFactory, "connectionFactory");
  321.         this.transactionRegistry = new TransactionRegistry(transactionManager, transactionSynchronizationRegistry);
  322.         this.connectionFactory = connectionFactory;
  323.     }

  324.     @Override
  325.     public Connection createConnection() throws SQLException {
  326.         // create a new connection
  327.         final Connection connection = connectionFactory.createConnection();

  328.         // create a XAResource to manage the connection during XA transactions
  329.         final XAResource xaResource = new LocalXAResource(connection);

  330.         // register the XA resource for the connection
  331.         transactionRegistry.registerConnection(connection, xaResource);

  332.         return connection;
  333.     }

  334.     /**
  335.      * Gets the connection factory.
  336.      *
  337.      * @return The connection factory.
  338.      * @since 2.6.0
  339.      */
  340.     public ConnectionFactory getConnectionFactory() {
  341.         return connectionFactory;
  342.     }

  343.     /**
  344.      * Gets the transaction registry.
  345.      *
  346.      * @return The transaction registry.
  347.      */
  348.     @Override
  349.     public TransactionRegistry getTransactionRegistry() {
  350.         return transactionRegistry;
  351.     }

  352. }