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 */
018 package org.apache.commons.dbcp.managed;
019
020 import javax.transaction.RollbackException;
021 import javax.transaction.Status;
022 import javax.transaction.Synchronization;
023 import javax.transaction.SystemException;
024 import javax.transaction.Transaction;
025 import javax.transaction.xa.XAResource;
026 import java.sql.Connection;
027 import java.sql.SQLException;
028 import java.lang.ref.WeakReference;
029
030 /**
031 * TransactionContext represents the association between a single XAConnectionFactory and a Transaction.
032 * This context contains a single shared connection which should be used by all ManagedConnections for
033 * the XAConnectionFactory, the ability to listen for the transaction completion event, and a method
034 * to check the status of the transaction.
035 *
036 * @author Dain Sundstrom
037 * @version $Revision$
038 */
039 public class TransactionContext {
040 private final TransactionRegistry transactionRegistry;
041 private final WeakReference transactionRef;
042 private Connection sharedConnection;
043
044 /**
045 * Creates a TransactionContext for the specified Transaction and TransactionRegistry. The
046 * TransactionRegistry is used to obtain the XAResource for the shared connection when it is
047 * enlisted in the transaction.
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(TransactionRegistry transactionRegistry, Transaction transaction) {
054 if (transactionRegistry == null) throw new NullPointerException("transactionRegistry is null");
055 if (transaction == null) throw new NullPointerException("transaction is null");
056 this.transactionRegistry = transactionRegistry;
057 this.transactionRef = new WeakReference(transaction);
058 }
059
060 /**
061 * Gets the connection shared by all ManagedConnections in the transaction. Specifically,
062 * connection using the same XAConnectionFactory from which the TransactionRegistry was
063 * obtained.
064 * @return the shared connection for this transaction
065 */
066 public Connection getSharedConnection() {
067 return sharedConnection;
068 }
069
070 /**
071 * Sets the shared connection for this transaction. The shared connection is enlisted
072 * in the transaction.
073 *
074 * @param sharedConnection the shared connection
075 * @throws SQLException if a shared connection is already set, if XAResource for the connection
076 * could not be found in the transaction registry, or if there was a problem enlisting the
077 * connection in the transaction
078 */
079 public void setSharedConnection(Connection sharedConnection) throws SQLException {
080 if (this.sharedConnection != null) {
081 throw new IllegalStateException("A shared connection is alredy set");
082 }
083
084 // This is the first use of the connection in this transaction, so we must
085 // enlist it in the transaction
086 Transaction transaction = getTransaction();
087 try {
088 XAResource xaResource = transactionRegistry.getXAResource(sharedConnection);
089 transaction.enlistResource(xaResource);
090 } catch (RollbackException e) {
091 // transaction was rolled back... proceed as if there never was a transaction
092 } catch (SystemException e) {
093 throw (SQLException) new SQLException("Unable to enlist connection the transaction").initCause(e);
094 }
095
096 this.sharedConnection = sharedConnection;
097 }
098
099 /**
100 * Adds a listener for transaction completion events.
101 *
102 * @param listener the listener to add
103 * @throws SQLException if a problem occurs adding the listener to the transaction
104 */
105 public void addTransactionContextListener(final TransactionContextListener listener) throws SQLException {
106 try {
107 getTransaction().registerSynchronization(new Synchronization() {
108 public void beforeCompletion() {
109 }
110
111 public void afterCompletion(int status) {
112 listener.afterCompletion(TransactionContext.this, status == Status.STATUS_COMMITTED);
113 }
114 });
115 } catch (RollbackException e) {
116 // JTA spec doesn't let us register with a transaction marked rollback only
117 // just ignore this and the tx state will be cleared another way.
118 } catch (Exception e) {
119 throw (SQLException) new SQLException("Unable to register transaction context listener").initCause(e);
120 }
121 }
122
123 /**
124 * True if the transaction is active or marked for rollback only.
125 * @return true if the transaction is active or marked for rollback only; false otherwise
126 * @throws SQLException if a problem occurs obtaining the transaction status
127 */
128 public boolean isActive() throws SQLException {
129 try {
130 Transaction transaction = (Transaction) this.transactionRef.get();
131 if (transaction == null) {
132 return false;
133 }
134 int status = transaction.getStatus();
135 return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK;
136 } catch (SystemException e) {
137 throw (SQLException) new SQLException("Unable to get transaction status").initCause(e);
138 }
139 }
140
141 private Transaction getTransaction() throws SQLException {
142 Transaction transaction = (Transaction) this.transactionRef.get();
143 if (transaction == null) {
144 throw new SQLException("Unable to enlist connection because the transaction has been garbage collected");
145 }
146 return transaction;
147 }
148 }