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