    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 org.apache.commons.dbcp2.Utils;
    029
    030import java.sql.Connection;
    031import java.sql.SQLException;
    032import java.util.Objects;
    033
    034/**
    035 * An implementation of XAConnectionFactory which uses a real XADataSource to obtain connections and XAResources.
    036 *
    037 * @since 2.0
    038 */
    039public class DataSourceXAConnectionFactory implements XAConnectionFactory {
    040    private final TransactionRegistry transactionRegistry;
    041    private final XADataSource xaDataSource;
    042    private String userName;
    043    private char[] userPassword;
    044
    045    /**
    046     * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections.
    047     * The connections are enlisted into transactions using the specified transaction manager.
    048     *
    049     * @param transactionManager
    050     *            the transaction manager in which connections will be enlisted
    051     * @param xaDataSource
    052     *            the data source from which connections will be retrieved
    053     */
    054    public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource) {
    055        this(transactionManager, xaDataSource, null, (char[]) null);
    056    }
    057
    058    /**
    059     * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections.
    060     * The connections are enlisted into transactions using the specified transaction manager.
    061     *
    062     * @param transactionManager
    063     *            the transaction manager in which connections will be enlisted
    064     * @param xaDataSource
    065     *            the data source from which connections will be retrieved
    066     * @param userName
    067     *            the user name used for authenticating new connections or null for unauthenticated
    068     * @param userPassword
    069     *            the password used for authenticating new connections
    070     */
    071    public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource,
    072            final String userName, final char[] userPassword) {
    073        Objects.requireNonNull(transactionManager, "transactionManager is null");
    074        Objects.requireNonNull(xaDataSource, "xaDataSource is null");
    075        this.transactionRegistry = new TransactionRegistry(transactionManager);
    076        this.xaDataSource = xaDataSource;
    077        this.userName = userName;
    078        this.userPassword = userPassword;
    079    }
    080
    081    /**
    082     * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections.
    083     * The connections are enlisted into transactions using the specified transaction manager.
    084     *
    085     * @param transactionManager
    086     *            the transaction manager in which connections will be enlisted
    087     * @param xaDataSource
    088     *            the data source from which connections will be retrieved
    089     * @param userName
    090     *            the user name used for authenticating new connections or null for unauthenticated
    091     * @param userPassword
    092     *            the password used for authenticating new connections
    093     */
    094    public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource,
    095            final String userName, final String userPassword) {
    096        this(transactionManager, xaDataSource, userName, Utils.toCharArray(userPassword));
    097    }
    098
    099    /**
    100     * Gets the user name used to authenticate new connections.
    101     *
    102     * @return the user name or null if unauthenticated connections are used
    103     */
    104    public String getUsername() {
    105        return userName;
    106    }
    107
    108    /**
    109     * Sets the user name used to authenticate new connections.
    110     *
    111     * @param userName
    112     *            the user name used for authenticating the connection or null for unauthenticated
    113     */
    114    public void setUsername(final String userName) {
    115        this.userName = userName;
    116    }
    117
    118    /**
    119     * Sets the password used to authenticate new connections.
    120     *
    121     * @param userPassword
    122     *            the password used for authenticating the connection or null for unauthenticated.
    123     * @since 2.4.0
    124     */
    125    public void setPassword(final char[] userPassword) {
    126        this.userPassword = userPassword;
    127    }
    128
    129    /**
    130     * Sets the password used to authenticate new connections.
    131     *
    132     * @param userPassword
    133     *            the password used for authenticating the connection or null for unauthenticated
    134     */
    135    public void setPassword(final String userPassword) {
    136        this.userPassword = Utils.toCharArray(userPassword);
    137    }
    138
    139    @Override
    140    public TransactionRegistry getTransactionRegistry() {
    141        return transactionRegistry;
    142    }
    143
    144    @Override
    145    public Connection createConnection() throws SQLException {
    146        // create a new XAConnection
    147        XAConnection xaConnection;
    148        if (userName == null) {
    149            xaConnection = xaDataSource.getXAConnection();
    150        } else {
    151            xaConnection = xaDataSource.getXAConnection(userName, Utils.toString(userPassword));
    152        }
    153
    154        // get the real connection and XAResource from the connection
    155        final Connection connection = xaConnection.getConnection();
    156        final XAResource xaResource = xaConnection.getXAResource();
    157
    158        // register the xa resource for the connection
    159        transactionRegistry.registerConnection(connection, xaResource);
    160
    161        // The Connection we're returning is a handle on the XAConnection.
    162        // When the pool calling us closes the Connection, we need to
    163        // also close the XAConnection that holds the physical connection.
    164        xaConnection.addConnectionEventListener(new ConnectionEventListener() {
    165
    166            @Override
    167            public void connectionClosed(final ConnectionEvent event) {
    168                final PooledConnection pc = (PooledConnection) event.getSource();
    169                pc.removeConnectionEventListener(this);
    170                try {
    171                    pc.close();
    172                } catch (final SQLException e) {
    173                    System.err.println("Failed to close XAConnection");
    174                    e.printStackTrace();
    175                }
    176            }
    177
    178            @Override
    179            public void connectionErrorOccurred(final ConnectionEvent event) {
    180                connectionClosed(event);
    181            }
    182        });
    183
    184        return connection;
    185    }
    186}