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 javax.transaction.RollbackException; 021 import javax.transaction.Status; 022 import javax.transaction.Synchronization; 023 import javax.transaction.SystemException; 024 import javax.transaction.Transaction; 025 import javax.transaction.xa.XAResource; 026 import java.sql.Connection; 027 import java.sql.SQLException; 028 import java.lang.ref.WeakReference; 029 030 /** 031 * TransactionContext represents the association between a single XAConnectionFactory and a Transaction. 032 * This context contains a single shared connection which should be used by all ManagedConnections for 033 * the XAConnectionFactory, the ability to listen for the transaction completion event, and a method 034 * to check the status of the transaction. 035 * 036 * @author Dain Sundstrom 037 * @version $Revision: 892307 $ 038 */ 039 public class TransactionContext { 040 private final TransactionRegistry transactionRegistry; 041 private final WeakReference transactionRef; 042 private Connection sharedConnection; 043 044 /** 045 * Creates a TransactionContext for the specified Transaction and TransactionRegistry. The 046 * TransactionRegistry is used to obtain the XAResource for the shared connection when it is 047 * enlisted in the transaction. 048 * 049 * @param transactionRegistry the TransactionRegistry used to obtain the XAResource for the 050 * shared connection 051 * @param transaction the transaction 052 */ 053 public TransactionContext(TransactionRegistry transactionRegistry, Transaction transaction) { 054 if (transactionRegistry == null) throw new NullPointerException("transactionRegistry is null"); 055 if (transaction == null) throw new NullPointerException("transaction is null"); 056 this.transactionRegistry = transactionRegistry; 057 this.transactionRef = new WeakReference(transaction); 058 } 059 060 /** 061 * Gets the connection shared by all ManagedConnections in the transaction. Specifically, 062 * connection using the same XAConnectionFactory from which the TransactionRegistry was 063 * obtained. 064 * @return the shared connection for this transaction 065 */ 066 public Connection getSharedConnection() { 067 return sharedConnection; 068 } 069 070 /** 071 * Sets the shared connection for this transaction. The shared connection is enlisted 072 * in the transaction. 073 * 074 * @param sharedConnection the shared connection 075 * @throws SQLException if a shared connection is already set, if XAResource for the connection 076 * could not be found in the transaction registry, or if there was a problem enlisting the 077 * connection in the transaction 078 */ 079 public void setSharedConnection(Connection sharedConnection) throws SQLException { 080 if (this.sharedConnection != null) { 081 throw new IllegalStateException("A shared connection is alredy set"); 082 } 083 084 // This is the first use of the connection in this transaction, so we must 085 // enlist it in the transaction 086 Transaction transaction = getTransaction(); 087 try { 088 XAResource xaResource = transactionRegistry.getXAResource(sharedConnection); 089 transaction.enlistResource(xaResource); 090 } catch (RollbackException e) { 091 // transaction was rolled back... proceed as if there never was a transaction 092 } catch (SystemException e) { 093 throw (SQLException) new SQLException("Unable to enlist connection the transaction").initCause(e); 094 } 095 096 this.sharedConnection = sharedConnection; 097 } 098 099 /** 100 * Adds a listener for transaction completion events. 101 * 102 * @param listener the listener to add 103 * @throws SQLException if a problem occurs adding the listener to the transaction 104 */ 105 public void addTransactionContextListener(final TransactionContextListener listener) throws SQLException { 106 try { 107 getTransaction().registerSynchronization(new Synchronization() { 108 public void beforeCompletion() { 109 } 110 111 public void afterCompletion(int status) { 112 listener.afterCompletion(TransactionContext.this, status == Status.STATUS_COMMITTED); 113 } 114 }); 115 } catch (RollbackException e) { 116 // JTA spec doesn't let us register with a transaction marked rollback only 117 // just ignore this and the tx state will be cleared another way. 118 } catch (Exception e) { 119 throw (SQLException) new SQLException("Unable to register transaction context listener").initCause(e); 120 } 121 } 122 123 /** 124 * True if the transaction is active or marked for rollback only. 125 * @return true if the transaction is active or marked for rollback only; false otherwise 126 * @throws SQLException if a problem occurs obtaining the transaction status 127 */ 128 public boolean isActive() throws SQLException { 129 try { 130 Transaction transaction = (Transaction) this.transactionRef.get(); 131 if (transaction == null) { 132 return false; 133 } 134 int status = transaction.getStatus(); 135 return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK; 136 } catch (SystemException e) { 137 throw (SQLException) new SQLException("Unable to get transaction status").initCause(e); 138 } 139 } 140 141 private Transaction getTransaction() throws SQLException { 142 Transaction transaction = (Transaction) this.transactionRef.get(); 143 if (transaction == null) { 144 throw new SQLException("Unable to enlist connection because the transaction has been garbage collected"); 145 } 146 return transaction; 147 } 148 }