    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 org.apache.commons.dbcp2.BasicDataSource;
    021import org.apache.commons.dbcp2.ConnectionFactory;
    022import org.apache.commons.dbcp2.PoolableConnection;
    023import org.apache.commons.dbcp2.PoolableConnectionFactory;
    024import org.apache.commons.dbcp2.PoolingDataSource;
    025import org.apache.commons.dbcp2.Utils;
    026
    027import javax.sql.DataSource;
    028import javax.sql.XADataSource;
    029import javax.transaction.TransactionManager;
    030import javax.transaction.TransactionSynchronizationRegistry;
    031
    032import java.sql.SQLException;
    033
    034/**
    035 * <p>
    036 * BasicManagedDataSource is an extension of BasicDataSource which creates ManagedConnections. This data source can
    037 * create either full two-phase-commit XA connections or one-phase-commit local connections. Both types of connections
    038 * are committed or rolled back as part of the global transaction (a.k.a. XA transaction or JTA Transaction), but only
    039 * XA connections can be recovered in the case of a system crash.
    040 * </p>
    041 * <p>
    042 * BasicManagedDataSource adds the TransactionManager and XADataSource properties. The TransactionManager property is
    043 * required and is used to enlist connections in global transactions. The XADataSource is optional and if set is the
    044 * class name of the XADataSource class for a two-phase-commit JDBC driver. If the XADataSource property is set, the
    045 * driverClassName is ignored and a DataSourceXAConnectionFactory is created. Otherwise, a standard
    046 * DriverConnectionFactory is created and wrapped with a LocalXAConnectionFactory.
    047 * </p>
    048 *
    049 * @see BasicDataSource
    050 * @see ManagedConnection
    051 * @since 2.0
    052 */
    053public class BasicManagedDataSource extends BasicDataSource {
    054
    055    /** Transaction Registry */
    056    private TransactionRegistry transactionRegistry;
    057
    058    /** Transaction Manager */
    059    private transient TransactionManager transactionManager;
    060
    061    /** XA data source class name */
    062    private String xaDataSource;
    063
    064    /** XA data source instance */
    065    private XADataSource xaDataSourceInstance;
    066
    067    /** Transaction Synchronization Registry */
    068    private transient TransactionSynchronizationRegistry transactionSynchronizationRegistry;
    069
    070    /**
    071     * Gets the XADataSource instance used by the XAConnectionFactory.
    072     *
    073     * @return the XADataSource
    074     */
    075    public synchronized XADataSource getXaDataSourceInstance() {
    076        return xaDataSourceInstance;
    077    }
    078
    079    /**
    080     * <p>
    081     * Sets the XADataSource instance used by the XAConnectionFactory.
    082     * </p>
    083     * <p>
    084     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
    085     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
    086     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
    087     * </p>
    088     *
    089     * @param xaDataSourceInstance
    090     *            XADataSource instance
    091     */
    092    public synchronized void setXaDataSourceInstance(final XADataSource xaDataSourceInstance) {
    093        this.xaDataSourceInstance = xaDataSourceInstance;
    094        xaDataSource = xaDataSourceInstance == null ? null : xaDataSourceInstance.getClass().getName();
    095    }
    096
    097    /**
    098     * Gets the required transaction manager property.
    099     *
    100     * @return the transaction manager used to enlist connections
    101     */
    102    public TransactionManager getTransactionManager() {
    103        return transactionManager;
    104    }
    105
    106    /**
    107     * Gets the optional TransactionSynchronizationRegistry.
    108     *
    109     * @return the TSR that can be used to register synchronizations.
    110     * @since 2.6.0
    111     */
    112    public TransactionSynchronizationRegistry getTransactionSynchronizationRegistry() {
    113        return transactionSynchronizationRegistry;
    114    }
    115
    116    /**
    117     * Gets the transaction registry.
    118     *
    119     * @return the transaction registry associating XAResources with managed connections
    120     */
    121    protected synchronized TransactionRegistry getTransactionRegistry() {
    122        return transactionRegistry;
    123    }
    124
    125    /**
    126     * Sets the required transaction manager property.
    127     *
    128     * @param transactionManager
    129     *            the transaction manager used to enlist connections
    130     */
    131    public void setTransactionManager(final TransactionManager transactionManager) {
    132        this.transactionManager = transactionManager;
    133    }
    134
    135    /**
    136     * Sets the optional TransactionSynchronizationRegistry property.
    137     *
    138     * @param transactionSynchronizationRegistry
    139     *            the TSR used to register synchronizations
    140     * @since 2.6.0
    141     */
    142    public void setTransactionSynchronizationRegistry(
    143            final TransactionSynchronizationRegistry transactionSynchronizationRegistry) {
    144        this.transactionSynchronizationRegistry = transactionSynchronizationRegistry;
    145    }
    146
    147    /**
    148     * Gets the optional XADataSource class name.
    149     *
    150     * @return the optional XADataSource class name
    151     */
    152    public synchronized String getXADataSource() {
    153        return xaDataSource;
    154    }
    155
    156    /**
    157     * Sets the optional XADataSource class name.
    158     *
    159     * @param xaDataSource
    160     *            the optional XADataSource class name
    161     */
    162    public synchronized void setXADataSource(final String xaDataSource) {
    163        this.xaDataSource = xaDataSource;
    164    }
    165
    166    @Override
    167    protected ConnectionFactory createConnectionFactory() throws SQLException {
    168        if (transactionManager == null) {
    169            throw new SQLException("Transaction manager must be set before a connection can be created");
    170        }
    171
    172        // If xa data source is not specified a DriverConnectionFactory is created and wrapped with a
    173        // LocalXAConnectionFactory
    174        if (xaDataSource == null) {
    175            final ConnectionFactory connectionFactory = super.createConnectionFactory();
    176            final XAConnectionFactory xaConnectionFactory = new LocalXAConnectionFactory(getTransactionManager(),
    177                    connectionFactory);
    178            transactionRegistry = xaConnectionFactory.getTransactionRegistry();
    179            return xaConnectionFactory;
    180        }
    181
    182        // Create the XADataSource instance using the configured class name if it has not been set
    183        if (xaDataSourceInstance == null) {
    184            Class<?> xaDataSourceClass = null;
    185            try {
    186                xaDataSourceClass = Class.forName(xaDataSource);
    187            } catch (final Exception t) {
    188                final String message = "Cannot load XA data source class '" + xaDataSource + "'";
    189                throw new SQLException(message, t);
    190            }
    191
    192            try {
    193                xaDataSourceInstance = (XADataSource) xaDataSourceClass.getConstructor().newInstance();
    194            } catch (final Exception t) {
    195                final String message = "Cannot create XA data source of class '" + xaDataSource + "'";
    196                throw new SQLException(message, t);
    197            }
    198        }
    199
    200        // finally, create the XAConnectionFactory using the XA data source
    201        final XAConnectionFactory xaConnectionFactory = new DataSourceXAConnectionFactory(getTransactionManager(),
    202                xaDataSourceInstance, getUsername(), Utils.toCharArray(getPassword()), getTransactionSynchronizationRegistry());
    203        transactionRegistry = xaConnectionFactory.getTransactionRegistry();
    204        return xaConnectionFactory;
    205    }
    206
    207    @Override
    208    protected DataSource createDataSourceInstance() throws SQLException {
    209        final PoolingDataSource<PoolableConnection> pds = new ManagedDataSource<>(getConnectionPool(),
    210                transactionRegistry);
    211        pds.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
    212        return pds;
    213    }
    214
    215    /**
    216     * Creates the PoolableConnectionFactory and attaches it to the connection pool.
    217     *
    218     * @param driverConnectionFactory
    219     *            JDBC connection factory created by {@link #createConnectionFactory()}
    220     * @throws SQLException
    221     *             if an error occurs creating the PoolableConnectionFactory
    222     */
    223    @Override
    224    protected PoolableConnectionFactory createPoolableConnectionFactory(final ConnectionFactory driverConnectionFactory)
    225            throws SQLException {
    226        PoolableConnectionFactory connectionFactory = null;
    227        try {
    228            connectionFactory = new PoolableManagedConnectionFactory((XAConnectionFactory) driverConnectionFactory,
    229                    getRegisteredJmxName());
    230            connectionFactory.setValidationQuery(getValidationQuery());
    231            connectionFactory.setValidationQueryTimeout(getValidationQueryTimeout());
    232            connectionFactory.setConnectionInitSql(getConnectionInitSqls());
    233            connectionFactory.setDefaultReadOnly(getDefaultReadOnly());
    234            connectionFactory.setDefaultAutoCommit(getDefaultAutoCommit());
    235            connectionFactory.setDefaultTransactionIsolation(getDefaultTransactionIsolation());
    236            connectionFactory.setDefaultCatalog(getDefaultCatalog());
    237            connectionFactory.setDefaultSchema(getDefaultSchema());
    238            connectionFactory.setCacheState(getCacheState());
    239            connectionFactory.setPoolStatements(isPoolPreparedStatements());
    240            connectionFactory.setMaxOpenPreparedStatements(getMaxOpenPreparedStatements());
    241            connectionFactory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis());
    242            connectionFactory.setRollbackOnReturn(getRollbackOnReturn());
    243            connectionFactory.setAutoCommitOnReturn(getAutoCommitOnReturn());
    244            connectionFactory.setDefaultQueryTimeout(getDefaultQueryTimeout());
    245            connectionFactory.setFastFailValidation(getFastFailValidation());
    246            connectionFactory.setDisconnectionSqlCodes(getDisconnectionSqlCodes());
    247            validateConnectionFactory(connectionFactory);
    248        } catch (final RuntimeException e) {
    249            throw e;
    250        } catch (final Exception e) {
    251            throw new SQLException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e);
    252        }
    253        return connectionFactory;
    254    }
    255}