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;
025
026import javax.sql.DataSource;
027import javax.sql.XADataSource;
028import javax.transaction.TransactionManager;
029
030import java.sql.SQLException;
031
032/**
033 * <p>
034 * BasicManagedDataSource is an extension of BasicDataSource which creates ManagedConnections. This data source can
035 * create either full two-phase-commit XA connections or one-phase-commit local connections. Both types of connections
036 * are committed or rolled back as part of the global transaction (a.k.a. XA transaction or JTA Transaction), but only
037 * XA connections can be recovered in the case of a system crash.
038 * </p>
039 * <p>
040 * BasicManagedDataSource adds the TransactionManager and XADataSource properties. The TransactionManager property is
041 * required and is used to enlist connections in global transactions. The XADataSource is optional and if set is the
042 * class name of the XADataSource class for a two-phase-commit JDBC driver. If the XADataSource property is set, the
043 * driverClassName is ignored and a DataSourceXAConnectionFactory is created. Otherwise, a standard
044 * DriverConnectionFactory is created and wrapped with a LocalXAConnectionFactory.
045 * </p>
046 *
047 * @see BasicDataSource
048 * @see ManagedConnection
049 * @since 2.0
050 */
051public class BasicManagedDataSource extends BasicDataSource {
052
053    /** Transaction Registry */
054    private TransactionRegistry transactionRegistry;
055
056    /** Transaction Manager */
057    private transient TransactionManager transactionManager;
058
059    /** XA data source class name */
060    private String xaDataSource;
061
062    /** XA data source instance */
063    private XADataSource xaDataSourceInstance;
064
065    /**
066     * Gets the XADataSource instance used by the XAConnectionFactory.
067     *
068     * @return the XADataSource
069     */
070    public synchronized XADataSource getXaDataSourceInstance() {
071        return xaDataSourceInstance;
072    }
073
074    /**
075     * <p>
076     * Sets the XADataSource instance used by the XAConnectionFactory.
077     * </p>
078     * <p>
079     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
080     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
081     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
082     * </p>
083     *
084     * @param xaDataSourceInstance
085     *            XADataSource instance
086     */
087    public synchronized void setXaDataSourceInstance(final XADataSource xaDataSourceInstance) {
088        this.xaDataSourceInstance = xaDataSourceInstance;
089        xaDataSource = xaDataSourceInstance == null ? null : xaDataSourceInstance.getClass().getName();
090    }
091
092    /**
093     * Gets the required transaction manager property.
094     *
095     * @return the transaction manager used to enlist connections
096     */
097    public TransactionManager getTransactionManager() {
098        return transactionManager;
099    }
100
101    /**
102     * Gets the transaction registry.
103     *
104     * @return the transaction registry associating XAResources with managed connections
105     */
106    protected synchronized TransactionRegistry getTransactionRegistry() {
107        return transactionRegistry;
108    }
109
110    /**
111     * Sets the required transaction manager property.
112     *
113     * @param transactionManager
114     *            the transaction manager used to enlist connections
115     */
116    public void setTransactionManager(final TransactionManager transactionManager) {
117        this.transactionManager = transactionManager;
118    }
119
120    /**
121     * Gets the optional XADataSource class name.
122     *
123     * @return the optional XADataSource class name
124     */
125    public synchronized String getXADataSource() {
126        return xaDataSource;
127    }
128
129    /**
130     * Sets the optional XADataSource class name.
131     *
132     * @param xaDataSource
133     *            the optional XADataSource class name
134     */
135    public synchronized void setXADataSource(final String xaDataSource) {
136        this.xaDataSource = xaDataSource;
137    }
138
139    @Override
140    protected ConnectionFactory createConnectionFactory() throws SQLException {
141        if (transactionManager == null) {
142            throw new SQLException("Transaction manager must be set before a connection can be created");
143        }
144
145        // If xa data source is not specified a DriverConnectionFactory is created and wrapped with a
146        // LocalXAConnectionFactory
147        if (xaDataSource == null) {
148            final ConnectionFactory connectionFactory = super.createConnectionFactory();
149            final XAConnectionFactory xaConnectionFactory = new LocalXAConnectionFactory(getTransactionManager(),
150                    connectionFactory);
151            transactionRegistry = xaConnectionFactory.getTransactionRegistry();
152            return xaConnectionFactory;
153        }
154
155        // Create the XADataSource instance using the configured class name if it has not been set
156        if (xaDataSourceInstance == null) {
157            Class<?> xaDataSourceClass = null;
158            try {
159                xaDataSourceClass = Class.forName(xaDataSource);
160            } catch (final Exception t) {
161                final String message = "Cannot load XA data source class '" + xaDataSource + "'";
162                throw new SQLException(message, t);
163            }
164
165            try {
166                xaDataSourceInstance = (XADataSource) xaDataSourceClass.newInstance();
167            } catch (final Exception t) {
168                final String message = "Cannot create XA data source of class '" + xaDataSource + "'";
169                throw new SQLException(message, t);
170            }
171        }
172
173        // finally, create the XAConnectionFactory using the XA data source
174        final XAConnectionFactory xaConnectionFactory = new DataSourceXAConnectionFactory(getTransactionManager(),
175                xaDataSourceInstance, getUsername(), getPassword());
176        transactionRegistry = xaConnectionFactory.getTransactionRegistry();
177        return xaConnectionFactory;
178    }
179
180    @Override
181    protected DataSource createDataSourceInstance() throws SQLException {
182        final PoolingDataSource<PoolableConnection> pds = new ManagedDataSource<>(getConnectionPool(),
183                transactionRegistry);
184        pds.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
185        return pds;
186    }
187
188    /**
189     * Creates the PoolableConnectionFactory and attaches it to the connection pool.
190     *
191     * @param driverConnectionFactory
192     *            JDBC connection factory created by {@link #createConnectionFactory()}
193     * @throws SQLException
194     *             if an error occurs creating the PoolableConnectionFactory
195     */
196    @Override
197    protected PoolableConnectionFactory createPoolableConnectionFactory(final ConnectionFactory driverConnectionFactory)
198            throws SQLException {
199        PoolableConnectionFactory connectionFactory = null;
200        try {
201            connectionFactory = new PoolableManagedConnectionFactory((XAConnectionFactory) driverConnectionFactory,
202                    getRegisteredJmxName());
203            connectionFactory.setValidationQuery(getValidationQuery());
204            connectionFactory.setValidationQueryTimeout(getValidationQueryTimeout());
205            connectionFactory.setConnectionInitSql(getConnectionInitSqls());
206            connectionFactory.setDefaultReadOnly(getDefaultReadOnly());
207            connectionFactory.setDefaultAutoCommit(getDefaultAutoCommit());
208            connectionFactory.setDefaultTransactionIsolation(getDefaultTransactionIsolation());
209            connectionFactory.setDefaultCatalog(getDefaultCatalog());
210            connectionFactory.setCacheState(getCacheState());
211            connectionFactory.setPoolStatements(isPoolPreparedStatements());
212            connectionFactory.setMaxOpenPreparedStatements(getMaxOpenPreparedStatements());
213            connectionFactory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis());
214            connectionFactory.setRollbackOnReturn(getRollbackOnReturn());
215            connectionFactory.setEnableAutoCommitOnReturn(getEnableAutoCommitOnReturn());
216            connectionFactory.setDefaultQueryTimeout(getDefaultQueryTimeout());
217            connectionFactory.setFastFailValidation(getFastFailValidation());
218            connectionFactory.setDisconnectionSqlCodes(getDisconnectionSqlCodes());
219            validateConnectionFactory(connectionFactory);
220        } catch (final RuntimeException e) {
221            throw e;
222        } catch (final Exception e) {
223            throw new SQLException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e);
224        }
225        return connectionFactory;
226    }
227}