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,
130                    getRegisteredJmxName());
131            connectionFactory.setValidationQuery(getValidationQuery());
132            connectionFactory.setValidationQueryTimeout(getValidationQueryTimeoutDuration());
133            connectionFactory.setConnectionInitSql(getConnectionInitSqls());
134            connectionFactory.setDefaultReadOnly(getDefaultReadOnly());
135            connectionFactory.setDefaultAutoCommit(getDefaultAutoCommit());
136            connectionFactory.setDefaultTransactionIsolation(getDefaultTransactionIsolation());
137            connectionFactory.setDefaultCatalog(getDefaultCatalog());
138            connectionFactory.setDefaultSchema(getDefaultSchema());
139            connectionFactory.setCacheState(getCacheState());
140            connectionFactory.setPoolStatements(isPoolPreparedStatements());
141            connectionFactory.setClearStatementPoolOnReturn(isClearStatementPoolOnReturn());
142            connectionFactory.setMaxOpenPreparedStatements(getMaxOpenPreparedStatements());
143            connectionFactory.setMaxConn(getMaxConnDuration());
144            connectionFactory.setRollbackOnReturn(getRollbackOnReturn());
145            connectionFactory.setAutoCommitOnReturn(getAutoCommitOnReturn());
146            connectionFactory.setDefaultQueryTimeout(getDefaultQueryTimeoutDuration());
147            connectionFactory.setFastFailValidation(getFastFailValidation());
148            connectionFactory.setDisconnectionSqlCodes(getDisconnectionSqlCodes());
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     * <p>
238     * Sets the XADataSource instance used by the XAConnectionFactory.
239     * </p>
240     * <p>
241     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
242     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
243     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
244     * </p>
245     *
246     * @param xaDataSourceInstance
247     *            XADataSource instance
248     */
249    public synchronized void setXaDataSourceInstance(final XADataSource xaDataSourceInstance) {
250        this.xaDataSourceInstance = xaDataSourceInstance;
251        xaDataSource = xaDataSourceInstance == null ? null : xaDataSourceInstance.getClass().getName();
252    }
253}