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