001 /**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one or more
004 * contributor license agreements. See the NOTICE file distributed with
005 * this work for additional information regarding copyright ownership.
006 * The ASF licenses this file to You under the Apache License, Version 2.0
007 * (the "License"); you may not use this file except in compliance with
008 * the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package org.apache.commons.dbcp.managed;
019
020 import org.apache.commons.dbcp.ConnectionFactory;
021
022 import javax.transaction.TransactionManager;
023 import javax.transaction.xa.XAException;
024 import javax.transaction.xa.XAResource;
025 import javax.transaction.xa.Xid;
026 import java.sql.Connection;
027 import java.sql.SQLException;
028
029 /**
030 * An implementation of XAConnectionFactory which manages non-XA connections in XA transactions. A non-XA connection
031 * commits and rolls back as part of the XA transaction, but is not recoverable since the connection does not implement
032 * the 2-phase protocol.
033 *
034 * @author Dain Sundstrom
035 * @version $Revision$
036 */
037 public class LocalXAConnectionFactory implements XAConnectionFactory {
038 protected TransactionRegistry transactionRegistry;
039 protected ConnectionFactory connectionFactory;
040
041 /**
042 * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database
043 * connections. The connections are enlisted into transactions using the specified transaction manager.
044 *
045 * @param transactionManager the transaction manager in which connections will be enlisted
046 * @param connectionFactory the connection factory from which connections will be retrieved
047 */
048 public LocalXAConnectionFactory(TransactionManager transactionManager, ConnectionFactory connectionFactory) {
049 if (transactionManager == null) throw new NullPointerException("transactionManager is null");
050 if (connectionFactory == null) throw new NullPointerException("connectionFactory is null");
051
052 this.transactionRegistry = new TransactionRegistry(transactionManager);
053 this.connectionFactory = connectionFactory;
054 }
055
056 public TransactionRegistry getTransactionRegistry() {
057 return transactionRegistry;
058 }
059
060 public Connection createConnection() throws SQLException {
061 // create a new connection
062 Connection connection = connectionFactory.createConnection();
063
064 // create a XAResource to manage the connection during XA transactions
065 XAResource xaResource = new LocalXAResource(connection);
066
067 // register the xa resource for the connection
068 transactionRegistry.registerConnection(connection, xaResource);
069
070 return connection;
071 }
072
073 /**
074 * LocalXAResource is a fake XAResource for non-XA connections. When a transaction is started
075 * the connection auto-commit is turned off. When the connection is committed or rolled back,
076 * the commit or rollback method is called on the connection and then the original auto-commit
077 * value is restored.
078 * </p>
079 * The LocalXAResource also respects the connection read-only setting. If the connection is
080 * read-only the commit method will not be called, and the prepare method returns the XA_RDONLY.
081 * </p>
082 * It is assumed that the wrapper around a managed connection disables the setAutoCommit(),
083 * commit(), rollback() and setReadOnly() methods while a transaction is in progress.
084 */
085 protected static class LocalXAResource implements XAResource {
086 private final Connection connection;
087 private Xid currentXid;
088 private boolean originalAutoCommit;
089
090 public LocalXAResource(Connection localTransaction) {
091 this.connection = localTransaction;
092 }
093
094 /**
095 * Gets the current xid of the transaction branch associated with this XAResource.
096 *
097 * @return the current xid of the transaction branch associated with this XAResource.
098 */
099 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 }