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