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 }