View Javadoc
1 /* ==================================================================== 2 * License: 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in 13 * the documentation and/or other materials provided with the 14 * distribution. 15 * 16 * 3. The end-user documentation included with the redistribution, 17 * if any, must include the following acknowledgment: 18 * "This product includes software developed by 19 * Robert Half International (http://www.rhi.com/)." 20 * Alternately, this acknowledgment may appear in the software itself, 21 * if and wherever such third-party acknowledgments normally appear. 22 * 23 * 4. The names "Parc", "RHI", and "Robert Half International" must 24 * not be used to endorse or promote products derived from this 25 * software without prior written permission. For written 26 * permission, please contact pete.mckinstry@rhi.com. 27 * 28 * 5. Products derived from this software may not be called "PARC", 29 * nor may "PARC" appear in their name, without prior written 30 * permission of Robert Half International. 31 * 32 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 33 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 34 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 35 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 36 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 37 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 38 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 39 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 40 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 41 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 42 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 43 * SUCH DAMAGE. 44 * ==================================================================== 45 * 46 */ 47 package com.rhi.architecture.db; 48 49 import com.rhi.architecture.logging.LogUtil; 50 import com.rhi.architecture.logging.Logger; 51 import com.rhi.architecture.config.ConfigurationException; 52 import com.rhi.architecture.resource.Resource; 53 54 import java.util.*; 55 56 import javax.sql.DataSource; 57 58 import org.apache.commons.dbcp.ConnectionFactory; 59 import org.apache.commons.dbcp.DriverManagerConnectionFactory; 60 import org.apache.commons.dbcp.PoolableConnectionFactory; 61 import org.apache.commons.pool.KeyedObjectPoolFactory; 62 import org.apache.commons.pool.impl.GenericObjectPool; 63 import org.apache.commons.pool.impl.StackKeyedObjectPoolFactory; 64 65 /*** 66 * Factory for creating DataSources that pool database connections. 67 * Encapsulates creation of DataSource so that user deals w/ DataSource 68 * objects in an entirely standard way. The DataSource returned is 69 * a 100% compliant DataSource that pool DB connections for the user. 70 * In addition, the factory provides a static reference to the 71 * DataSources so that only 1 will be created per JVM. Multiple DBs 72 * can be connected to by providing different ResourceBundle filenames 73 * to the getBundle() method. The filename makes the bundle unique. 74 * 75 * @author Pete McKinstry 76 * @copyright 2002, Robert Half Int'l., Inc. All rights reserved. 77 * 78 * @since 1.0 79 */ 80 public class DataSourceFactory implements Resource { 81 82 /*** 83 * the configuration key used by the ResourceContext to look for & return 84 * the configured DataSourceFactory 85 */ 86 public static final String KEY = "Resource.DataSourceFactory"; 87 88 /*** 89 * config key for optional sql string to be run upon new Connection checkout. 90 */ 91 public static final String INIT_SQL_KEY = "DataSourceFactory.INIT_SQL"; 92 93 /*** 94 * the max size of the connection pool 95 */ 96 public static final String MAX_SIZE_KEY = "DataSourceFactory.maxSize"; 97 private static final String DEFAULT_MAX_SIZE = "10"; 98 /*** 99 * the max wait for a connection before timing out & throwing an exception 100 */ 101 public static final String MAX_WAIT_KEY = "DataSourceFactory.maxWait"; 102 private static final String DEFAULT_MAX_WAIT = "-1"; /* block forever */ 103 /*** 104 * the max idle time for a connection in the pool before it is closed. 105 */ 106 public static final String MAX_IDLE_KEY = "maxidle"; 107 private static final String DEFAULT_MAX_IDLE = "5"; 108 /*** 109 * the initial size of the connection pool. (# of connections checked out 110 * immediately) 111 */ 112 public static final String INIT_SIZE_KEY = "initsize"; 113 private static final String DEFAULT_INIT_SIZE = "1"; 114 /*** 115 * max number of Statements to be stored per connection. 116 */ 117 public static final String MAX_STMTS_KEY = 118 "DataSourceFactory.maxStatements"; 119 private static final String DEFAULT_MAX_STMTS = "25"; 120 121 private static final String DATA_SOURCE_NAME_KEY = "DataSource."; 122 /*** 123 * the default datasource name prefix. 124 */ 125 public static final String DEFAULT_DATA_SOURCE_NAME = "default"; 126 127 /*** 128 * the config key for the DB url. 129 */ 130 public static final String DB_URL_KEY = "db_url"; 131 /*** 132 * the config key for the jdbc driver name. 133 */ 134 public static final String DRIVER_KEY = "driver"; 135 /*** 136 * the config key for the db user name. 137 */ 138 public static final String USERNAME_KEY = "username"; 139 /*** 140 * the config key for the db password. 141 */ 142 public static final String PASSWORD_KEY = "password"; 143 144 private static Logger log = null; 145 146 // for handling thread safety 147 private static Object lock = new Object(); 148 149 private static HashMap sources = new HashMap(); 150 151 private byte action = GenericObjectPool.WHEN_EXHAUSTED_BLOCK; 152 153 /*** 154 * Default Constructor 155 * 156 * @since 1.0 157 */ 158 public DataSourceFactory() { 159 super(); 160 } 161 162 163 /*** 164 * No initialization required. 165 * @param p 166 */ 167 public void init(Properties p) { 168 try { 169 log = LogUtil.getLogger(); 170 } 171 catch (ConfigurationException e) { 172 log = LogUtil.getDefaultLogger(); 173 } 174 175 Enumeration pnames = p.propertyNames(); 176 while ( pnames.hasMoreElements() ) { 177 String pname = ( String )pnames.nextElement(); 178 if ( ! pname.startsWith( DATA_SOURCE_NAME_KEY ) ) { 179 continue; 180 } 181 182 String key = getDataSourceName( pname ); 183 sources.put( key, createDataSource( p, key ) ); 184 log.info( " created DataSource [ " + key + " ] " ); 185 } 186 } 187 188 /*** 189 * DataSource.web.properties as property name should give 'web' 190 * as data source name 191 * @param pname 192 * @return String 193 */ 194 private static String getDataSourceName( String pname ) { 195 int firstDot = pname.indexOf( "." ); 196 int lastDot = pname.lastIndexOf( "." ); // optional 197 if ( lastDot == firstDot ) { 198 lastDot = pname.length(); 199 } 200 return pname.substring( firstDot + 1, lastDot ); 201 } 202 203 /*** 204 * create new datasource from provided properties file. Extracts 205 * info from properties file & calls buildDataSource. 206 * @param props 207 * @param dsName 208 * @return DataSource 209 * @throws ConfigurationException 210 * @since 1.1 211 */ 212 private DataSource createDataSource( Properties props, String dsName ) 213 throws ConfigurationException { 214 log.debug("Creating new DataSource."); 215 216 int maxSize = -1; 217 long maxWait = -1; 218 int maxIdle = -1; 219 int initSize = -1; 220 int maxStatements = -1; 221 222 String db_url = ""; 223 String driver = ""; 224 String username = ""; 225 String password = ""; 226 String sql = null; 227 228 try { 229 String maxSizeStr = 230 props.getProperty(MAX_SIZE_KEY, DEFAULT_MAX_SIZE); 231 maxSize = Integer.parseInt(maxSizeStr); 232 String maxWaitStr = 233 props.getProperty(MAX_WAIT_KEY, DEFAULT_MAX_WAIT); 234 maxWait = Integer.parseInt(maxWaitStr); 235 // default maxIdle to maxSize 236 // having maxIdle less than maxSize could potentially 237 // cause the driver to exceed max connections in database. 238 maxIdle = maxSize; 239 String initSizeStr = 240 props.getProperty(INIT_SIZE_KEY, DEFAULT_INIT_SIZE); 241 initSize = Integer.parseInt(initSizeStr); 242 String maxStatementsStr = 243 props.getProperty(MAX_STMTS_KEY, DEFAULT_MAX_STMTS); 244 maxStatements = Integer.parseInt(maxStatementsStr); 245 246 db_url = getProperty( props, dsName, DB_URL_KEY ); 247 driver = getProperty( props, dsName, DRIVER_KEY ); 248 username = getProperty( props, dsName, USERNAME_KEY ); 249 password = getProperty( props, dsName, PASSWORD_KEY ); 250 sql = getProperty( props, dsName, INIT_SQL_KEY ); 251 252 Class driverClass = Class.forName(driver); 253 log.debug("jdbc driver loaded. name = [" + driverClass.getName() + "]"); 254 } 255 catch (NumberFormatException nfe) { 256 log.error("NumberFormatException. e = " + nfe); 257 throw new ConfigurationException(nfe.toString()); 258 } 259 catch (ClassNotFoundException cnfe) { 260 log.error(cnfe.toString()); 261 throw new ConfigurationException(cnfe.toString()); 262 } 263 264 DataSource dataSource = 265 buildDataSource( 266 maxSize, 267 maxWait, 268 maxIdle, 269 initSize, 270 maxStatements, 271 db_url, 272 username, 273 password, 274 sql); 275 return dataSource; 276 } 277 278 /*** 279 * helper method for retrieving a property for the specific data source. 280 * 281 * @param p - the configuration information. 282 * @param ds - the datasource name. 283 * @param attribute - the attribute name which is being requested. 284 * @return String - the value of the request attribute. 285 */ 286 private String getProperty( Properties p, String ds, String attribute ) 287 { 288 return p.getProperty( DATA_SOURCE_NAME_KEY + ds + "." + attribute ); 289 } 290 291 /*** 292 * build data source. 293 * @param maxSize 294 * @param maxWait 295 * @param maxIdle 296 * @param initSize 297 * @param maxStatements 298 * @param db_url 299 * @param username 300 * @param password 301 * @param sql 302 * @return 303 * @throws ConfigurationException 304 */ 305 private DataSource buildDataSource( 306 int maxSize, 307 long maxWait, 308 int maxIdle, 309 int initSize, 310 int maxStatements, 311 String db_url, 312 String username, 313 String password, 314 String sql) 315 throws ConfigurationException { 316 // Pool for storing Connection objects. 317 GenericObjectPool pool = new GenericObjectPool(null); 318 pool.setMaxActive(maxSize); 319 pool.setMaxIdle(maxIdle); 320 pool.setMaxWait(maxWait); 321 pool.setWhenExhaustedAction(action); 322 // no initSize available for GenericObjectPools. 323 324 // Factory to create Connections. 325 ConnectionFactory conFactory = 326 new DriverManagerConnectionFactory(db_url, username, password); 327 // Wrapper factory that allows execution of initialization SQL 328 ConnectionFactory initFactory = 329 new InitializingConnectionFactory(conFactory, sql); 330 331 // Pool for prepared statements. 332 KeyedObjectPoolFactory kopf = 333 new StackKeyedObjectPoolFactory(null, maxStatements, 0); 334 335 // PoolableConnectionFactory 336 try { 337 PoolableConnectionFactory poolFactory = 338 new PoolableConnectionFactory( 339 initFactory, 340 pool, 341 kopf, 342 null, 343 /* validation query */ 344 false, /* default read only */ 345 true /* default auto-commit */ 346 ); 347 log.debug("poolableConnectionFactory built = " + 348 poolFactory.getClass().getName()); 349 } 350 // I consider this really bad form for a number of reasons. 351 // 1) It's catching a java.lang.Exception --- the most 352 // general possible class. That leaves the developer using the 353 // package will little choice but to fail absolutely when it's 354 // thrown. 355 // 2) The factory constructor is the method that throws 356 // it. You really should not do things in your constructor 357 // that require throwing an exception. 358 // 3) there is no reason that it can't throw a 359 // IllegalStateException, which is the only concrete exception 360 // possible. But it chooses to throw a higher level class 361 // limiting the visibility of the developer to the details of 362 // the problem 363 // Note: I can't really fix this because i would never be able 364 // to use the apache project out of the box again. So i just 365 // follow principle #2. Fail drastically & immediately. 366 catch (Exception e) { 367 log.error( 368 "Configuration error. PoolableConnectionFactory" 369 + "could not be created successfully."); 370 throw new ConfigurationException(e.toString()); 371 } 372 373 // Finally, the actual DataSource. (our own version w/ a get 374 // method for pool retrieval) 375 PoolingDataSource dataSource = 376 new com.rhi.architecture.db.PoolingDataSource(pool); 377 378 return dataSource; 379 } 380 381 /*** 382 * retrieve a datsource by name. 383 * @param dsName 384 * @return DataSource 385 */ 386 public DataSource getDataSource( String dsName ) { 387 return ( DataSource )sources.get( dsName ); 388 } 389 390 /*** 391 * Retrieve the default datasource. This is for backward compatibility, but 392 * the getDataSource(String name) version is preferred. 393 * 394 * @return DataSource. 395 */ 396 public DataSource getDataSource() { 397 return getDataSource( DEFAULT_DATA_SOURCE_NAME ); 398 } 399 400 /*** 401 * Cleanup the object pool that provides the connections. 402 * 403 * @since 1.0 404 */ 405 public void close() { 406 try { 407 synchronized (lock) { 408 Collection dataSourceCollection = sources.values(); 409 Iterator iter = dataSourceCollection.iterator(); 410 while (iter.hasNext()) { 411 com.rhi.architecture.db.PoolingDataSource ds = 412 (com.rhi.architecture.db.PoolingDataSource) iter 413 .next(); 414 ds.getPool().close(); 415 } 416 sources.clear(); 417 } 418 } 419 catch (Exception e) { 420 log.error("exception caught = " + e); 421 } 422 } 423 424 /*** 425 * Sets the action to be used when the pool is exhausted. This method is 426 * mostly useful for testing where you may want to fail immediately if 427 * the pool is exhausted 428 * 429 * @param action 430 */ 431 public void setAction(byte action) { 432 this.action = action; 433 } 434 435 }

This page was automatically generated by Maven