View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one or more
4    * contributor license agreements.  See the NOTICE file distributed with
5    * this work for additional information regarding copyright ownership.
6    * The ASF licenses this file to You under the Apache License, Version 2.0
7    * (the "License"); you may not use this file except in compliance with
8    * the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing, software
13   *  distributed under the License is distributed on an "AS IS" BASIS,
14   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *  See the License for the specific language governing permissions and
16   *  limitations under the License.
17   */
18  package org.apache.commons.dbcp.managed;
19  
20  import org.apache.commons.dbcp.ConnectionFactory;
21  
22  import javax.transaction.TransactionManager;
23  import javax.transaction.xa.XAException;
24  import javax.transaction.xa.XAResource;
25  import javax.transaction.xa.Xid;
26  import java.sql.Connection;
27  import java.sql.SQLException;
28  
29  /**
30   * An implementation of XAConnectionFactory which manages non-XA connections in XA transactions.  A non-XA connection
31   * commits and rolls back as part of the XA transaction, but is not recoverable since the connection does not implement
32   * the 2-phase protocol.
33   *
34   * @author Dain Sundstrom
35   * @version $Revision$
36   */
37  public class LocalXAConnectionFactory implements XAConnectionFactory {
38      protected TransactionRegistry transactionRegistry;
39      protected ConnectionFactory connectionFactory;
40  
41      /**
42       * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database
43       * connections.  The connections are enlisted into transactions using the specified transaction manager.
44       *
45       * @param transactionManager the transaction manager in which connections will be enlisted
46       * @param connectionFactory  the connection factory from which connections will be retrieved
47       */
48      public LocalXAConnectionFactory(TransactionManager transactionManager, ConnectionFactory connectionFactory) {
49          if (transactionManager == null) throw new NullPointerException("transactionManager is null");
50          if (connectionFactory == null) throw new NullPointerException("connectionFactory is null");
51  
52          this.transactionRegistry = new TransactionRegistry(transactionManager);
53          this.connectionFactory = connectionFactory;
54      }
55  
56      public TransactionRegistry getTransactionRegistry() {
57          return transactionRegistry;
58      }
59  
60      public Connection createConnection() throws SQLException {
61          // create a new connection
62          Connection connection = connectionFactory.createConnection();
63  
64          // create a XAResource to manage the connection during XA transactions
65          XAResource xaResource = new LocalXAResource(connection);
66  
67          // register the xa resource for the connection
68          transactionRegistry.registerConnection(connection, xaResource);
69  
70          return connection;
71      }
72  
73      /**
74       * LocalXAResource is a fake XAResource for non-XA connections.  When a transaction is started
75       * the connection auto-commit is turned off.  When the connection is committed or rolled back,
76       * the commit or rollback method is called on the connection and then the original auto-commit
77       * value is restored.
78       * </p>
79       * The LocalXAResource also respects the connection read-only setting.  If the connection is
80       * read-only the commit method will not be called, and the prepare method returns the XA_RDONLY.
81       * </p>
82       * It is assumed that the wrapper around a managed connection disables the setAutoCommit(),
83       * commit(), rollback() and setReadOnly() methods while a transaction is in progress.
84       */
85      protected static class LocalXAResource implements XAResource {
86          private final Connection connection;
87          private Xid currentXid;
88          private boolean originalAutoCommit;
89  
90          public LocalXAResource(Connection localTransaction) {
91              this.connection = localTransaction;
92          }
93  
94          /**
95           * Gets the current xid of the transaction branch associated with this XAResource.
96           *
97           * @return the current xid of the transaction branch associated with this XAResource.
98           */
99          public synchronized Xid getXid() {
100             return currentXid;
101         }
102 
103         /**
104          * Signals that a the connection has been enrolled in a transaction.  This method saves off the
105          * current auto commit flag, and then disables auto commit.  The original auto commit setting is
106          * restored when the transaction completes.
107          *
108          * @param xid  the id of the transaction branch for this connection
109          * @param flag either XAResource.TMNOFLAGS or XAResource.TMRESUME
110          * @throws XAException if the connection is already enlisted in another transaction, or if auto-commit
111          *                     could not be disabled
112          */
113         public synchronized void start(Xid xid, int flag) throws XAException {
114             if (flag == XAResource.TMNOFLAGS) {
115                 // first time in this transaction
116 
117                 // make sure we aren't already in another tx
118                 if (this.currentXid != null) {
119                     throw new XAException("Already enlisted in another transaction with xid " + xid);
120                 }
121 
122                 // save off the current auto commit flag so it can be restored after the transaction completes
123                 try {
124                     originalAutoCommit = connection.getAutoCommit();
125                 } catch (SQLException ignored) {
126                     // no big deal, just assume it was off
127                     originalAutoCommit = true;
128                 }
129 
130                 // update the auto commit flag
131                 try {
132                     connection.setAutoCommit(false);
133                 } catch (SQLException e) {
134                     throw (XAException) new XAException("Count not turn off auto commit for a XA transaction").initCause(e);
135                 }
136 
137                 this.currentXid = xid;
138             } else if (flag == XAResource.TMRESUME) {
139                 if (xid != this.currentXid) {
140                     throw new XAException("Attempting to resume in different transaction: expected " + this.currentXid + ", but was " + xid);
141                 }
142             } else {
143                 throw new XAException("Unknown start flag " + flag);
144             }
145         }
146 
147         /**
148          * This method does nothing.
149          *
150          * @param xid  the id of the transaction branch for this connection
151          * @param flag ignored
152          * @throws XAException if the connection is already enlisted in another transaction
153          */
154         public synchronized void end(Xid xid, int flag) throws XAException {
155             if (xid == null) throw new NullPointerException("xid is null");
156             if (!this.currentXid.equals(xid)) throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid);
157 
158             // This notification tells us that the application server is done using this
159             // connection for the time being.  The connection is still associated with an
160             // open transaction, so we must still wait for the commit or rollback method
161         }
162 
163         /**
164          * This method does nothing since the LocalXAConnection does not support two-phase-commit.  This method
165          * will return XAResource.XA_RDONLY if the connection isReadOnly().  This assumes that the physical
166          * connection is wrapped with a proxy that prevents an application from changing the read-only flag
167          * while enrolled in a transaction.
168          *
169          * @param xid the id of the transaction branch for this connection
170          * @return XAResource.XA_RDONLY if the connection.isReadOnly(); XAResource.XA_OK otherwise
171          */
172         public synchronized int prepare(Xid xid) {
173             // if the connection is read-only, then the resource is read-only
174             // NOTE: this assumes that the outer proxy throws an exception when application code
175             // attempts to set this in a transaction
176             try {
177                 if (connection.isReadOnly()) {
178                     // update the auto commit flag
179                     connection.setAutoCommit(originalAutoCommit);
180 
181                     // tell the transaction manager we are read only
182                     return XAResource.XA_RDONLY;
183                 }
184             } catch (SQLException ignored) {
185                 // no big deal
186             }
187 
188             // this is a local (one phase) only connection, so we can't prepare
189             return XAResource.XA_OK;
190         }
191 
192         /**
193          * Commits the transaction and restores the original auto commit setting.
194          *
195          * @param xid  the id of the transaction branch for this connection
196          * @param flag ignored
197          * @throws XAException if connection.commit() throws a SQLException
198          */
199         public synchronized void commit(Xid xid, boolean flag) throws XAException {
200             if (xid == null) throw new NullPointerException("xid is null");
201             if (!this.currentXid.equals(xid)) throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid);
202 
203             try {
204                 // make sure the connection isn't already closed
205                 if (connection.isClosed()) {
206                     throw new XAException("Conection is closed");
207                 }
208 
209                 // A read only connection should not be committed
210                 if (!connection.isReadOnly()) {
211                     connection.commit();
212                 }
213             } catch (SQLException e) {
214                 throw (XAException) new XAException().initCause(e);
215             } finally {
216                 try {
217                     connection.setAutoCommit(originalAutoCommit);
218                 } catch (SQLException e) {
219                 }
220                 this.currentXid = null;
221             }
222         }
223 
224         /**
225          * Rolls back the transaction and restores the original auto commit setting.
226          *
227          * @param xid the id of the transaction branch for this connection
228          * @throws XAException if connection.rollback() throws a SQLException
229          */
230         public synchronized void rollback(Xid xid) throws XAException {
231             if (xid == null) throw new NullPointerException("xid is null");
232             if (!this.currentXid.equals(xid)) throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid);
233 
234             try {
235                 connection.rollback();
236             } catch (SQLException e) {
237                 throw (XAException) new XAException().initCause(e);
238             } finally {
239                 try {
240                     connection.setAutoCommit(originalAutoCommit);
241                 } catch (SQLException e) {
242                 }
243                 this.currentXid = null;
244             }
245         }
246 
247         /**
248          * Returns true if the specified XAResource == this XAResource.
249          *
250          * @param xaResource the XAResource to test
251          * @return true if the specified XAResource == this XAResource; false otherwise
252          */
253         public boolean isSameRM(XAResource xaResource) {
254             return this == xaResource;
255         }
256 
257         /**
258          * Clears the currently associated transaction if it is the specified xid.
259          *
260          * @param xid the id of the transaction to forget
261          */
262         public synchronized void forget(Xid xid) {
263             if (xid != null && this.currentXid.equals(xid)) {
264                 this.currentXid = null;
265             }
266         }
267 
268         /**
269          * Always returns a zero length Xid array.  The LocalXAConnectionFactory can not support recovery, so no xids will ever be found.
270          *
271          * @param flag ignored since recovery is not supported
272          * @return always a zero length Xid array.
273          */
274         public Xid[] recover(int flag) {
275             return new Xid[0];
276         }
277 
278         /**
279          * Always returns 0 since we have no way to set a transaction timeout on a JDBC connection.
280          *
281          * @return always 0
282          */
283         public int getTransactionTimeout() {
284             return 0;
285         }
286 
287         /**
288          * Always returns false since we have no way to set a transaction timeout on a JDBC connection.
289          *
290          * @param transactionTimeout ignored since we have no way to set a transaction timeout on a JDBC connection
291          * @return always false
292          */
293         public boolean setTransactionTimeout(int transactionTimeout) {
294             return false;
295         }
296     }
297 
298 }