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