1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.dbcp.cpdsadapter;
19
20 import java.util.Hashtable;
21 import java.util.Properties;
22 import java.io.PrintWriter;
23 import java.io.Serializable;
24 import java.sql.DriverManager;
25 import java.sql.SQLException;
26 import javax.sql.PooledConnection;
27 import javax.sql.ConnectionPoolDataSource;
28 import javax.naming.Name;
29 import javax.naming.Context;
30 import javax.naming.Referenceable;
31 import javax.naming.spi.ObjectFactory;
32 import javax.naming.Reference;
33 import javax.naming.RefAddr;
34 import javax.naming.StringRefAddr;
35 import javax.naming.NamingException;
36
37 import org.apache.commons.pool.KeyedObjectPool;
38 import org.apache.commons.pool.impl.GenericKeyedObjectPool;
39
40 /**
41 * <p>
42 * An adapter for jdbc drivers that do not include an implementation
43 * of {@link javax.sql.ConnectionPoolDataSource}, but still include a
44 * {@link java.sql.DriverManager} implementation.
45 * <code>ConnectionPoolDataSource</code>s are not used within general
46 * applications. They are used by <code>DataSource</code> implementations
47 * that pool <code>Connection</code>s, such as
48 * {@link org.apache.commons.dbcp.datasources.SharedPoolDataSource}. A J2EE
49 * container will normally provide some method of initializing the
50 * <code>ConnectionPoolDataSource</code> whose attributes are presented
51 * as bean getters/setters and then deploying it via JNDI. It is then
52 * available as a source of physical connections to the database, when
53 * the pooling <code>DataSource</code> needs to create a new
54 * physical connection.
55 * </p>
56 *
57 * <p>
58 * Although normally used within a JNDI environment, the DriverAdapterCPDS
59 * can be instantiated and initialized as any bean and then attached
60 * directly to a pooling <code>DataSource</code>.
61 * <code>Jdbc2PoolDataSource</code> can use the
62 * <code>ConnectionPoolDataSource</code> with or without the use of JNDI.
63 * </p>
64 *
65 * <p>
66 * The DriverAdapterCPDS also provides <code>PreparedStatement</code> pooling
67 * which is not generally available in jbdc2
68 * <code>ConnectionPoolDataSource</code> implementation, but is
69 * addressed within the jdbc3 specification. The <code>PreparedStatement</code>
70 * pool in DriverAdapterCPDS has been in the dbcp package for some time, but
71 * it has not undergone extensive testing in the configuration used here.
72 * It should be considered experimental and can be toggled with the
73 * poolPreparedStatements attribute.
74 * </p>
75 *
76 * <p>
77 * The <a href="package-summary.html">package documentation</a> contains an
78 * example using catalina and JNDI. The <a
79 * href="../datasources/package-summary.html">datasources package documentation</a>
80 * shows how to use <code>DriverAdapterCPDS</code> as a source for
81 * <code>Jdbc2PoolDataSource</code> without the use of JNDI.
82 * </p>
83 *
84 * @author John D. McNally
85 * @version $Revision: 821997 $ $Date: 2009-10-05 16:33:44 -0400 (Mon, 05 Oct 2009) $
86 */
87 public class DriverAdapterCPDS
88 implements ConnectionPoolDataSource, Referenceable, Serializable,
89 ObjectFactory {
90
91 private static final String GET_CONNECTION_CALLED
92 = "A PooledConnection was already requested from this source, "
93 + "further initialization is not allowed.";
94
95 /** Description */
96 private String description;
97 /** Password */
98 private String password;
99 /** Url name */
100 private String url;
101 /** User name */
102 private String user;
103 /** Driver class name */
104 private String driver;
105
106 /** Login TimeOut in seconds */
107 private int loginTimeout;
108 /** Log stream */
109 private PrintWriter logWriter = null;
110
111 // PreparedStatement pool properties
112 private boolean poolPreparedStatements;
113 private int maxActive = 10;
114 private int maxIdle = 10;
115 private int _timeBetweenEvictionRunsMillis = -1;
116 private int _numTestsPerEvictionRun = -1;
117 private int _minEvictableIdleTimeMillis = -1;
118 private int _maxPreparedStatements = -1;
119
120 /** Whether or not getConnection has been called */
121 private volatile boolean getConnectionCalled = false;
122
123 /** Connection properties passed to JDBC Driver */
124 private Properties connectionProperties = null;
125
126 static {
127 // Attempt to prevent deadlocks - see DBCP - 272
128 DriverManager.getDrivers();
129 }
130
131 /**
132 * Controls access to the underlying connection
133 */
134 private boolean accessToUnderlyingConnectionAllowed = false;
135
136 /**
137 * Default no-arg constructor for Serialization
138 */
139 public DriverAdapterCPDS() {
140 }
141
142 /**
143 * Attempt to establish a database connection using the default
144 * user and password.
145 */
146 public PooledConnection getPooledConnection() throws SQLException {
147 return getPooledConnection(getUser(), getPassword());
148 }
149
150 /**
151 * Attempt to establish a database connection.
152 */
153 public PooledConnection getPooledConnection(String username,
154 String password)
155 throws SQLException {
156 getConnectionCalled = true;
157 /*
158 public GenericKeyedObjectPool(KeyedPoolableObjectFactory factory,
159 int maxActive, byte whenExhaustedAction, long maxWait,
160 int maxIdle, boolean testOnBorrow, boolean testOnReturn,
161 long timeBetweenEvictionRunsMillis,
162 int numTestsPerEvictionRun, long minEvictableIdleTimeMillis,
163 boolean testWhileIdle) {
164 */
165 KeyedObjectPool stmtPool = null;
166 if (isPoolPreparedStatements()) {
167 if (getMaxPreparedStatements() <= 0)
168 {
169 // since there is no limit, create a prepared statement pool with an eviction thread
170 // evictor settings are the same as the connection pool settings.
171 stmtPool = new GenericKeyedObjectPool(null,
172 getMaxActive(), GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0,
173 getMaxIdle(), false, false,
174 getTimeBetweenEvictionRunsMillis(),getNumTestsPerEvictionRun(),getMinEvictableIdleTimeMillis(),
175 false);
176 }
177 else
178 {
179 // since there is limit, create a prepared statement pool without an eviction thread
180 // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared.
181 // see org.apache.commons.pool.impl.GenericKeyedObjectPool.clearOldest method
182 stmtPool = new GenericKeyedObjectPool(null,
183 getMaxActive(), GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0,
184 getMaxIdle(), getMaxPreparedStatements(), false, false,
185 -1,0,0, // -1 tells the pool that there should be no eviction thread.
186 false);
187 }
188 }
189 // Workaround for buggy WebLogic 5.1 classloader - ignore the
190 // exception upon first invocation.
191 try {
192 PooledConnectionImpl pci = null;
193 if (connectionProperties != null) {
194 connectionProperties.put("user", username);
195 connectionProperties.put("password", password);
196 pci = new PooledConnectionImpl(
197 DriverManager.getConnection(getUrl(), connectionProperties),
198 stmtPool);
199 } else {
200 pci = new PooledConnectionImpl(
201 DriverManager.getConnection(getUrl(), username, password),
202 stmtPool);
203 }
204 pci.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
205 return pci;
206 }
207 catch (ClassCircularityError e)
208 {
209 PooledConnectionImpl pci = null;
210 if (connectionProperties != null) {
211 pci = new PooledConnectionImpl(
212 DriverManager.getConnection(getUrl(), connectionProperties),
213 stmtPool);
214 } else {
215 pci = new PooledConnectionImpl(
216 DriverManager.getConnection(getUrl(), username, password),
217 stmtPool);
218 }
219 pci.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
220 return pci;
221 }
222 }
223
224 // ----------------------------------------------------------------------
225 // Referenceable implementation
226
227 /**
228 * <CODE>Referenceable</CODE> implementation.
229 */
230 public Reference getReference() throws NamingException {
231 // this class implements its own factory
232 String factory = getClass().getName();
233
234 Reference ref = new Reference(getClass().getName(), factory, null);
235
236 ref.add(new StringRefAddr("description", getDescription()));
237 ref.add(new StringRefAddr("driver", getDriver()));
238 ref.add(new StringRefAddr("loginTimeout",
239 String.valueOf(getLoginTimeout())));
240 ref.add(new StringRefAddr("password", getPassword()));
241 ref.add(new StringRefAddr("user", getUser()));
242 ref.add(new StringRefAddr("url", getUrl()));
243
244 ref.add(new StringRefAddr("poolPreparedStatements",
245 String.valueOf(isPoolPreparedStatements())));
246 ref.add(new StringRefAddr("maxActive",
247 String.valueOf(getMaxActive())));
248 ref.add(new StringRefAddr("maxIdle",
249 String.valueOf(getMaxIdle())));
250 ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis",
251 String.valueOf(getTimeBetweenEvictionRunsMillis())));
252 ref.add(new StringRefAddr("numTestsPerEvictionRun",
253 String.valueOf(getNumTestsPerEvictionRun())));
254 ref.add(new StringRefAddr("minEvictableIdleTimeMillis",
255 String.valueOf(getMinEvictableIdleTimeMillis())));
256 ref.add(new StringRefAddr("maxPreparedStatements",
257 String.valueOf(getMaxPreparedStatements())));
258
259 return ref;
260 }
261
262
263 // ----------------------------------------------------------------------
264 // ObjectFactory implementation
265
266 /**
267 * implements ObjectFactory to create an instance of this class
268 */
269 public Object getObjectInstance(Object refObj, Name name,
270 Context context, Hashtable env)
271 throws Exception {
272 // The spec says to return null if we can't create an instance
273 // of the reference
274 DriverAdapterCPDS cpds = null;
275 if (refObj instanceof Reference) {
276 Reference ref = (Reference)refObj;
277 if (ref.getClassName().equals(getClass().getName())) {
278 RefAddr ra = ref.get("description");
279 if (ra != null && ra.getContent() != null) {
280 setDescription(ra.getContent().toString());
281 }
282
283 ra = ref.get("driver");
284 if (ra != null && ra.getContent() != null) {
285 setDriver(ra.getContent().toString());
286 }
287 ra = ref.get("url");
288 if (ra != null && ra.getContent() != null) {
289 setUrl(ra.getContent().toString());
290 }
291 ra = ref.get("user");
292 if (ra != null && ra.getContent() != null) {
293 setUser(ra.getContent().toString());
294 }
295 ra = ref.get("password");
296 if (ra != null && ra.getContent() != null) {
297 setPassword(ra.getContent().toString());
298 }
299
300 ra = ref.get("poolPreparedStatements");
301 if (ra != null && ra.getContent() != null) {
302 setPoolPreparedStatements(Boolean.valueOf(
303 ra.getContent().toString()).booleanValue());
304 }
305 ra = ref.get("maxActive");
306 if (ra != null && ra.getContent() != null) {
307 setMaxActive(Integer.parseInt(ra.getContent().toString()));
308 }
309
310 ra = ref.get("maxIdle");
311 if (ra != null && ra.getContent() != null) {
312 setMaxIdle(Integer.parseInt(ra.getContent().toString()));
313 }
314
315 ra = ref.get("timeBetweenEvictionRunsMillis");
316 if (ra != null && ra.getContent() != null) {
317 setTimeBetweenEvictionRunsMillis(
318 Integer.parseInt(ra.getContent().toString()));
319 }
320
321 ra = ref.get("numTestsPerEvictionRun");
322 if (ra != null && ra.getContent() != null) {
323 setNumTestsPerEvictionRun(
324 Integer.parseInt(ra.getContent().toString()));
325 }
326
327 ra = ref.get("minEvictableIdleTimeMillis");
328 if (ra != null && ra.getContent() != null) {
329 setMinEvictableIdleTimeMillis(
330 Integer.parseInt(ra.getContent().toString()));
331 }
332 ra = ref.get("maxPreparedStatements");
333 if (ra != null && ra.getContent() != null) {
334 setMaxPreparedStatements(
335 Integer.parseInt(ra.getContent().toString()));
336 }
337
338 cpds = this;
339 }
340 }
341 return cpds;
342 }
343
344 /**
345 * Throws an IllegalStateException, if a PooledConnection has already
346 * been requested.
347 */
348 private void assertInitializationAllowed() throws IllegalStateException {
349 if (getConnectionCalled) {
350 throw new IllegalStateException(GET_CONNECTION_CALLED);
351 }
352 }
353
354 // ----------------------------------------------------------------------
355 // Properties
356
357 /**
358 * Get the connection properties passed to the JDBC driver.
359 *
360 * @return the JDBC connection properties used when creating connections.
361 * @since 1.3
362 */
363 public Properties getConnectionProperties() {
364 return connectionProperties;
365 }
366
367 /**
368 * <p>Set the connection properties passed to the JDBC driver.</p>
369 *
370 * <p>If <code>props</code> contains "user" and/or "password"
371 * properties, the corresponding instance properties are set. If these
372 * properties are not present, they are filled in using
373 * {@link #getUser()}, {@link #getPassword()} when {@link #getPooledConnection()}
374 * is called, or using the actual parameters to the method call when
375 * {@link #getPooledConnection(String, String)} is called. Calls to
376 * {@link #setUser(String)} or {@link #setPassword(String)} overwrite the values
377 * of these properties if <code>connectionProperties</code> is not null.</p>
378 *
379 * @param props Connection properties to use when creating new connections.
380 * @since 1.3
381 */
382 public void setConnectionProperties(Properties props) {
383 assertInitializationAllowed();
384 connectionProperties = props;
385 if (connectionProperties.containsKey("user")) {
386 setUser(connectionProperties.getProperty("user"));
387 }
388 if (connectionProperties.containsKey("password")) {
389 setPassword(connectionProperties.getProperty("password"));
390 }
391 }
392
393 /**
394 * Get the value of description. This property is here for use by
395 * the code which will deploy this datasource. It is not used
396 * internally.
397 *
398 * @return value of description.
399 */
400 public String getDescription() {
401 return description;
402 }
403
404 /**
405 * Set the value of description. This property is here for use by
406 * the code which will deploy this datasource. It is not used
407 * internally.
408 *
409 * @param v Value to assign to description.
410 */
411 public void setDescription(String v) {
412 this.description = v;
413 }
414
415 /**
416 * Get the value of password for the default user.
417 * @return value of password.
418 */
419 public String getPassword() {
420 return password;
421 }
422
423 /**
424 * Set the value of password for the default user.
425 * @param v Value to assign to password.
426 */
427 public void setPassword(String v) {
428 assertInitializationAllowed();
429 this.password = v;
430 if (connectionProperties != null) {
431 connectionProperties.setProperty("password", v);
432 }
433 }
434
435 /**
436 * Get the value of url used to locate the database for this datasource.
437 * @return value of url.
438 */
439 public String getUrl() {
440 return url;
441 }
442
443 /**
444 * Set the value of url used to locate the database for this datasource.
445 * @param v Value to assign to url.
446 */
447 public void setUrl(String v) {
448 assertInitializationAllowed();
449 this.url = v;
450 }
451
452 /**
453 * Get the value of default user (login or username).
454 * @return value of user.
455 */
456 public String getUser() {
457 return user;
458 }
459
460 /**
461 * Set the value of default user (login or username).
462 * @param v Value to assign to user.
463 */
464 public void setUser(String v) {
465 assertInitializationAllowed();
466 this.user = v;
467 if (connectionProperties != null) {
468 connectionProperties.setProperty("user", v);
469 }
470 }
471
472 /**
473 * Get the driver classname.
474 * @return value of driver.
475 */
476 public String getDriver() {
477 return driver;
478 }
479
480 /**
481 * Set the driver classname. Setting the driver classname cause the
482 * driver to be registered with the DriverManager.
483 * @param v Value to assign to driver.
484 */
485 public void setDriver(String v) throws ClassNotFoundException {
486 assertInitializationAllowed();
487 this.driver = v;
488 // make sure driver is registered
489 Class.forName(v);
490 }
491
492 /**
493 * Gets the maximum time in seconds that this data source can wait
494 * while attempting to connect to a database. NOT USED.
495 */
496 public int getLoginTimeout() {
497 return loginTimeout;
498 }
499
500 /**
501 * Get the log writer for this data source. NOT USED.
502 */
503 public PrintWriter getLogWriter() {
504 return logWriter;
505 }
506
507 /**
508 * Sets the maximum time in seconds that this data source will wait
509 * while attempting to connect to a database. NOT USED.
510 */
511 public void setLoginTimeout(int seconds) {
512 loginTimeout = seconds;
513 }
514
515 /**
516 * Set the log writer for this data source. NOT USED.
517 */
518 public void setLogWriter(java.io.PrintWriter out) {
519 logWriter = out;
520 }
521
522
523 // ------------------------------------------------------------------
524 // PreparedStatement pool properties
525
526
527 /**
528 * Flag to toggle the pooling of <code>PreparedStatement</code>s
529 * @return value of poolPreparedStatements.
530 */
531 public boolean isPoolPreparedStatements() {
532 return poolPreparedStatements;
533 }
534
535 /**
536 * Flag to toggle the pooling of <code>PreparedStatement</code>s
537 * @param v true to pool statements.
538 */
539 public void setPoolPreparedStatements(boolean v) {
540 assertInitializationAllowed();
541 this.poolPreparedStatements = v;
542 }
543
544 /**
545 * The maximum number of active statements that can be allocated from
546 * this pool at the same time, or non-positive for no limit.
547 */
548 public int getMaxActive() {
549 return (this.maxActive);
550 }
551
552 /**
553 * The maximum number of active statements that can be allocated from
554 * this pool at the same time, or non-positive for no limit.
555 */
556 public void setMaxActive(int maxActive) {
557 assertInitializationAllowed();
558 this.maxActive = maxActive;
559 }
560
561 /**
562 * The maximum number of statements that can remain idle in the
563 * pool, without extra ones being released, or negative for no limit.
564 */
565 public int getMaxIdle() {
566 return (this.maxIdle);
567 }
568
569 /**
570 * The maximum number of statements that can remain idle in the
571 * pool, without extra ones being released, or negative for no limit.
572 */
573 public void setMaxIdle(int maxIdle) {
574 assertInitializationAllowed();
575 this.maxIdle = maxIdle;
576 }
577
578 /**
579 * Returns the number of milliseconds to sleep between runs of the
580 * idle object evictor thread.
581 * When non-positive, no idle object evictor thread will be
582 * run.
583 *
584 * *see #setTimeBetweenEvictionRunsMillis
585 */
586 public int getTimeBetweenEvictionRunsMillis() {
587 return _timeBetweenEvictionRunsMillis;
588 }
589
590 /**
591 * Sets the number of milliseconds to sleep between runs of the
592 * idle object evictor thread.
593 * When non-positive, no idle object evictor thread will be
594 * run.
595 *
596 * *see #getTimeBetweenEvictionRunsMillis
597 */
598 public void setTimeBetweenEvictionRunsMillis(
599 int timeBetweenEvictionRunsMillis) {
600 assertInitializationAllowed();
601 _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
602 }
603
604 /**
605 * Returns the number of statements to examine during each run of the
606 * idle object evictor thread (if any).
607 *
608 * *see #setNumTestsPerEvictionRun
609 * *see #setTimeBetweenEvictionRunsMillis
610 */
611 public int getNumTestsPerEvictionRun() {
612 return _numTestsPerEvictionRun;
613 }
614
615 /**
616 * Sets the number of statements to examine during each run of the
617 * idle object evictor thread (if any).
618 * <p>
619 * When a negative value is supplied, <tt>ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun})</tt>
620 * tests will be run. I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the
621 * idle objects will be tested per run.
622 *
623 * *see #getNumTestsPerEvictionRun
624 * *see #setTimeBetweenEvictionRunsMillis
625 */
626 public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
627 assertInitializationAllowed();
628 _numTestsPerEvictionRun = numTestsPerEvictionRun;
629 }
630
631 /**
632 * Returns the minimum amount of time a statement may sit idle in the pool
633 * before it is eligible for eviction by the idle object evictor
634 * (if any).
635 *
636 * *see #setMinEvictableIdleTimeMillis
637 * *see #setTimeBetweenEvictionRunsMillis
638 */
639 public int getMinEvictableIdleTimeMillis() {
640 return _minEvictableIdleTimeMillis;
641 }
642
643 /**
644 * Sets the minimum amount of time a statement may sit idle in the pool
645 * before it is eligable for eviction by the idle object evictor
646 * (if any).
647 * When non-positive, no objects will be evicted from the pool
648 * due to idle time alone.
649 *
650 * *see #getMinEvictableIdleTimeMillis
651 * *see #setTimeBetweenEvictionRunsMillis
652 */
653 public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
654 assertInitializationAllowed();
655 _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
656 }
657
658 /**
659 * Returns the value of the accessToUnderlyingConnectionAllowed property.
660 *
661 * @return true if access to the underlying is allowed, false otherwise.
662 */
663 public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
664 return this.accessToUnderlyingConnectionAllowed;
665 }
666
667 /**
668 * Sets the value of the accessToUnderlyingConnectionAllowed property.
669 * It controls if the PoolGuard allows access to the underlying connection.
670 * (Default: false)
671 *
672 * @param allow Access to the underlying connection is granted when true.
673 */
674 public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) {
675 this.accessToUnderlyingConnectionAllowed = allow;
676 }
677
678 /**
679 * Returns the maximun number of prepared statements.
680 *
681 * @return maxPrepartedStatements value
682 * @since 1.2.2
683 */
684 public int getMaxPreparedStatements()
685 {
686 return _maxPreparedStatements;
687 }
688
689 /**
690 * Sets the maximum number of prepared statements.
691 * @param maxPreparedStatements the new maximum number of prepared
692 * statements
693 *
694 * @since 1.2.2
695 */
696 public void setMaxPreparedStatements(int maxPreparedStatements)
697 {
698 _maxPreparedStatements = maxPreparedStatements;
699 }
700 }