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 javax.sql.ConnectionEvent;
021import javax.sql.ConnectionEventListener;
022import javax.sql.PooledConnection;
023import javax.sql.XAConnection;
024import javax.sql.XADataSource;
025import javax.transaction.TransactionManager;
026import javax.transaction.xa.XAResource;
027
028import java.sql.Connection;
029import java.sql.SQLException;
030
031/**
032 * An implementation of XAConnectionFactory which uses a real XADataSource to obtain connections and XAResources.
033 *
034 * @author Dain Sundstrom
035 * @since 2.0
036 */
037public class DataSourceXAConnectionFactory implements XAConnectionFactory {
038    private final TransactionRegistry transactionRegistry;
039    private final XADataSource xaDataSource;
040    private String username;
041    private String password;
042
043    /**
044     * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database
045     * connections.  The connections are enlisted into transactions using the specified transaction manager.
046     *
047     * @param transactionManager the transaction manager in which connections will be enlisted
048     * @param xaDataSource the data source from which connections will be retrieved
049     */
050    public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource) {
051        this(transactionManager, xaDataSource, null, null);
052    }
053
054    /**
055     * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database
056     * connections.  The connections are enlisted into transactions using the specified transaction manager.
057     *
058     * @param transactionManager the transaction manager in which connections will be enlisted
059     * @param xaDataSource the data source from which connections will be retrieved
060     * @param username the username used for authenticating new connections or null for unauthenticated
061     * @param password the password used for authenticating new connections
062     */
063    public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource, final String username, final String password) {
064        if (transactionManager == null) {
065            throw new NullPointerException("transactionManager is null");
066        }
067        if (xaDataSource == null) {
068            throw new NullPointerException("xaDataSource is null");
069        }
070
071        this.transactionRegistry = new TransactionRegistry(transactionManager);
072        this.xaDataSource = xaDataSource;
073        this.username = username;
074        this.password = password;
075    }
076
077    /**
078     * Gets the username used to authenticate new connections.
079     * @return the user name or null if unauthenticated connections are used
080     */
081    public String getUsername() {
082        return username;
083    }
084
085    /**
086     * Sets the username used to authenticate new connections.
087     * @param username the username used for authenticating the connection or null for unauthenticated
088     */
089    public void setUsername(final String username) {
090        this.username = username;
091    }
092
093    /**
094     * Sets the password used to authenticate new connections.
095     * @param password the password used for authenticating the connection or null for unauthenticated
096     */
097    public void setPassword(final String password) {
098        this.password = password;
099    }
100
101    @Override
102    public TransactionRegistry getTransactionRegistry() {
103        return transactionRegistry;
104    }
105
106    @Override
107    public Connection createConnection() throws SQLException {
108        // create a new XAConnection
109        XAConnection xaConnection;
110        if (username == null) {
111            xaConnection = xaDataSource.getXAConnection();
112        } else {
113            xaConnection = xaDataSource.getXAConnection(username, password);
114        }
115
116        // get the real connection and XAResource from the connection
117        final Connection connection = xaConnection.getConnection();
118        final XAResource xaResource = xaConnection.getXAResource();
119
120        // register the xa resource for the connection
121        transactionRegistry.registerConnection(connection, xaResource);
122
123        // The Connection we're returning is a handle on the XAConnection.
124        // When the pool calling us closes the Connection, we need to
125        // also close the XAConnection that holds the physical connection.
126        xaConnection.addConnectionEventListener(new ConnectionEventListener() {
127
128            @Override
129            public void connectionClosed(final ConnectionEvent event) {
130                final PooledConnection pc = (PooledConnection) event.getSource();
131                pc.removeConnectionEventListener(this);
132                try {
133                    pc.close();
134                } catch (final SQLException e) {
135                    System.err.println("Failed to close XAConnection");
136                    e.printStackTrace();
137                }
138            }
139
140            @Override
141            public void connectionErrorOccurred(final ConnectionEvent event) {
142                connectionClosed(event);
143            }
144        });
145
146
147        return connection;
148    }
149}