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