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