View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one or more
4    * contributor license agreements.  See the NOTICE file distributed with
5    * this work for additional information regarding copyright ownership.
6    * The ASF licenses this file to You under the Apache License, Version 2.0
7    * (the "License"); you may not use this file except in compliance with
8    * the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing, software
13   *  distributed under the License is distributed on an "AS IS" BASIS,
14   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *  See the License for the specific language governing permissions and
16   *  limitations under the License.
17   */
18  package org.apache.commons.dbcp.managed;
19  
20  import javax.transaction.RollbackException;
21  import javax.transaction.Status;
22  import javax.transaction.Synchronization;
23  import javax.transaction.SystemException;
24  import javax.transaction.Transaction;
25  import javax.transaction.xa.XAResource;
26  import java.sql.Connection;
27  import java.sql.SQLException;
28  import java.lang.ref.WeakReference;
29  
30  /**
31   * TransactionContext represents the association between a single XAConnectionFactory and a Transaction.
32   * This context contains a single shared connection which should be used by all ManagedConnections for
33   * the XAConnectionFactory, the ability to listen for the transaction completion event, and a method
34   * to check the status of the transaction.
35   *
36   * @author Dain Sundstrom
37   * @version $Revision$
38   */
39  public class TransactionContext {
40      private final TransactionRegistry transactionRegistry;
41      private final WeakReference transactionRef;
42      private Connection sharedConnection;
43  
44      /**
45       * Creates a TransactionContext for the specified Transaction and TransactionRegistry.  The
46       * TransactionRegistry is used to obtain the XAResource for the shared connection when it is
47       * enlisted in the transaction.
48       *
49       * @param transactionRegistry the TransactionRegistry used to obtain the XAResource for the
50       * shared connection
51       * @param transaction the transaction
52       */
53      public TransactionContext(TransactionRegistry transactionRegistry, Transaction transaction) {
54          if (transactionRegistry == null) throw new NullPointerException("transactionRegistry is null");
55          if (transaction == null) throw new NullPointerException("transaction is null");
56          this.transactionRegistry = transactionRegistry;
57          this.transactionRef = new WeakReference(transaction);
58      }
59  
60      /**
61       * Gets the connection shared by all ManagedConnections in the transaction.  Specifically,
62       * connection using the same XAConnectionFactory from which the TransactionRegistry was
63       * obtained.
64       * @return the shared connection for this transaction
65       */
66      public Connection getSharedConnection() {
67          return sharedConnection;
68      }
69  
70      /**
71       * Sets the shared connection for this transaction.  The shared connection is enlisted
72       * in the transaction.
73       *
74       * @param sharedConnection the shared connection
75       * @throws SQLException if a shared connection is already set, if XAResource for the connection
76       * could not be found in the transaction registry, or if there was a problem enlisting the
77       * connection in the transaction
78       */
79      public void setSharedConnection(Connection sharedConnection) throws SQLException {
80          if (this.sharedConnection != null) {
81              throw new IllegalStateException("A shared connection is alredy set");
82          }
83  
84          // This is the first use of the connection in this transaction, so we must
85          // enlist it in the transaction
86          Transaction transaction = getTransaction();
87          try {
88              XAResource xaResource = transactionRegistry.getXAResource(sharedConnection);
89              transaction.enlistResource(xaResource);
90          } catch (RollbackException e) {
91              // transaction was rolled back... proceed as if there never was a transaction
92          } catch (SystemException e) {
93              throw (SQLException) new SQLException("Unable to enlist connection the transaction").initCause(e);
94          }
95  
96          this.sharedConnection = sharedConnection;
97      }
98  
99      /**
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 }