/** * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.dbcp2.managed; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionSynchronizationRegistry; import javax.transaction.xa.XAResource; import java.sql.Connection; import java.sql.SQLException; import java.util.Objects; import java.lang.ref.WeakReference; /** * TransactionContext represents the association between a single XAConnectionFactory and a Transaction. This context * contains a single shared connection which should be used by all ManagedConnections for the XAConnectionFactory, the * ability to listen for the transaction completion event, and a method to check the status of the transaction. * * @since 2.0 */ public class TransactionContext { private final TransactionRegistry transactionRegistry; private final WeakReference<Transaction> transactionRef; private final TransactionSynchronizationRegistry transactionSynchronizationRegistry; private Connection sharedConnection; private boolean transactionComplete; /** * Creates a TransactionContext for the specified Transaction and TransactionRegistry. The TransactionRegistry is * used to obtain the XAResource for the shared connection when it is enlisted in the transaction. * * @param transactionRegistry * the TransactionRegistry used to obtain the XAResource for the shared connection * @param transaction * the transaction * @param transactionSynchronizationRegistry * The optional TSR to register synchronizations with * @since 2.6.0 */ public TransactionContext(final TransactionRegistry transactionRegistry, final Transaction transaction, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { Objects.requireNonNull(transactionRegistry, "transactionRegistry is null"); Objects.requireNonNull(transaction, "transaction is null"); this.transactionRegistry = transactionRegistry; this.transactionRef = new WeakReference<>(transaction); this.transactionComplete = false; this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; } /** * Provided for backwards compatibility * * @param transactionRegistry the TransactionRegistry used to obtain the XAResource for the * shared connection * @param transaction the transaction */ public TransactionContext(final TransactionRegistry transactionRegistry, final Transaction transaction) { this (transactionRegistry, transaction, null); } /** * Gets the connection shared by all ManagedConnections in the transaction. Specifically, connection using the same * XAConnectionFactory from which the TransactionRegistry was obtained. * * @return the shared connection for this transaction */ public Connection getSharedConnection() { return sharedConnection; } /** * Sets the shared connection for this transaction. The shared connection is enlisted in the transaction. * * @param sharedConnection * the shared connection * @throws SQLException * if a shared connection is already set, if XAResource for the connection could not be found in the * transaction registry, or if there was a problem enlisting the connection in the transaction */ public void setSharedConnection(final Connection sharedConnection) throws SQLException { if (this.sharedConnection != null) { throw new IllegalStateException("A shared connection is already set"); } // This is the first use of the connection in this transaction, so we must // enlist it in the transaction final Transaction transaction = getTransaction(); try { final XAResource xaResource = transactionRegistry.getXAResource(sharedConnection); if (!transaction.enlistResource(xaResource)) { throw new SQLException("Unable to enlist connection in transaction: enlistResource returns 'false'."); } } catch (final IllegalStateException e) { // This can happen if the transaction is already timed out throw new SQLException("Unable to enlist connection in the transaction", e); } catch (final RollbackException e) { // transaction was rolled back... proceed as if there never was a transaction } catch (final SystemException e) { throw new SQLException("Unable to enlist connection the transaction", e); } this.sharedConnection = sharedConnection; } /** * Adds a listener for transaction completion events. * * @param listener * the listener to add * @throws SQLException * if a problem occurs adding the listener to the transaction */ public void addTransactionContextListener(final TransactionContextListener listener) throws SQLException { try { if (!isActive()) { final Transaction transaction = this.transactionRef.get(); listener.afterCompletion(TransactionContext.this, transaction == null ? false : transaction.getStatus() == Status.STATUS_COMMITTED); return; } final Synchronization s = new Synchronization() { @Override public void beforeCompletion() { // empty } @Override public void afterCompletion(final int status) { listener.afterCompletion(TransactionContext.this, status == Status.STATUS_COMMITTED); } }; if (transactionSynchronizationRegistry != null) { transactionSynchronizationRegistry.registerInterposedSynchronization(s); } else { getTransaction().registerSynchronization(s); } } catch (final RollbackException e) { // JTA spec doesn't let us register with a transaction marked rollback only // just ignore this and the tx state will be cleared another way. } catch (final Exception e) { throw new SQLException("Unable to register transaction context listener", e); } } /** * True if the transaction is active or marked for rollback only. * * @return true if the transaction is active or marked for rollback only; false otherwise * @throws SQLException * if a problem occurs obtaining the transaction status */ public boolean isActive() throws SQLException { try { final Transaction transaction = this.transactionRef.get(); if (transaction == null) { return false; } final int status = transaction.getStatus(); return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK; } catch (final SystemException e) { throw new SQLException("Unable to get transaction status", e); } } private Transaction getTransaction() throws SQLException { final Transaction transaction = this.transactionRef.get(); if (transaction == null) { throw new SQLException("Unable to enlist connection because the transaction has been garbage collected"); } return transaction; } /** * Sets the transaction complete flag to true. * * @since 2.4.0 */ public void completeTransaction() { this.transactionComplete = true; } /** * Gets the transaction complete flag to true. * * @return The transaction complete flag. * * @since 2.4.0 */ public boolean isTransactionComplete() { return this.transactionComplete; } }