001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. 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: package org.apache.commons.dbcp.cpdsadapter;
019:
020: import java.util.Hashtable;
021: import java.io.PrintWriter;
022: import java.io.Serializable;
023: import java.sql.DriverManager;
024: import java.sql.SQLException;
025: import javax.sql.PooledConnection;
026: import javax.sql.ConnectionPoolDataSource;
027: import javax.naming.Name;
028: import javax.naming.Context;
029: import javax.naming.Referenceable;
030: import javax.naming.spi.ObjectFactory;
031: import javax.naming.Reference;
032: import javax.naming.RefAddr;
033: import javax.naming.StringRefAddr;
034: import javax.naming.NamingException;
035:
036: import org.apache.commons.pool.KeyedObjectPool;
037: import org.apache.commons.pool.impl.GenericKeyedObjectPool;
038:
039: /**
040: * <p>
041: * An adapter for jdbc drivers that do not include an implementation
042: * of {@link javax.sql.ConnectionPoolDataSource}, but still include a
043: * {@link java.sql.DriverManager} implementation.
044: * <code>ConnectionPoolDataSource</code>s are not used within general
045: * applications. They are used by <code>DataSource</code> implementations
046: * that pool <code>Connection</code>s, such as
047: * {@link org.apache.commons.dbcp.datasources.SharedPoolDataSource}. A J2EE
048: * container will normally provide some method of initializing the
049: * <code>ConnectionPoolDataSource</code> whose attributes are presented
050: * as bean getters/setters and then deploying it via JNDI. It is then
051: * available as a source of physical connections to the database, when
052: * the pooling <code>DataSource</code> needs to create a new
053: * physical connection.
054: * </p>
055: *
056: * <p>
057: * Although normally used within a JNDI environment, the DriverAdapterCPDS
058: * can be instantiated and initialized as any bean and then attached
059: * directly to a pooling <code>DataSource</code>.
060: * <code>Jdbc2PoolDataSource</code> can use the
061: * <code>ConnectionPoolDataSource</code> with or without the use of JNDI.
062: * </p>
063: *
064: * <p>
065: * The DriverAdapterCPDS also provides <code>PreparedStatement</code> pooling
066: * which is not generally available in jbdc2
067: * <code>ConnectionPoolDataSource</code> implementation, but is
068: * addressed within the jdbc3 specification. The <code>PreparedStatement</code>
069: * pool in DriverAdapterCPDS has been in the dbcp package for some time, but
070: * it has not undergone extensive testing in the configuration used here.
071: * It should be considered experimental and can be toggled with the
072: * poolPreparedStatements attribute.
073: * </p>
074: *
075: * <p>
076: * The <a href="package-summary.html">package documentation</a> contains an
077: * example using catalina and JNDI. The <a
078: * href="../datasources/package-summary.html">datasources package documentation</a>
079: * shows how to use <code>DriverAdapterCPDS</code> as a source for
080: * <code>Jdbc2PoolDataSource</code> without the use of JNDI.
081: * </p>
082: *
083: * @author John D. McNally
084: * @version $Revision: 500687 $ $Date: 2007-01-27 16:33:47 -0700 (Sat, 27 Jan 2007) $
085: */
086: public class DriverAdapterCPDS implements ConnectionPoolDataSource,
087: Referenceable, Serializable, ObjectFactory {
088:
089: private static final String GET_CONNECTION_CALLED = "A PooledConnection was already requested from this source, "
090: + "further initialization is not allowed.";
091:
092: /** Description */
093: private String description;
094: /** Password */
095: private String password;
096: /** Url name */
097: private String url;
098: /** User name */
099: private String user;
100: /** Driver class name */
101: private String driver;
102:
103: /** Login TimeOut in seconds */
104: private int loginTimeout;
105: /** Log stream */
106: private PrintWriter logWriter = null;
107:
108: // PreparedStatement pool properties
109: private boolean poolPreparedStatements;
110: private int maxActive = 10;
111: private int maxIdle = 10;
112: private int _timeBetweenEvictionRunsMillis = -1;
113: private int _numTestsPerEvictionRun = -1;
114: private int _minEvictableIdleTimeMillis = -1;
115: private int _maxPreparedStatements = -1;
116:
117: private boolean getConnectionCalled = false;
118:
119: /**
120: * Default no-arg constructor for Serialization
121: */
122: public DriverAdapterCPDS() {
123: }
124:
125: /**
126: * Attempt to establish a database connection using the default
127: * user and password.
128: */
129: public PooledConnection getPooledConnection() throws SQLException {
130: return getPooledConnection(getUser(), getPassword());
131: }
132:
133: /**
134: * Attempt to establish a database connection.
135: */
136: public PooledConnection getPooledConnection(String username,
137: String password) throws SQLException {
138: getConnectionCalled = true;
139: /*
140: public GenericKeyedObjectPool(KeyedPoolableObjectFactory factory,
141: int maxActive, byte whenExhaustedAction, long maxWait,
142: int maxIdle, boolean testOnBorrow, boolean testOnReturn,
143: long timeBetweenEvictionRunsMillis,
144: int numTestsPerEvictionRun, long minEvictableIdleTimeMillis,
145: boolean testWhileIdle) {
146: */
147: KeyedObjectPool stmtPool = null;
148: if (isPoolPreparedStatements()) {
149: if (getMaxPreparedStatements() <= 0) {
150: // since there is no limit, create a prepared statement pool with an eviction thread
151: // evictor settings are the same as the connection pool settings.
152: stmtPool = new GenericKeyedObjectPool(null,
153: getMaxActive(),
154: GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0,
155: getMaxIdle(), false, false,
156: getTimeBetweenEvictionRunsMillis(),
157: getNumTestsPerEvictionRun(),
158: getMinEvictableIdleTimeMillis(), false);
159: } else {
160: // since there is limit, create a prepared statement pool without an eviction thread
161: // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared.
162: // see org.apache.commons.pool.impl.GenericKeyedObjectPool.clearOldest method
163: stmtPool = new GenericKeyedObjectPool(null,
164: getMaxActive(),
165: GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0,
166: getMaxIdle(), getMaxPreparedStatements(),
167: false, false, -1, 0, 0, // -1 tells the pool that there should be no eviction thread.
168: false);
169: }
170: }
171: // Workaround for buggy WebLogic 5.1 classloader - ignore the
172: // exception upon first invocation.
173: try {
174: return new PooledConnectionImpl(DriverManager
175: .getConnection(getUrl(), username, password),
176: stmtPool);
177: } catch (ClassCircularityError e) {
178: return new PooledConnectionImpl(DriverManager
179: .getConnection(getUrl(), username, password),
180: stmtPool);
181: }
182: }
183:
184: // ----------------------------------------------------------------------
185: // Referenceable implementation
186:
187: /**
188: * <CODE>Referenceable</CODE> implementation.
189: */
190: public Reference getReference() throws NamingException {
191: // this class implements its own factory
192: String factory = getClass().getName();
193:
194: Reference ref = new Reference(getClass().getName(), factory,
195: null);
196:
197: ref.add(new StringRefAddr("description", getDescription()));
198: ref.add(new StringRefAddr("driver", getDriver()));
199: ref.add(new StringRefAddr("loginTimeout", String
200: .valueOf(getLoginTimeout())));
201: ref.add(new StringRefAddr("password", getPassword()));
202: ref.add(new StringRefAddr("user", getUser()));
203: ref.add(new StringRefAddr("url", getUrl()));
204:
205: ref.add(new StringRefAddr("poolPreparedStatements", String
206: .valueOf(isPoolPreparedStatements())));
207: ref.add(new StringRefAddr("maxActive", String
208: .valueOf(getMaxActive())));
209: ref.add(new StringRefAddr("maxIdle", String
210: .valueOf(getMaxIdle())));
211: ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis",
212: String.valueOf(getTimeBetweenEvictionRunsMillis())));
213: ref.add(new StringRefAddr("numTestsPerEvictionRun", String
214: .valueOf(getNumTestsPerEvictionRun())));
215: ref.add(new StringRefAddr("minEvictableIdleTimeMillis", String
216: .valueOf(getMinEvictableIdleTimeMillis())));
217: ref.add(new StringRefAddr("maxPreparedStatements", String
218: .valueOf(getMaxPreparedStatements())));
219:
220: return ref;
221: }
222:
223: // ----------------------------------------------------------------------
224: // ObjectFactory implementation
225:
226: /**
227: * implements ObjectFactory to create an instance of this class
228: */
229: public Object getObjectInstance(Object refObj, Name name,
230: Context context, Hashtable env) throws Exception {
231: // The spec says to return null if we can't create an instance
232: // of the reference
233: DriverAdapterCPDS cpds = null;
234: if (refObj instanceof Reference) {
235: Reference ref = (Reference) refObj;
236: if (ref.getClassName().equals(getClass().getName())) {
237: RefAddr ra = ref.get("description");
238: if (ra != null && ra.getContent() != null) {
239: setDescription(ra.getContent().toString());
240: }
241:
242: ra = ref.get("driver");
243: if (ra != null && ra.getContent() != null) {
244: setDriver(ra.getContent().toString());
245: }
246: ra = ref.get("url");
247: if (ra != null && ra.getContent() != null) {
248: setUrl(ra.getContent().toString());
249: }
250: ra = ref.get("user");
251: if (ra != null && ra.getContent() != null) {
252: setUser(ra.getContent().toString());
253: }
254: ra = ref.get("password");
255: if (ra != null && ra.getContent() != null) {
256: setPassword(ra.getContent().toString());
257: }
258:
259: ra = ref.get("poolPreparedStatements");
260: if (ra != null && ra.getContent() != null) {
261: setPoolPreparedStatements(Boolean.getBoolean(ra
262: .getContent().toString()));
263: }
264: ra = ref.get("maxActive");
265: if (ra != null && ra.getContent() != null) {
266: setMaxActive(Integer.parseInt(ra.getContent()
267: .toString()));
268: }
269:
270: ra = ref.get("maxIdle");
271: if (ra != null && ra.getContent() != null) {
272: setMaxIdle(Integer.parseInt(ra.getContent()
273: .toString()));
274: }
275:
276: ra = ref.get("timeBetweenEvictionRunsMillis");
277: if (ra != null && ra.getContent() != null) {
278: setTimeBetweenEvictionRunsMillis(Integer
279: .parseInt(ra.getContent().toString()));
280: }
281:
282: ra = ref.get("numTestsPerEvictionRun");
283: if (ra != null && ra.getContent() != null) {
284: setNumTestsPerEvictionRun(Integer.parseInt(ra
285: .getContent().toString()));
286: }
287:
288: ra = ref.get("minEvictableIdleTimeMillis");
289: if (ra != null && ra.getContent() != null) {
290: setMinEvictableIdleTimeMillis(Integer.parseInt(ra
291: .getContent().toString()));
292: }
293: ra = ref.get("maxPreparedStatements");
294: if (ra != null && ra.getContent() != null) {
295: setMaxPreparedStatements(Integer.parseInt(ra
296: .getContent().toString()));
297: }
298:
299: cpds = this ;
300: }
301: }
302: return cpds;
303: }
304:
305: /**
306: * Throws an IllegalStateException, if a PooledConnection has already
307: * been requested.
308: */
309: private void assertInitializationAllowed()
310: throws IllegalStateException {
311: if (getConnectionCalled) {
312: throw new IllegalStateException(GET_CONNECTION_CALLED);
313: }
314: }
315:
316: // ----------------------------------------------------------------------
317: // Properties
318:
319: /**
320: * Get the value of description. This property is here for use by
321: * the code which will deploy this datasource. It is not used
322: * internally.
323: *
324: * @return value of description.
325: */
326: public String getDescription() {
327: return description;
328: }
329:
330: /**
331: * Set the value of description. This property is here for use by
332: * the code which will deploy this datasource. It is not used
333: * internally.
334: *
335: * @param v Value to assign to description.
336: */
337: public void setDescription(String v) {
338: this .description = v;
339: }
340:
341: /**
342: * Get the value of password for the default user.
343: * @return value of password.
344: */
345: public String getPassword() {
346: return password;
347: }
348:
349: /**
350: * Set the value of password for the default user.
351: * @param v Value to assign to password.
352: */
353: public void setPassword(String v) {
354: assertInitializationAllowed();
355: this .password = v;
356: }
357:
358: /**
359: * Get the value of url used to locate the database for this datasource.
360: * @return value of url.
361: */
362: public String getUrl() {
363: return url;
364: }
365:
366: /**
367: * Set the value of url used to locate the database for this datasource.
368: * @param v Value to assign to url.
369: */
370: public void setUrl(String v) {
371: assertInitializationAllowed();
372: this .url = v;
373: }
374:
375: /**
376: * Get the value of default user (login or username).
377: * @return value of user.
378: */
379: public String getUser() {
380: return user;
381: }
382:
383: /**
384: * Set the value of default user (login or username).
385: * @param v Value to assign to user.
386: */
387: public void setUser(String v) {
388: assertInitializationAllowed();
389: this .user = v;
390: }
391:
392: /**
393: * Get the driver classname.
394: * @return value of driver.
395: */
396: public String getDriver() {
397: return driver;
398: }
399:
400: /**
401: * Set the driver classname. Setting the driver classname cause the
402: * driver to be registered with the DriverManager.
403: * @param v Value to assign to driver.
404: */
405: public void setDriver(String v) throws ClassNotFoundException {
406: assertInitializationAllowed();
407: this .driver = v;
408: // make sure driver is registered
409: Class.forName(v);
410: }
411:
412: /**
413: * Gets the maximum time in seconds that this data source can wait
414: * while attempting to connect to a database. NOT USED.
415: */
416: public int getLoginTimeout() {
417: return loginTimeout;
418: }
419:
420: /**
421: * Get the log writer for this data source. NOT USED.
422: */
423: public PrintWriter getLogWriter() {
424: return logWriter;
425: }
426:
427: /**
428: * Sets the maximum time in seconds that this data source will wait
429: * while attempting to connect to a database. NOT USED.
430: */
431: public void setLoginTimeout(int seconds) {
432: loginTimeout = seconds;
433: }
434:
435: /**
436: * Set the log writer for this data source. NOT USED.
437: */
438: public void setLogWriter(java.io.PrintWriter out) {
439: logWriter = out;
440: }
441:
442: // ------------------------------------------------------------------
443: // PreparedStatement pool properties
444:
445: /**
446: * Flag to toggle the pooling of <code>PreparedStatement</code>s
447: * @return value of poolPreparedStatements.
448: */
449: public boolean isPoolPreparedStatements() {
450: return poolPreparedStatements;
451: }
452:
453: /**
454: * Flag to toggle the pooling of <code>PreparedStatement</code>s
455: * @param v true to pool statements.
456: */
457: public void setPoolPreparedStatements(boolean v) {
458: assertInitializationAllowed();
459: this .poolPreparedStatements = v;
460: }
461:
462: /**
463: * The maximum number of active statements that can be allocated from
464: * this pool at the same time, or non-positive for no limit.
465: */
466: public int getMaxActive() {
467: return (this .maxActive);
468: }
469:
470: /**
471: * The maximum number of active statements that can be allocated from
472: * this pool at the same time, or non-positive for no limit.
473: */
474: public void setMaxActive(int maxActive) {
475: assertInitializationAllowed();
476: this .maxActive = maxActive;
477: }
478:
479: /**
480: * The maximum number of statements that can remain idle in the
481: * pool, without extra ones being released, or negative for no limit.
482: */
483: public int getMaxIdle() {
484: return (this .maxIdle);
485: }
486:
487: /**
488: * The maximum number of statements that can remain idle in the
489: * pool, without extra ones being released, or negative for no limit.
490: */
491: public void setMaxIdle(int maxIdle) {
492: assertInitializationAllowed();
493: this .maxIdle = maxIdle;
494: }
495:
496: /**
497: * Returns the number of milliseconds to sleep between runs of the
498: * idle object evictor thread.
499: * When non-positive, no idle object evictor thread will be
500: * run.
501: *
502: * *see #setTimeBetweenEvictionRunsMillis
503: */
504: public int getTimeBetweenEvictionRunsMillis() {
505: return _timeBetweenEvictionRunsMillis;
506: }
507:
508: /**
509: * Sets the number of milliseconds to sleep between runs of the
510: * idle object evictor thread.
511: * When non-positive, no idle object evictor thread will be
512: * run.
513: *
514: * *see #getTimeBetweenEvictionRunsMillis
515: */
516: public void setTimeBetweenEvictionRunsMillis(
517: int timeBetweenEvictionRunsMillis) {
518: assertInitializationAllowed();
519: _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
520: }
521:
522: /**
523: * Returns the number of statements to examine during each run of the
524: * idle object evictor thread (if any).
525: *
526: * *see #setNumTestsPerEvictionRun
527: * *see #setTimeBetweenEvictionRunsMillis
528: */
529: public int getNumTestsPerEvictionRun() {
530: return _numTestsPerEvictionRun;
531: }
532:
533: /**
534: * Sets the number of statements to examine during each run of the
535: * idle object evictor thread (if any).
536: * <p>
537: * When a negative value is supplied, <tt>ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun})</tt>
538: * tests will be run. I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the
539: * idle objects will be tested per run.
540: *
541: * *see #getNumTestsPerEvictionRun
542: * *see #setTimeBetweenEvictionRunsMillis
543: */
544: public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
545: assertInitializationAllowed();
546: _numTestsPerEvictionRun = numTestsPerEvictionRun;
547: }
548:
549: /**
550: * Returns the minimum amount of time a statement may sit idle in the pool
551: * before it is eligible for eviction by the idle object evictor
552: * (if any).
553: *
554: * *see #setMinEvictableIdleTimeMillis
555: * *see #setTimeBetweenEvictionRunsMillis
556: */
557: public int getMinEvictableIdleTimeMillis() {
558: return _minEvictableIdleTimeMillis;
559: }
560:
561: /**
562: * Sets the minimum amount of time a statement may sit idle in the pool
563: * before it is eligable for eviction by the idle object evictor
564: * (if any).
565: * When non-positive, no objects will be evicted from the pool
566: * due to idle time alone.
567: *
568: * *see #getMinEvictableIdleTimeMillis
569: * *see #setTimeBetweenEvictionRunsMillis
570: */
571: public void setMinEvictableIdleTimeMillis(
572: int minEvictableIdleTimeMillis) {
573: assertInitializationAllowed();
574: _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
575: }
576:
577: /**
578: * Returns the maximun number of prepared statements.
579: *
580: * @return maxPrepartedStatements value
581: * @since 1.2.2
582: */
583: public int getMaxPreparedStatements() {
584: return _maxPreparedStatements;
585: }
586:
587: /**
588: * Sets the maximum number of prepared statements.
589: * @param maxPreparedStatements the new maximum number of prepared
590: * statements
591: *
592: * @since 1.2.2
593: */
594: public void setMaxPreparedStatements(int maxPreparedStatements) {
595: _maxPreparedStatements = maxPreparedStatements;
596: }
597: }
|