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     */
018    package org.apache.commons.dbcp.managed;
019    
020    import org.apache.commons.dbcp.AbandonedConfig;
021    import org.apache.commons.dbcp.BasicDataSource;
022    import org.apache.commons.dbcp.ConnectionFactory;
023    import org.apache.commons.dbcp.PoolableConnectionFactory;
024    import org.apache.commons.dbcp.PoolingDataSource;
025    import org.apache.commons.pool.KeyedObjectPoolFactory;
026    
027    import javax.sql.XADataSource;
028    import javax.transaction.TransactionManager;
029    import java.sql.SQLException;
030    
031    /**
032     * <p>BasicManagedDataSource is an extension of BasicDataSource which
033     * creates ManagedConnections.  This data source can create either
034     * full two-phase-commit XA connections or one-phase-commit
035     * local connections.  Both types of connections are committed or
036     * rolled back as part of the global transaction (a.k.a. XA
037     * transaction or JTA Transaction), but only XA connections can be
038     * recovered in the case of a system crash.
039     * </p>
040     * <p>BasicManagedDataSource adds the TransactionManager and XADataSource
041     * properties.  The TransactionManager property is required and is
042     * used to elist connections in global transactions.  The XADataSource
043     * is optional and if set is the class name of the XADataSource class
044     * for a two-phase-commit JDBC driver.  If the XADataSource property
045     * is set, the driverClassName is ignored and a DataSourceXAConnectionFactory
046     * is created. Otherwise, a standard DriverConnectionFactory is created
047     * and wrapped with a LocalXAConnectionFactory.
048     * </p>
049     *
050     * @see BasicDataSource
051     * @see ManagedConnection
052     * @version $Revision: 892307 $
053     */
054    public class BasicManagedDataSource extends BasicDataSource {
055        /** Transaction Registry */
056        private TransactionRegistry transactionRegistry;
057        /** Transaction Manager */
058        private transient TransactionManager transactionManager;
059        /** XA datasource class name */
060        private String xaDataSource;
061        /** XA datasource instance */
062        private XADataSource xaDataSourceInstance;
063    
064        /**
065         * Gets the XADataSource instance used by the XAConnectionFactory.
066         * 
067         * @return the XADataSource
068         */
069        public synchronized XADataSource getXaDataSourceInstance() {
070            return xaDataSourceInstance;
071        }
072    
073        /**
074         * <p>Sets the XADataSource instance used by the XAConnectionFactory.</p>
075         * <p>
076         * Note: this method currently has no effect once the pool has been
077         * initialized.  The pool is initialized the first time one of the
078         * following methods is invoked: <code>getConnection, setLogwriter,
079         * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p>
080         * 
081         * @param xaDataSourceInstance XADataSource instance
082         */
083        public synchronized void setXaDataSourceInstance(XADataSource xaDataSourceInstance) {
084            this.xaDataSourceInstance = xaDataSourceInstance;
085            xaDataSource = xaDataSourceInstance == null ? null : xaDataSourceInstance.getClass().getName();
086        }
087    
088        /**
089         * Gets the required transaction manager property.
090         * @return the transaction manager used to enlist connections
091         */
092        public TransactionManager getTransactionManager() {
093            return transactionManager;
094        }
095        
096        /**
097         * Gets the transaction registry.
098         * @return the transaction registry associating XAResources with managed connections
099         */
100        protected synchronized TransactionRegistry getTransactionRegistry() {
101            return transactionRegistry;
102        }
103    
104        /**
105         * Sets the required transaction manager property.
106         * @param transactionManager the transaction manager used to enlist connections
107         */
108        public void setTransactionManager(TransactionManager transactionManager) {
109            this.transactionManager = transactionManager;
110        }
111    
112        /**
113         * Gets the optional XADataSource class name.
114         * @return the optional XADataSource class name
115         */
116        public synchronized String getXADataSource() {
117            return xaDataSource;
118        }
119    
120        /**
121         * Sets the optional XADataSource class name.
122         * @param xaDataSource the optional XADataSource class name
123         */
124        public synchronized void setXADataSource(String xaDataSource) {
125            this.xaDataSource = xaDataSource;
126        }
127    
128        protected ConnectionFactory createConnectionFactory() throws SQLException {
129            if (transactionManager == null) {
130                throw new SQLException("Transaction manager must be set before a connection can be created");
131            }
132    
133            // If xa data source is not specified a DriverConnectionFactory is created and wrapped with a LocalXAConnectionFactory
134            if (xaDataSource == null) {
135                ConnectionFactory connectionFactory = super.createConnectionFactory();
136                XAConnectionFactory xaConnectionFactory = new LocalXAConnectionFactory(getTransactionManager(), connectionFactory);
137                transactionRegistry = xaConnectionFactory.getTransactionRegistry();
138                return xaConnectionFactory;
139            }
140    
141            // Create the XADataSource instance using the configured class name if it has not been set
142            if (xaDataSourceInstance == null) {
143                Class xaDataSourceClass = null;
144                try {
145                    xaDataSourceClass = Class.forName(xaDataSource);
146                } catch (Throwable t) {
147                    String message = "Cannot load XA data source class '" + xaDataSource + "'";
148                    throw (SQLException)new SQLException(message).initCause(t);
149                }
150                
151                try {
152                    xaDataSourceInstance = (XADataSource) xaDataSourceClass.newInstance();
153                } catch (Throwable t) {
154                    String message = "Cannot create XA data source of class '" + xaDataSource + "'";
155                    throw (SQLException)new SQLException(message).initCause(t);
156                }
157            }
158    
159            // finally, create the XAConectionFactory using the XA data source
160            XAConnectionFactory xaConnectionFactory = new DataSourceXAConnectionFactory(getTransactionManager(), xaDataSourceInstance, username, password);
161            transactionRegistry = xaConnectionFactory.getTransactionRegistry();
162            return xaConnectionFactory;
163        }
164    
165        protected void createDataSourceInstance() throws SQLException {
166            PoolingDataSource pds = new ManagedDataSource(connectionPool, transactionRegistry);
167            pds.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
168            pds.setLogWriter(logWriter);
169            dataSource = pds; 
170        }
171        
172        /**
173         * Creates the PoolableConnectionFactory and attaches it to the connection pool.
174         *
175         * @param driverConnectionFactory JDBC connection factory created by {@link #createConnectionFactory()}
176         * @param statementPoolFactory statement pool factory (null if statement pooling is turned off)
177         * @param abandonedConfig abandoned connection tracking configuration (null if no tracking)
178         * @throws SQLException if an error occurs creating the PoolableConnectionFactory
179         */
180        protected void createPoolableConnectionFactory(ConnectionFactory driverConnectionFactory,
181                KeyedObjectPoolFactory statementPoolFactory, AbandonedConfig abandonedConfig) throws SQLException {
182            PoolableConnectionFactory connectionFactory = null;
183            try {
184                connectionFactory =
185                    new PoolableManagedConnectionFactory((XAConnectionFactory) driverConnectionFactory,
186                                                  connectionPool,
187                                                  statementPoolFactory,
188                                                  validationQuery,
189                                                  validationQueryTimeout,
190                                                  connectionInitSqls,
191                                                  defaultReadOnly,
192                                                  defaultAutoCommit,
193                                                  defaultTransactionIsolation,
194                                                  defaultCatalog,
195                                                  abandonedConfig);
196                validateConnectionFactory(connectionFactory);
197            } catch (RuntimeException e) {
198                throw e;
199            } catch (Exception e) {
200                throw (SQLException)new SQLException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")").initCause(e);
201            }
202        }
203    }