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