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