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 */ 018package org.apache.commons.dbcp2.managed; 019 020import org.apache.commons.dbcp2.ConnectionFactory; 021 022import javax.transaction.TransactionManager; 023import javax.transaction.xa.XAException; 024import javax.transaction.xa.XAResource; 025import javax.transaction.xa.Xid; 026import java.sql.Connection; 027import 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 $Id: LocalXAConnectionFactory.java 1660791 2015-02-19 04:13:10Z psteitz $ 036 * @since 2.0 037 */ 038public class LocalXAConnectionFactory implements XAConnectionFactory { 039 private final TransactionRegistry transactionRegistry; 040 private final ConnectionFactory connectionFactory; 041 042 /** 043 * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database 044 * connections. The connections are enlisted into transactions using the specified transaction manager. 045 * 046 * @param transactionManager the transaction manager in which connections will be enlisted 047 * @param connectionFactory the connection factory from which connections will be retrieved 048 */ 049 public LocalXAConnectionFactory(TransactionManager transactionManager, ConnectionFactory connectionFactory) { 050 if (transactionManager == null) { 051 throw new NullPointerException("transactionManager is null"); 052 } 053 if (connectionFactory == null) { 054 throw new NullPointerException("connectionFactory is null"); 055 } 056 057 this.transactionRegistry = new TransactionRegistry(transactionManager); 058 this.connectionFactory = connectionFactory; 059 } 060 061 @Override 062 public TransactionRegistry getTransactionRegistry() { 063 return transactionRegistry; 064 } 065 066 @Override 067 public Connection createConnection() throws SQLException { 068 // create a new connection 069 Connection connection = connectionFactory.createConnection(); 070 071 // create a XAResource to manage the connection during XA transactions 072 XAResource xaResource = new LocalXAResource(connection); 073 074 // register the xa resource for the connection 075 transactionRegistry.registerConnection(connection, xaResource); 076 077 return connection; 078 } 079 080 /** 081 * LocalXAResource is a fake XAResource for non-XA connections. When a transaction is started 082 * the connection auto-commit is turned off. When the connection is committed or rolled back, 083 * the commit or rollback method is called on the connection and then the original auto-commit 084 * value is restored. 085 * <p> 086 * The LocalXAResource also respects the connection read-only setting. If the connection is 087 * read-only the commit method will not be called, and the prepare method returns the XA_RDONLY. 088 * </p> 089 * It is assumed that the wrapper around a managed connection disables the setAutoCommit(), 090 * commit(), rollback() and setReadOnly() methods while a transaction is in progress. 091 * @since 2.0 092 */ 093 protected static class LocalXAResource implements XAResource { 094 private final Connection connection; 095 private Xid currentXid; // @GuardedBy("this") 096 private boolean originalAutoCommit; // @GuardedBy("this") 097 098 public LocalXAResource(Connection localTransaction) { 099 this.connection = localTransaction; 100 } 101 102 /** 103 * Gets the current xid of the transaction branch associated with this XAResource. 104 * 105 * @return the current xid of the transaction branch associated with this XAResource. 106 */ 107 public synchronized Xid getXid() { 108 return currentXid; 109 } 110 111 /** 112 * Signals that a the connection has been enrolled in a transaction. This method saves off the 113 * current auto commit flag, and then disables auto commit. The original auto commit setting is 114 * restored when the transaction completes. 115 * 116 * @param xid the id of the transaction branch for this connection 117 * @param flag either XAResource.TMNOFLAGS or XAResource.TMRESUME 118 * @throws XAException if the connection is already enlisted in another transaction, or if auto-commit 119 * could not be disabled 120 */ 121 @Override 122 public synchronized void start(Xid xid, int flag) throws XAException { 123 if (flag == XAResource.TMNOFLAGS) { 124 // first time in this transaction 125 126 // make sure we aren't already in another tx 127 if (this.currentXid != null) { 128 throw new XAException("Already enlisted in another transaction with xid " + xid); 129 } 130 131 // save off the current auto commit flag so it can be restored after the transaction completes 132 try { 133 originalAutoCommit = connection.getAutoCommit(); 134 } catch (SQLException ignored) { 135 // no big deal, just assume it was off 136 originalAutoCommit = true; 137 } 138 139 // update the auto commit flag 140 try { 141 connection.setAutoCommit(false); 142 } catch (SQLException e) { 143 throw (XAException) new XAException("Count not turn off auto commit for a XA transaction").initCause(e); 144 } 145 146 this.currentXid = xid; 147 } else if (flag == XAResource.TMRESUME) { 148 if (!xid.equals(this.currentXid)) { 149 throw new XAException("Attempting to resume in different transaction: expected " + this.currentXid + ", but was " + xid); 150 } 151 } else { 152 throw new XAException("Unknown start flag " + flag); 153 } 154 } 155 156 /** 157 * This method does nothing. 158 * 159 * @param xid the id of the transaction branch for this connection 160 * @param flag ignored 161 * @throws XAException if the connection is already enlisted in another transaction 162 */ 163 @Override 164 public synchronized void end(Xid xid, int flag) throws XAException { 165 if (xid == null) { 166 throw new NullPointerException("xid is null"); 167 } 168 if (!this.currentXid.equals(xid)) { 169 throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid); 170 } 171 172 // This notification tells us that the application server is done using this 173 // connection for the time being. The connection is still associated with an 174 // open transaction, so we must still wait for the commit or rollback method 175 } 176 177 /** 178 * This method does nothing since the LocalXAConnection does not support two-phase-commit. This method 179 * will return XAResource.XA_RDONLY if the connection isReadOnly(). This assumes that the physical 180 * connection is wrapped with a proxy that prevents an application from changing the read-only flag 181 * while enrolled in a transaction. 182 * 183 * @param xid the id of the transaction branch for this connection 184 * @return XAResource.XA_RDONLY if the connection.isReadOnly(); XAResource.XA_OK otherwise 185 */ 186 @Override 187 public synchronized int prepare(Xid xid) { 188 // if the connection is read-only, then the resource is read-only 189 // NOTE: this assumes that the outer proxy throws an exception when application code 190 // attempts to set this in a transaction 191 try { 192 if (connection.isReadOnly()) { 193 // update the auto commit flag 194 connection.setAutoCommit(originalAutoCommit); 195 196 // tell the transaction manager we are read only 197 return XAResource.XA_RDONLY; 198 } 199 } catch (SQLException ignored) { 200 // no big deal 201 } 202 203 // this is a local (one phase) only connection, so we can't prepare 204 return XAResource.XA_OK; 205 } 206 207 /** 208 * Commits the transaction and restores the original auto commit setting. 209 * 210 * @param xid the id of the transaction branch for this connection 211 * @param flag ignored 212 * @throws XAException if connection.commit() throws a SQLException 213 */ 214 @Override 215 public synchronized void commit(Xid xid, boolean flag) throws XAException { 216 if (xid == null) { 217 throw new NullPointerException("xid is null"); 218 } 219 if (this.currentXid == null) { 220 throw new XAException("There is no current transaction"); 221 } 222 if (!this.currentXid.equals(xid)) { 223 throw new XAException("Invalid Xid: expected " + 224 this.currentXid + ", but was " + xid); 225 } 226 227 try { 228 // make sure the connection isn't already closed 229 if (connection.isClosed()) { 230 throw new XAException("Conection is closed"); 231 } 232 233 // A read only connection should not be committed 234 if (!connection.isReadOnly()) { 235 connection.commit(); 236 } 237 } catch (SQLException e) { 238 throw (XAException) new XAException().initCause(e); 239 } finally { 240 try { 241 connection.setAutoCommit(originalAutoCommit); 242 } catch (SQLException e) { 243 } 244 this.currentXid = null; 245 } 246 } 247 248 /** 249 * Rolls back the transaction and restores the original auto commit setting. 250 * 251 * @param xid the id of the transaction branch for this connection 252 * @throws XAException if connection.rollback() throws a SQLException 253 */ 254 @Override 255 public synchronized void rollback(Xid xid) throws XAException { 256 if (xid == null) { 257 throw new NullPointerException("xid is null"); 258 } 259 if (!this.currentXid.equals(xid)) { 260 throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid); 261 } 262 263 try { 264 connection.rollback(); 265 } catch (SQLException e) { 266 throw (XAException) new XAException().initCause(e); 267 } finally { 268 try { 269 connection.setAutoCommit(originalAutoCommit); 270 } catch (SQLException e) { 271 } 272 this.currentXid = null; 273 } 274 } 275 276 /** 277 * Returns true if the specified XAResource == this XAResource. 278 * 279 * @param xaResource the XAResource to test 280 * @return true if the specified XAResource == this XAResource; false otherwise 281 */ 282 @Override 283 public boolean isSameRM(XAResource xaResource) { 284 return this == xaResource; 285 } 286 287 /** 288 * Clears the currently associated transaction if it is the specified xid. 289 * 290 * @param xid the id of the transaction to forget 291 */ 292 @Override 293 public synchronized void forget(Xid xid) { 294 if (xid != null && xid.equals(currentXid)) { 295 currentXid = null; 296 } 297 } 298 299 /** 300 * Always returns a zero length Xid array. The LocalXAConnectionFactory can not support recovery, so no xids will ever be found. 301 * 302 * @param flag ignored since recovery is not supported 303 * @return always a zero length Xid array. 304 */ 305 @Override 306 public Xid[] recover(int flag) { 307 return new Xid[0]; 308 } 309 310 /** 311 * Always returns 0 since we have no way to set a transaction timeout on a JDBC connection. 312 * 313 * @return always 0 314 */ 315 @Override 316 public int getTransactionTimeout() { 317 return 0; 318 } 319 320 /** 321 * Always returns false since we have no way to set a transaction timeout on a JDBC connection. 322 * 323 * @param transactionTimeout ignored since we have no way to set a transaction timeout on a JDBC connection 324 * @return always false 325 */ 326 @Override 327 public boolean setTransactionTimeout(int transactionTimeout) { 328 return false; 329 } 330 } 331 332}