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