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.sql.Connection; 021import java.sql.SQLException; 022import java.util.Map; 023import java.util.Objects; 024import java.util.WeakHashMap; 025 026import javax.transaction.SystemException; 027import javax.transaction.Transaction; 028import javax.transaction.TransactionManager; 029import javax.transaction.xa.XAResource; 030 031import org.apache.commons.dbcp2.DelegatingConnection; 032 033/** 034 * TransactionRegistry tracks Connections and XAResources in a transacted environment for a single XAConnectionFactory. 035 * <p> 036 * The TransactionRegistry hides the details of transaction processing from the existing DBCP pooling code, and gives 037 * the ManagedConnection a way to enlist connections in a transaction, allowing for the maximal rescue of DBCP. 038 * </p> 039 * 040 * @since 2.0 041 */ 042public class TransactionRegistry { 043 private final TransactionManager transactionManager; 044 private final Map<Transaction, TransactionContext> caches = new WeakHashMap<>(); 045 private final Map<Connection, XAResource> xaResources = new WeakHashMap<>(); 046 047 /** 048 * Creates a TransactionRegistry for the specified transaction manager. 049 * 050 * @param transactionManager 051 * the transaction manager used to enlist connections. 052 */ 053 public TransactionRegistry(final TransactionManager transactionManager) { 054 this.transactionManager = transactionManager; 055 } 056 057 /** 058 * Registers the association between a Connection and a XAResource. When a connection is enlisted in a transaction, 059 * it is actually the XAResource that is given to the transaction manager. 060 * 061 * @param connection 062 * The JDBC connection. 063 * @param xaResource 064 * The XAResource which managed the connection within a transaction. 065 */ 066 public synchronized void registerConnection(final Connection connection, final XAResource xaResource) { 067 Objects.requireNonNull(connection, "connection is null"); 068 Objects.requireNonNull(xaResource, "xaResource is null"); 069 xaResources.put(connection, xaResource); 070 } 071 072 /** 073 * Gets the XAResource registered for the connection. 074 * 075 * @param connection 076 * the connection 077 * @return The XAResource registered for the connection; never null. 078 * @throws SQLException 079 * Thrown when the connection does not have a registered XAResource. 080 */ 081 public synchronized XAResource getXAResource(final Connection connection) throws SQLException { 082 Objects.requireNonNull(connection, "connection is null"); 083 final Connection key = getConnectionKey(connection); 084 final XAResource xaResource = xaResources.get(key); 085 if (xaResource == null) { 086 throw new SQLException("Connection does not have a registered XAResource " + connection); 087 } 088 return xaResource; 089 } 090 091 /** 092 * Gets the active TransactionContext or null if not Transaction is active. 093 * 094 * @return The active TransactionContext or null if no Transaction is active. 095 * @throws SQLException 096 * Thrown when an error occurs while fetching the transaction. 097 */ 098 public TransactionContext getActiveTransactionContext() throws SQLException { 099 Transaction transaction = null; 100 try { 101 transaction = transactionManager.getTransaction(); 102 103 // was there a transaction? 104 if (transaction == null) { 105 return null; 106 } 107 108 // This is the transaction on the thread so no need to check it's status - we should try to use it and 109 // fail later based on the subsequent status 110 } catch (final SystemException e) { 111 throw new SQLException("Unable to determine current transaction ", e); 112 } 113 114 // register the the context (or create a new one) 115 synchronized (this) { 116 TransactionContext cache = caches.get(transaction); 117 if (cache == null) { 118 cache = new TransactionContext(this, transaction); 119 caches.put(transaction, cache); 120 } 121 return cache; 122 } 123 } 124 125 /** 126 * Unregisters a destroyed connection from {@link TransactionRegistry}. 127 * 128 * @param connection 129 * A destroyed connection from {@link TransactionRegistry}. 130 */ 131 public synchronized void unregisterConnection(final Connection connection) { 132 final Connection key = getConnectionKey(connection); 133 xaResources.remove(key); 134 } 135 136 private Connection getConnectionKey(final Connection connection) { 137 Connection result; 138 if (connection instanceof DelegatingConnection) { 139 result = ((DelegatingConnection<?>) connection).getInnermostDelegateInternal(); 140 } else { 141 result = connection; 142 } 143 return result; 144 } 145}