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