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.WeakHashMap; 024 025import javax.transaction.Status; 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/** 035 * TransactionRegistry tracks Connections and XAResources in a transacted environment for a single XAConnectionFactory. 036 * <p> 037 * The TransactionRegistry hides the details of transaction processing from the existing DBCP pooling code, and gives 038 * the ManagedConnection a way to enlist connections in a transaction, allowing for the maximal rescue of DBCP. 039 * </p> 040 * @author Dain Sundstrom 041 * @since 2.0 042 */ 043public class TransactionRegistry { 044 private final TransactionManager transactionManager; 045 private final Map<Transaction, TransactionContext> caches = 046 new WeakHashMap<>(); 047 private final Map<Connection, XAResource> xaResources = new WeakHashMap<>(); 048 049 /** 050 * Creates a TransactionRegistry for the specified transaction manager. 051 * @param transactionManager 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 059 * is enlisted in a transaction, it is actually the XAResource that is given to the transaction 060 * manager. 061 * 062 * @param connection the JDBC connection 063 * @param xaResource the XAResource which managed the connection within a transaction 064 */ 065 public synchronized void registerConnection(final Connection connection, final XAResource xaResource) { 066 if (connection == null) { 067 throw new NullPointerException("connection is null"); 068 } 069 if (xaResource == null) { 070 throw new NullPointerException("xaResource is null"); 071 } 072 xaResources.put(connection, xaResource); 073 } 074 075 /** 076 * Gets the XAResource registered for the connection. 077 * @param connection the connection 078 * @return the XAResource registered for the connection; never null 079 * @throws SQLException if the connection does not have a registered XAResource 080 */ 081 public synchronized XAResource getXAResource(final Connection connection) throws SQLException { 082 if (connection == null) { 083 throw new NullPointerException("connection is null"); 084 } 085 final Connection key = getConnectionKey(connection); 086 final XAResource xaResource = xaResources.get(key); 087 if (xaResource == null) { 088 throw new SQLException("Connection does not have a registered XAResource " + connection); 089 } 090 return xaResource; 091 } 092 093 /** 094 * Gets the active TransactionContext or null if not Transaction is active. 095 * @return the active TransactionContext or null if no Transaction is active 096 * @throws SQLException if 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 // is it active 109 final int status = transaction.getStatus(); 110 if (status != Status.STATUS_ACTIVE && status != Status.STATUS_MARKED_ROLLBACK) { 111 return null; 112 } 113 } catch (final SystemException e) { 114 throw new SQLException("Unable to determine current transaction ", e); 115 } 116 117 // register the the context (or create a new one) 118 synchronized (this) { 119 TransactionContext cache = caches.get(transaction); 120 if (cache == null) { 121 cache = new TransactionContext(this, transaction); 122 caches.put(transaction, cache); 123 } 124 return cache; 125 } 126 } 127 128 /** 129 * Unregisters a destroyed connection from {@link TransactionRegistry} 130 * @param connection 131 */ 132 public synchronized void unregisterConnection(final Connection connection) { 133 final Connection key = getConnectionKey(connection); 134 xaResources.remove(key); 135 } 136 137 138 private Connection getConnectionKey(final Connection connection) { 139 Connection result; 140 if (connection instanceof DelegatingConnection) { 141 result = ((DelegatingConnection<?>) connection).getInnermostDelegateInternal(); 142 } else { 143 result = connection; 144 } 145 return result; 146 } 147} 148