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