001    package org.apache.ojb.broker.accesslayer;
002    
003    /* Copyright 2002-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://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     */
017    
018    import org.apache.commons.dbcp.AbandonedConfig;
019    import org.apache.commons.dbcp.AbandonedObjectPool;
020    import org.apache.commons.dbcp.DriverManagerConnectionFactory;
021    import org.apache.commons.dbcp.PoolableConnectionFactory;
022    import org.apache.commons.dbcp.PoolingDataSource;
023    import org.apache.commons.pool.KeyedObjectPoolFactory;
024    import org.apache.commons.pool.ObjectPool;
025    import org.apache.commons.pool.impl.GenericKeyedObjectPool;
026    import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
027    import org.apache.commons.pool.impl.GenericObjectPool;
028    import org.apache.ojb.broker.PBKey;
029    import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
030    import org.apache.ojb.broker.util.ClassHelper;
031    import org.apache.ojb.broker.util.logging.Logger;
032    import org.apache.ojb.broker.util.logging.LoggerFactory;
033    import org.apache.ojb.broker.util.logging.LoggerWrapperPrintWriter;
034    
035    import javax.sql.DataSource;
036    import java.sql.Connection;
037    import java.sql.SQLException;
038    import java.util.Collection;
039    import java.util.Collections;
040    import java.util.HashMap;
041    import java.util.Iterator;
042    import java.util.Map;
043    import java.util.Properties;
044    
045    /**
046     * ConnectionFactory implementation using Commons DBCP and Commons Pool API
047     * to pool connections.
048     *
049     * Based on a proposal of Dirk Verbeek - Thanks.
050     *
051     * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
052     * @version $Id: ConnectionFactoryDBCPImpl.java,v 1.1 2007-08-24 22:17:30 ewestfal Exp $
053     * @see <a href="http://jakarta.apache.org/commons/pool/">Commons Pool Website</a>
054     * @see <a href="http://jakarta.apache.org/commons/dbcp/">Commons DBCP Website</a>
055     */
056    public class ConnectionFactoryDBCPImpl extends ConnectionFactoryAbstractImpl
057    {
058    
059        public static final String PARAM_NAME_UNWRAP_ALLOWED = "accessToUnderlyingConnectionAllowed";
060        public static final String PARAM_NAME_POOL_STATEMENTS = "poolPreparedStatements";
061        public static final String PARAM_NAME_STATEMENT_POOL_MAX_TOTAL = "maxOpenPreparedStatements";
062    
063        private Logger log = LoggerFactory.getLogger(ConnectionFactoryDBCPImpl.class);
064    
065        /** Key=PBKey, value=ObjectPool. */
066        private Map poolMap = Collections.synchronizedMap(new HashMap());
067        /** Key=PBKey, value=PoolingDataSource. */
068        private Map dsMap = Collections.synchronizedMap(new HashMap());
069        /** Synchronize object for operations not synchronized on Map only. */
070        private final Object poolSynch = new Object();
071    
072        public Connection checkOutJdbcConnection(JdbcConnectionDescriptor jcd) throws LookupException
073        {
074            final DataSource ds = getDataSource(jcd);
075    
076            // Returned DS is never null, exception are logged by getDataSource and gets
077            // re-thrown here since we don't catch them
078    
079            Connection conn;
080            try
081            {
082                conn = ds.getConnection();
083            }
084            catch (SQLException e)
085            {
086                throw new LookupException("Could not get connection from DBCP DataSource", e);
087            }
088            return conn;
089        }
090    
091        public void releaseJdbcConnection(JdbcConnectionDescriptor jcd, Connection con)
092                throws LookupException
093        {
094            try
095            {
096                // We are using datasources, thus close returns connection to pool
097                con.close();
098            }
099            catch (SQLException e)
100            {
101                log.warn("Connection close failed", e);
102            }
103        }
104    
105        /**
106         * Closes all managed pools.
107         */
108        public void releaseAllResources()
109        {
110            super.releaseAllResources();
111            synchronized (poolSynch)
112            {
113                if (!poolMap.isEmpty())
114                {
115                    Collection pools = poolMap.values();
116                    Iterator iterator = pools.iterator();
117                    ObjectPool op = null;
118                    while (iterator.hasNext())
119                    {
120                        try
121                        {
122                            op = (ObjectPool) iterator.next();
123                            op.close();
124                        }
125                        catch (Exception e)
126                        {
127                            log.error("Exception occured while closing ObjectPool " + op, e);
128                        }
129                    }
130                    poolMap.clear();
131                }
132                dsMap.clear();
133            }
134        }
135    
136        /**
137         * Returns the DBCP DataSource for the specified connection descriptor,
138         * after creating a new DataSource if needed.
139         * @param jcd the descriptor for which to return a DataSource
140         * @return a DataSource, after creating a new pool if needed.
141         * Guaranteed to never be null.
142         * @throws LookupException if pool is not in cache and cannot be created
143         */
144        protected DataSource getDataSource(JdbcConnectionDescriptor jcd)
145                throws LookupException
146        {
147            final PBKey key = jcd.getPBKey();
148            DataSource ds = (DataSource) dsMap.get(key);
149            if (ds == null)
150            {
151                // Found no pool for PBKey
152                try
153                {
154                    synchronized (poolSynch)
155                    {
156                        // Setup new object pool
157                        ObjectPool pool = setupPool(jcd);
158                        poolMap.put(key, pool);
159                        // Wrap the underlying object pool as DataSource
160                        ds = wrapAsDataSource(jcd, pool);
161                        dsMap.put(key, ds);
162                    }
163                }
164                catch (Exception e)
165                {
166                    log.error("Could not setup DBCP DataSource for " + jcd, e);
167                    throw new LookupException(e);
168                }
169            }
170            return ds;
171        }
172    
173        /**
174         * Returns a new ObjectPool for the specified connection descriptor.
175         * Override this method to setup your own pool.
176         * @param jcd the connection descriptor for which to set up the pool
177         * @return a newly created object pool
178         */
179        protected ObjectPool setupPool(JdbcConnectionDescriptor jcd)
180        {
181            log.info("Create new ObjectPool for DBCP connections:" + jcd);
182    
183            try
184            {
185                ClassHelper.newInstance(jcd.getDriver());
186            }
187            catch (InstantiationException e)
188            {
189                log.fatal("Unable to instantiate the driver class: " + jcd.getDriver() + " in ConnectionFactoryDBCImpl!" , e);
190            }
191            catch (IllegalAccessException e)
192            {
193                log.fatal("IllegalAccessException while instantiating the driver class: " + jcd.getDriver() + " in ConnectionFactoryDBCImpl!" , e);
194            }
195            catch (ClassNotFoundException e)
196            {
197                log.fatal("Could not find the driver class : " + jcd.getDriver() + " in ConnectionFactoryDBCImpl!" , e);
198            }
199    
200            // Get the configuration for the connection pool
201            GenericObjectPool.Config conf = jcd.getConnectionPoolDescriptor().getObjectPoolConfig();
202    
203            // Get the additional abandoned configuration
204            AbandonedConfig ac = jcd.getConnectionPoolDescriptor().getAbandonedConfig();
205    
206            // Create the ObjectPool that serves as the actual pool of connections.
207            final ObjectPool connectionPool = createConnectionPool(conf, ac);
208    
209            // Create a DriverManager-based ConnectionFactory that
210            // the connectionPool will use to create Connection instances
211            final org.apache.commons.dbcp.ConnectionFactory connectionFactory;
212            connectionFactory = createConnectionFactory(jcd);
213    
214            // Create PreparedStatement object pool (if any)
215            KeyedObjectPoolFactory statementPoolFactory = createStatementPoolFactory(jcd);
216    
217            // Set validation query and auto-commit mode
218            final String validationQuery;
219            final boolean defaultAutoCommit;
220            final boolean defaultReadOnly = false;
221            validationQuery = jcd.getConnectionPoolDescriptor().getValidationQuery();
222            defaultAutoCommit = (jcd.getUseAutoCommit() != JdbcConnectionDescriptor.AUTO_COMMIT_SET_FALSE);
223    
224            //
225            // Now we'll create the PoolableConnectionFactory, which wraps
226            // the "real" Connections created by the ConnectionFactory with
227            // the classes that implement the pooling functionality.
228            //
229            final PoolableConnectionFactory poolableConnectionFactory;
230            poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory,
231                    connectionPool,
232                    statementPoolFactory,
233                    validationQuery,
234                    defaultReadOnly,
235                    defaultAutoCommit,
236                    ac);
237            return poolableConnectionFactory.getPool();
238        }
239    
240        protected ObjectPool createConnectionPool(GenericObjectPool.Config config,
241                                                  AbandonedConfig ac)
242        {
243            final GenericObjectPool connectionPool;
244            final boolean doRemoveAbandoned = ac != null && ac.getRemoveAbandoned();
245    
246            if (doRemoveAbandoned) {
247                connectionPool = new AbandonedObjectPool(null, ac);
248            } else {
249                connectionPool = new GenericObjectPool();
250            }
251            connectionPool.setMaxActive(config.maxActive);
252            connectionPool.setMaxIdle(config.maxIdle);
253            connectionPool.setMinIdle(config.minIdle);
254            connectionPool.setMaxWait(config.maxWait);
255            connectionPool.setTestOnBorrow(config.testOnBorrow);
256            connectionPool.setTestOnReturn(config.testOnReturn);
257            connectionPool.setTimeBetweenEvictionRunsMillis(config.timeBetweenEvictionRunsMillis);
258            connectionPool.setNumTestsPerEvictionRun(config.numTestsPerEvictionRun);
259            connectionPool.setMinEvictableIdleTimeMillis(config.minEvictableIdleTimeMillis);
260            connectionPool.setTestWhileIdle(config.testWhileIdle);
261            return connectionPool;
262        }
263    
264        protected KeyedObjectPoolFactory createStatementPoolFactory(JdbcConnectionDescriptor jcd)
265        {
266            final String platform = jcd.getDbms();
267            if (platform.startsWith("Oracle9i"))
268            {
269                // mkalen: let the platform set Oracle-specific statement pooling
270                return null;
271            }
272    
273            // Set up statement pool, if desired
274            GenericKeyedObjectPoolFactory statementPoolFactory = null;
275            final Properties properties = jcd.getConnectionPoolDescriptor().getDbcpProperties();
276            final String poolStmtParam = properties.getProperty(PARAM_NAME_POOL_STATEMENTS);
277            if (poolStmtParam != null && Boolean.valueOf(poolStmtParam).booleanValue())
278            {
279                int maxOpenPreparedStatements = GenericKeyedObjectPool.DEFAULT_MAX_TOTAL;
280                final String maxOpenPrepStmtString = properties.getProperty(PARAM_NAME_STATEMENT_POOL_MAX_TOTAL);
281                if (maxOpenPrepStmtString != null)
282                {
283                    maxOpenPreparedStatements = Integer.parseInt(maxOpenPrepStmtString);
284                }
285                // Use the same values as Commons DBCP BasicDataSource
286                statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
287                            -1, // unlimited maxActive (per key)
288                            GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
289                            0, // maxWait
290                            1, // maxIdle (per key)
291                            maxOpenPreparedStatements);
292            }
293            return statementPoolFactory;
294        }
295    
296        /**
297         * Wraps the specified object pool for connections as a DataSource.
298         *
299         * @param jcd the OJB connection descriptor for the pool to be wrapped
300         * @param connectionPool the connection pool to be wrapped
301         * @return a DataSource attached to the connection pool.
302         * Connections will be wrapped using DBCP PoolGuard, that will not allow
303         * unwrapping unless the "accessToUnderlyingConnectionAllowed=true" configuration
304         * is specified.
305         */
306        protected DataSource wrapAsDataSource(JdbcConnectionDescriptor jcd,
307                                              ObjectPool connectionPool)
308        {
309            final boolean allowConnectionUnwrap;
310            if (jcd == null)
311            {
312                allowConnectionUnwrap = false;
313            }
314            else
315            {
316                final Properties properties = jcd.getConnectionPoolDescriptor().getDbcpProperties();
317                final String allowConnectionUnwrapParam;
318                allowConnectionUnwrapParam = properties.getProperty(PARAM_NAME_UNWRAP_ALLOWED);
319                allowConnectionUnwrap = allowConnectionUnwrapParam != null &&
320                        Boolean.valueOf(allowConnectionUnwrapParam).booleanValue();
321            }
322            final PoolingDataSource dataSource;
323            dataSource = new PoolingDataSource(connectionPool);
324            dataSource.setAccessToUnderlyingConnectionAllowed(allowConnectionUnwrap);
325    
326            if(jcd != null)
327            {
328                final AbandonedConfig ac = jcd.getConnectionPoolDescriptor().getAbandonedConfig();
329                if (ac.getRemoveAbandoned() && ac.getLogAbandoned()) {
330                    final LoggerWrapperPrintWriter loggerPiggyBack;
331                    loggerPiggyBack = new LoggerWrapperPrintWriter(log, Logger.ERROR);
332                    dataSource.setLogWriter(loggerPiggyBack);
333                }
334            }
335            return dataSource;
336        }
337    
338        /**
339         * Creates a DriverManager-based ConnectionFactory for creating the Connection
340         * instances to feed into the object pool of the specified jcd-alias.
341         * <p>
342         * <b>NB!</b> If you override this method to specify your own ConnectionFactory
343         * you <em>must</em> make sure that you follow OJB's lifecycle contract defined in the
344         * {@link org.apache.ojb.broker.platforms.Platform} API - ie that you call
345         * initializeJdbcConnection when a new Connection is created. For convenience, use
346         * {@link ConnectionFactoryAbstractImpl#initializeJdbcConnection} instead of Platform call.
347         * <p>
348         * The above is automatically true if you re-use the inner class {@link ConPoolFactory}
349         * below and just override this method for additional user-defined "tweaks".
350         *
351         * @param jcd the jdbc-connection-alias for which we are creating a ConnectionFactory
352         * @return a DriverManager-based ConnectionFactory that creates Connection instances
353         * using DriverManager, and that follows the lifecycle contract defined in OJB
354         * {@link org.apache.ojb.broker.platforms.Platform} API.
355         */
356        protected org.apache.commons.dbcp.ConnectionFactory createConnectionFactory(JdbcConnectionDescriptor jcd)
357        {
358            final ConPoolFactory result;
359            final Properties properties = getJdbcProperties(jcd);
360            result = new ConPoolFactory(jcd, properties);
361            return result;
362        }
363    
364        // ----- deprecated methods, to be removed -----
365    
366        /**
367         * mkalen: Left for binary API-compatibility with OJB 1.0.3 (don't break users' factories)
368         * @deprecated since OJB 1.0.4,
369         * please use {@link #createConnectionPool(org.apache.commons.pool.impl.GenericObjectPool.Config, org.apache.commons.dbcp.AbandonedConfig)}
370         */
371        protected ObjectPool createObjectPool(GenericObjectPool.Config config)
372        {
373            return createConnectionPool(config, null);
374        }
375    
376        /**
377         * mkalen: Left for binary API-compatibility with OJB 1.0.3 (don't break users' factories)
378         * @deprecated since OJB 1.0.4,
379         * please use {@link #wrapAsDataSource(org.apache.ojb.broker.metadata.JdbcConnectionDescriptor, org.apache.commons.pool.ObjectPool)}
380         */
381        protected PoolingDataSource createPoolingDataSource(ObjectPool connectionPool)
382        {
383            // mkalen: not a nice cast but we do not want to break signature and it is safe
384            // since any new implementations will not be based on this method and the wrapper-
385            // call here goes to code we control (where we know it's PoolingDataSource)
386            return (PoolingDataSource) wrapAsDataSource(null, connectionPool);
387        }
388    
389        // ----- end deprecated methods -----
390    
391        //**************************************************************************************
392        // Inner classes
393        //************************************************************************************
394    
395        /**
396         * Inner class used as factory for DBCP connection pooling.
397         * Adhers to OJB platform specification by calling platform-specific init methods
398         * on newly created connections.
399         * @see org.apache.ojb.broker.platforms.Platform#initializeJdbcConnection
400         */
401        class ConPoolFactory extends DriverManagerConnectionFactory
402        {
403    
404            private final JdbcConnectionDescriptor jcd;
405    
406            public ConPoolFactory(JdbcConnectionDescriptor jcd, Properties properties)
407            {
408                super(getDbURL(jcd), properties);
409                this.jcd = jcd;
410            }
411    
412            public Connection createConnection() throws SQLException
413            {
414                final Connection conn = super.createConnection();
415                if (conn != null)
416                {
417                    try
418                    {
419                        initializeJdbcConnection(conn, jcd);
420                    }
421                    catch (LookupException e)
422                    {
423                        log.error("Platform dependent initialization of connection failed", e);
424                    }
425                }
426                return conn;
427            }
428    
429        }
430    
431    }