001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.dbcp2.managed; 018 019import java.lang.ref.WeakReference; 020import java.sql.Connection; 021import java.sql.SQLException; 022import java.util.Objects; 023 024import javax.transaction.RollbackException; 025import javax.transaction.Status; 026import javax.transaction.Synchronization; 027import javax.transaction.SystemException; 028import javax.transaction.Transaction; 029import javax.transaction.TransactionSynchronizationRegistry; 030import javax.transaction.xa.XAResource; 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 * Provided for backwards compatibility 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 this (transactionRegistry, transaction, null); 055 } 056 057 /** 058 * Creates a TransactionContext for the specified Transaction and TransactionRegistry. The TransactionRegistry is 059 * used to obtain the XAResource for the shared connection when it is enlisted in the transaction. 060 * 061 * @param transactionRegistry 062 * the TransactionRegistry used to obtain the XAResource for the shared connection 063 * @param transaction 064 * the transaction 065 * @param transactionSynchronizationRegistry 066 * The optional TSR to register synchronizations with 067 * @since 2.6.0 068 */ 069 public TransactionContext(final TransactionRegistry transactionRegistry, final Transaction transaction, 070 final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { 071 Objects.requireNonNull(transactionRegistry, "transactionRegistry"); 072 Objects.requireNonNull(transaction, "transaction"); 073 this.transactionRegistry = transactionRegistry; 074 this.transactionRef = new WeakReference<>(transaction); 075 this.transactionComplete = false; 076 this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; 077 } 078 079 /** 080 * Adds a listener for transaction completion events. 081 * 082 * @param listener 083 * the listener to add 084 * @throws SQLException 085 * if a problem occurs adding the listener to the transaction 086 */ 087 public void addTransactionContextListener(final TransactionContextListener listener) throws SQLException { 088 try { 089 if (!isActive()) { 090 final Transaction transaction = this.transactionRef.get(); 091 listener.afterCompletion(this, transaction != null && transaction.getStatus() == Status.STATUS_COMMITTED); 092 return; 093 } 094 final Synchronization s = new SynchronizationAdapter() { 095 @Override 096 public void afterCompletion(final int status) { 097 listener.afterCompletion(TransactionContext.this, status == Status.STATUS_COMMITTED); 098 } 099 }; 100 if (transactionSynchronizationRegistry != null) { 101 transactionSynchronizationRegistry.registerInterposedSynchronization(s); 102 } else { 103 getTransaction().registerSynchronization(s); 104 } 105 } catch (final RollbackException ignored) { 106 // JTA spec doesn't let us register with a transaction marked rollback only 107 // just ignore this and the tx state will be cleared another way. 108 } catch (final Exception e) { 109 throw new SQLException("Unable to register transaction context listener", e); 110 } 111 } 112 113 /** 114 * Sets the transaction complete flag to true. 115 * 116 * @since 2.4.0 117 */ 118 public void completeTransaction() { 119 this.transactionComplete = true; 120 } 121 122 /** 123 * Gets the connection shared by all ManagedConnections in the transaction. Specifically, connection using the same 124 * XAConnectionFactory from which the TransactionRegistry was obtained. 125 * 126 * @return the shared connection for this transaction 127 */ 128 public Connection getSharedConnection() { 129 return sharedConnection; 130 } 131 132 private Transaction getTransaction() throws SQLException { 133 final Transaction transaction = this.transactionRef.get(); 134 if (transaction == null) { 135 throw new SQLException("Unable to enlist connection because the transaction has been garbage collected"); 136 } 137 return transaction; 138 } 139 140 /** 141 * True if the transaction is active or marked for rollback only. 142 * 143 * @return true if the transaction is active or marked for rollback only; false otherwise 144 * @throws SQLException 145 * if a problem occurs obtaining the transaction status 146 */ 147 public boolean isActive() throws SQLException { 148 try { 149 final Transaction transaction = this.transactionRef.get(); 150 if (transaction == null) { 151 return false; 152 } 153 final int status = transaction.getStatus(); 154 return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK; 155 } catch (final SystemException e) { 156 throw new SQLException("Unable to get transaction status", e); 157 } 158 } 159 160 /** 161 * Gets the transaction complete flag to true. 162 * 163 * @return The transaction complete flag. 164 * 165 * @since 2.4.0 166 */ 167 public boolean isTransactionComplete() { 168 return this.transactionComplete; 169 } 170 171 /** 172 * Sets the shared connection for this transaction. The shared connection is enlisted in the transaction. 173 * 174 * @param sharedConnection 175 * the shared connection 176 * @throws SQLException 177 * if a shared connection is already set, if XAResource for the connection could not be found in the 178 * transaction registry, or if there was a problem enlisting the connection in the transaction 179 */ 180 public void setSharedConnection(final Connection sharedConnection) throws SQLException { 181 if (this.sharedConnection != null) { 182 throw new IllegalStateException("A shared connection is already set"); 183 } 184 185 // This is the first use of the connection in this transaction, so we must 186 // enlist it in the transaction 187 final Transaction transaction = getTransaction(); 188 try { 189 final XAResource xaResource = transactionRegistry.getXAResource(sharedConnection); 190 if (!transaction.enlistResource(xaResource)) { 191 throw new SQLException("Unable to enlist connection in transaction: enlistResource returns 'false'."); 192 } 193 } catch (final IllegalStateException e) { 194 // This can happen if the transaction is already timed out 195 throw new SQLException("Unable to enlist connection in the transaction", e); 196 } catch (final RollbackException ignored) { 197 // transaction was rolled back... proceed as if there never was a transaction 198 } catch (final SystemException e) { 199 throw new SQLException("Unable to enlist connection the transaction", e); 200 } 201 202 this.sharedConnection = sharedConnection; 203 } 204}