Coverage Report - org.apache.ojb.broker.accesslayer.ConnectionFactoryDBCPImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
ConnectionFactoryDBCPImpl
N/A
N/A
3
ConnectionFactoryDBCPImpl$ConPoolFactory
N/A
N/A
3
 
 1  
 package org.apache.ojb.broker.accesslayer;
 2  
 
 3  
 /* Copyright 2002-2005 The Apache Software Foundation
 4  
  *
 5  
  * Licensed under the Apache License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 
 18  
 import org.apache.commons.dbcp.AbandonedConfig;
 19  
 import org.apache.commons.dbcp.AbandonedObjectPool;
 20  
 import org.apache.commons.dbcp.DriverManagerConnectionFactory;
 21  
 import org.apache.commons.dbcp.PoolableConnectionFactory;
 22  
 import org.apache.commons.dbcp.PoolingDataSource;
 23  
 import org.apache.commons.pool.KeyedObjectPoolFactory;
 24  
 import org.apache.commons.pool.ObjectPool;
 25  
 import org.apache.commons.pool.impl.GenericKeyedObjectPool;
 26  
 import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
 27  
 import org.apache.commons.pool.impl.GenericObjectPool;
 28  
 import org.apache.ojb.broker.PBKey;
 29  
 import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
 30  
 import org.apache.ojb.broker.util.ClassHelper;
 31  
 import org.apache.ojb.broker.util.logging.Logger;
 32  
 import org.apache.ojb.broker.util.logging.LoggerFactory;
 33  
 import org.apache.ojb.broker.util.logging.LoggerWrapperPrintWriter;
 34  
 
 35  
 import javax.sql.DataSource;
 36  
 import java.sql.Connection;
 37  
 import java.sql.SQLException;
 38  
 import java.util.Collection;
 39  
 import java.util.Collections;
 40  
 import java.util.HashMap;
 41  
 import java.util.Iterator;
 42  
 import java.util.Map;
 43  
 import java.util.Properties;
 44  
 
 45  
 /**
 46  
  * ConnectionFactory implementation using Commons DBCP and Commons Pool API
 47  
  * to pool connections.
 48  
  *
 49  
  * Based on a proposal of Dirk Verbeek - Thanks.
 50  
  *
 51  
  * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
 52  
  * @version $Id: ConnectionFactoryDBCPImpl.java,v 1.1 2007-08-24 22:17:30 ewestfal Exp $
 53  
  * @see <a href="http://jakarta.apache.org/commons/pool/">Commons Pool Website</a>
 54  
  * @see <a href="http://jakarta.apache.org/commons/dbcp/">Commons DBCP Website</a>
 55  
  */
 56  
 public class ConnectionFactoryDBCPImpl extends ConnectionFactoryAbstractImpl
 57  
 {
 58  
 
 59  
     public static final String PARAM_NAME_UNWRAP_ALLOWED = "accessToUnderlyingConnectionAllowed";
 60  
     public static final String PARAM_NAME_POOL_STATEMENTS = "poolPreparedStatements";
 61  
     public static final String PARAM_NAME_STATEMENT_POOL_MAX_TOTAL = "maxOpenPreparedStatements";
 62  
 
 63  
     private Logger log = LoggerFactory.getLogger(ConnectionFactoryDBCPImpl.class);
 64  
 
 65  
     /** Key=PBKey, value=ObjectPool. */
 66  
     private Map poolMap = Collections.synchronizedMap(new HashMap());
 67  
     /** Key=PBKey, value=PoolingDataSource. */
 68  
     private Map dsMap = Collections.synchronizedMap(new HashMap());
 69  
     /** Synchronize object for operations not synchronized on Map only. */
 70  
     private final Object poolSynch = new Object();
 71  
 
 72  
     public Connection checkOutJdbcConnection(JdbcConnectionDescriptor jcd) throws LookupException
 73  
     {
 74  
         final DataSource ds = getDataSource(jcd);
 75  
 
 76  
         // Returned DS is never null, exception are logged by getDataSource and gets
 77  
         // re-thrown here since we don't catch them
 78  
 
 79  
         Connection conn;
 80  
         try
 81  
         {
 82  
             conn = ds.getConnection();
 83  
         }
 84  
         catch (SQLException e)
 85  
         {
 86  
             throw new LookupException("Could not get connection from DBCP DataSource", e);
 87  
         }
 88  
         return conn;
 89  
     }
 90  
 
 91  
     public void releaseJdbcConnection(JdbcConnectionDescriptor jcd, Connection con)
 92  
             throws LookupException
 93  
     {
 94  
         try
 95  
         {
 96  
             // We are using datasources, thus close returns connection to pool
 97  
             con.close();
 98  
         }
 99  
         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  
 }