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