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 * @version $Id: TransactionRegistry.java 1660791 2015-02-19 04:13:10Z psteitz $
042 * @since 2.0
043 */
044public class TransactionRegistry {
045    private final TransactionManager transactionManager;
046    private final Map<Transaction, TransactionContext> caches =
047            new WeakHashMap<>();
048    private final Map<Connection, XAResource> xaResources = new WeakHashMap<>();
049
050    /**
051     * Creates a TransactionRegistry for the specified transaction manager.
052     * @param transactionManager the transaction manager used to enlist connections
053     */
054    public TransactionRegistry(TransactionManager transactionManager) {
055        this.transactionManager = transactionManager;
056    }
057
058    /**
059     * Registers the association between a Connection and a XAResource.  When a connection
060     * is enlisted in a transaction, it is actually the XAResource that is given to the transaction
061     * manager.
062     *
063     * @param connection the JDBC connection
064     * @param xaResource the XAResource which managed the connection within a transaction
065     */
066    public synchronized void registerConnection(Connection connection, XAResource xaResource) {
067        if (connection == null) {
068            throw new NullPointerException("connection is null");
069        }
070        if (xaResource == null) {
071            throw new NullPointerException("xaResource is null");
072        }
073        xaResources.put(connection, xaResource);
074    }
075
076    /**
077     * Gets the XAResource registered for the connection.
078     * @param connection the connection
079     * @return the XAResource registered for the connection; never null
080     * @throws SQLException if the connection does not have a registered XAResource
081     */
082    public synchronized XAResource getXAResource(Connection connection) throws SQLException {
083        if (connection == null) {
084            throw new NullPointerException("connection is null");
085        }
086        Connection key = getConnectionKey(connection);
087        XAResource xaResource = xaResources.get(key);
088        if (xaResource == null) {
089            throw new SQLException("Connection does not have a registered XAResource " + connection);
090        }
091        return xaResource;
092    }
093
094    /**
095     * Gets the active TransactionContext or null if not Transaction is active.
096     * @return the active TransactionContext or null if no Transaction is active
097     * @throws SQLException if an error occurs while fetching the transaction
098     */
099    public TransactionContext getActiveTransactionContext() throws SQLException {
100        Transaction transaction = null;
101        try {
102            transaction = transactionManager.getTransaction();
103
104            // was there a transaction?
105            if (transaction == null) {
106                return null;
107            }
108
109            // is it active
110            int status = transaction.getStatus();
111            if (status != Status.STATUS_ACTIVE && status != Status.STATUS_MARKED_ROLLBACK) {
112                return null;
113            }
114        } catch (SystemException e) {
115            throw new SQLException("Unable to determine current transaction ", e);
116        }
117
118        // register the the context (or create a new one)
119        synchronized (this) {
120            TransactionContext cache = caches.get(transaction);
121            if (cache == null) {
122                cache = new TransactionContext(this, transaction);
123                caches.put(transaction, cache);
124            }
125            return cache;
126        }
127    }
128
129    /**
130     * Unregisters a destroyed connection from {@link TransactionRegistry}
131     * @param connection
132     */
133    public synchronized void unregisterConnection(Connection connection) {
134        Connection key = getConnectionKey(connection);
135        xaResources.remove(key);
136    }
137
138
139    private Connection getConnectionKey(Connection connection) {
140        Connection result;
141        if (connection instanceof DelegatingConnection) {
142            result = ((DelegatingConnection<?>) connection).getInnermostDelegateInternal();
143        } else {
144            result = connection;
145        }
146        return result;
147    }
148}
149