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 }