View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.dbcp2.managed;
18  
19  import java.sql.Connection;
20  import java.sql.SQLException;
21  import java.util.Map;
22  import java.util.Objects;
23  import java.util.WeakHashMap;
24  
25  import javax.transaction.SystemException;
26  import javax.transaction.Transaction;
27  import javax.transaction.TransactionManager;
28  import javax.transaction.TransactionSynchronizationRegistry;
29  import javax.transaction.xa.XAResource;
30  
31  import org.apache.commons.dbcp2.DelegatingConnection;
32  
33  /**
34   * TransactionRegistry tracks Connections and XAResources in a transacted environment for a single XAConnectionFactory.
35   * <p>
36   * The TransactionRegistry hides the details of transaction processing from the existing DBCP pooling code, and gives
37   * the ManagedConnection a way to enlist connections in a transaction, allowing for the maximal rescue of DBCP.
38   * </p>
39   *
40   * @since 2.0
41   */
42  public class TransactionRegistry {
43      private final TransactionManager transactionManager;
44      private final Map<Transaction, TransactionContext> caches = new WeakHashMap<>();
45      private final Map<Connection, XAResource> xaResources = new WeakHashMap<>();
46      private final TransactionSynchronizationRegistry transactionSynchronizationRegistry;
47  
48      /**
49       * Provided for backwards compatibility
50       * @param transactionManager the transaction manager used to enlist connections
51       */
52      public TransactionRegistry(final TransactionManager transactionManager) {
53          this (transactionManager, null);
54      }
55  
56      /**
57       * Creates a TransactionRegistry for the specified transaction manager.
58       *
59       * @param transactionManager
60       *            the transaction manager used to enlist connections.
61       * @param transactionSynchronizationRegistry
62       *              The optional TSR to register synchronizations with
63       * @since 2.6.0
64       */
65      public TransactionRegistry(final TransactionManager transactionManager, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) {
66          this.transactionManager = transactionManager;
67          this.transactionSynchronizationRegistry = transactionSynchronizationRegistry;
68      }
69  
70      /**
71       * Gets the active TransactionContext or null if not Transaction is active.
72       *
73       * @return The active TransactionContext or null if no Transaction is active.
74       * @throws SQLException
75       *             Thrown when an error occurs while fetching the transaction.
76       */
77      public TransactionContext getActiveTransactionContext() throws SQLException {
78          Transaction transaction = null;
79          try {
80              transaction = transactionManager.getTransaction();
81  
82              // was there a transaction?
83              if (transaction == null) {
84                  return null;
85              }
86  
87              // This is the transaction on the thread so no need to check its status - we should try to use it and
88              // fail later based on the subsequent status
89          } catch (final SystemException e) {
90              throw new SQLException("Unable to determine current transaction ", e);
91          }
92  
93          // register the context (or create a new one)
94          synchronized (this) {
95              return caches.computeIfAbsent(transaction, k -> new TransactionContext(this, k, transactionSynchronizationRegistry));
96          }
97      }
98  
99      private Connection getConnectionKey(final Connection connection) {
100         final Connection result;
101         if (connection instanceof DelegatingConnection) {
102             result = ((DelegatingConnection<?>) connection).getInnermostDelegateInternal();
103         } else {
104             result = connection;
105         }
106         return result;
107     }
108 
109     /**
110      * Gets the XAResource registered for the connection.
111      *
112      * @param connection
113      *            the connection
114      * @return The XAResource registered for the connection; never null.
115      * @throws SQLException
116      *             Thrown when the connection does not have a registered XAResource.
117      */
118     public synchronized XAResource getXAResource(final Connection connection) throws SQLException {
119         Objects.requireNonNull(connection, "connection");
120         final Connection key = getConnectionKey(connection);
121         final XAResource xaResource = xaResources.get(key);
122         if (xaResource == null) {
123             throw new SQLException("Connection does not have a registered XAResource " + connection);
124         }
125         return xaResource;
126     }
127 
128     /**
129      * Registers the association between a Connection and a XAResource. When a connection is enlisted in a transaction,
130      * it is actually the XAResource that is given to the transaction manager.
131      *
132      * @param connection
133      *            The JDBC connection.
134      * @param xaResource
135      *            The XAResource which managed the connection within a transaction.
136      */
137     public synchronized void registerConnection(final Connection connection, final XAResource xaResource) {
138         Objects.requireNonNull(connection, "connection");
139         Objects.requireNonNull(xaResource, "xaResource");
140         xaResources.put(connection, xaResource);
141     }
142 
143     /**
144      * Unregisters a destroyed connection from {@link TransactionRegistry}.
145      *
146      * @param connection
147      *            A destroyed connection from {@link TransactionRegistry}.
148      */
149     public synchronized void unregisterConnection(final Connection connection) {
150         xaResources.remove(getConnectionKey(connection));
151     }
152 }