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.util.Objects; 029import java.lang.ref.WeakReference; 030 031/** 032 * TransactionContext represents the association between a single XAConnectionFactory and a Transaction. This context 033 * contains a single shared connection which should be used by all ManagedConnections for the XAConnectionFactory, the 034 * ability to listen for the transaction completion event, and a method to check the status of the transaction. 035 * 036 * @since 2.0 037 */ 038public class TransactionContext { 039 private final TransactionRegistry transactionRegistry; 040 private final WeakReference<Transaction> transactionRef; 041 private Connection sharedConnection; 042 private boolean transactionComplete; 043 044 /** 045 * Creates a TransactionContext for the specified Transaction and TransactionRegistry. The TransactionRegistry is 046 * used to obtain the XAResource for the shared connection when it is enlisted in the transaction. 047 * 048 * @param transactionRegistry 049 * the TransactionRegistry used to obtain the XAResource for the shared connection 050 * @param transaction 051 * the transaction 052 */ 053 public TransactionContext(final TransactionRegistry transactionRegistry, final Transaction transaction) { 054 Objects.requireNonNull(transactionRegistry, "transactionRegistry is null"); 055 Objects.requireNonNull(transaction, "transaction is null"); 056 this.transactionRegistry = transactionRegistry; 057 this.transactionRef = new WeakReference<>(transaction); 058 this.transactionComplete = false; 059 } 060 061 /** 062 * Gets the connection shared by all ManagedConnections in the transaction. Specifically, connection using the same 063 * XAConnectionFactory from which the TransactionRegistry was obtained. 064 * 065 * @return the shared connection for this transaction 066 */ 067 public Connection getSharedConnection() { 068 return sharedConnection; 069 } 070 071 /** 072 * Sets the shared connection for this transaction. The shared connection is enlisted in the transaction. 073 * 074 * @param sharedConnection 075 * the shared connection 076 * @throws SQLException 077 * if a shared connection is already set, if XAResource for the connection could not be found in the 078 * transaction registry, or if there was a problem enlisting the connection in the transaction 079 */ 080 public void setSharedConnection(final Connection sharedConnection) throws SQLException { 081 if (this.sharedConnection != null) { 082 throw new IllegalStateException("A shared connection is already set"); 083 } 084 085 // This is the first use of the connection in this transaction, so we must 086 // enlist it in the transaction 087 final Transaction transaction = getTransaction(); 088 try { 089 final XAResource xaResource = transactionRegistry.getXAResource(sharedConnection); 090 if (!transaction.enlistResource(xaResource)) { 091 throw new SQLException("Unable to enlist connection in transaction: enlistResource returns 'false'."); 092 } 093 } catch (final IllegalStateException e) { 094 // This can happen if the transaction is already timed out 095 throw new SQLException("Unable to enlist connection in the transaction", e); 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 109 * the listener to add 110 * @throws SQLException 111 * if a problem occurs adding the listener to the transaction 112 */ 113 public void addTransactionContextListener(final TransactionContextListener listener) throws SQLException { 114 try { 115 getTransaction().registerSynchronization(new Synchronization() { 116 @Override 117 public void beforeCompletion() { 118 // empty 119 } 120 121 @Override 122 public void afterCompletion(final int status) { 123 listener.afterCompletion(TransactionContext.this, status == Status.STATUS_COMMITTED); 124 } 125 }); 126 } catch (final RollbackException e) { 127 // JTA spec doesn't let us register with a transaction marked rollback only 128 // just ignore this and the tx state will be cleared another way. 129 } catch (final Exception e) { 130 throw new SQLException("Unable to register transaction context listener", e); 131 } 132 } 133 134 /** 135 * True if the transaction is active or marked for rollback only. 136 * 137 * @return true if the transaction is active or marked for rollback only; false otherwise 138 * @throws SQLException 139 * if a problem occurs obtaining the transaction status 140 */ 141 public boolean isActive() throws SQLException { 142 try { 143 final Transaction transaction = this.transactionRef.get(); 144 if (transaction == null) { 145 return false; 146 } 147 final int status = transaction.getStatus(); 148 return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK; 149 } catch (final SystemException e) { 150 throw new SQLException("Unable to get transaction status", e); 151 } 152 } 153 154 private Transaction getTransaction() throws SQLException { 155 final Transaction transaction = this.transactionRef.get(); 156 if (transaction == null) { 157 throw new SQLException("Unable to enlist connection because the transaction has been garbage collected"); 158 } 159 return transaction; 160 } 161 162 /** 163 * Sets the transaction complete flag to true. 164 * 165 * @since 2.4.0 166 */ 167 public void completeTransaction() { 168 this.transactionComplete = true; 169 } 170 171 /** 172 * Gets the transaction complete flag to true. 173 * 174 * @return The transaction complete flag. 175 * 176 * @since 2.4.0 177 */ 178 public boolean isTransactionComplete() { 179 return this.transactionComplete; 180 } 181}