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 }