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