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