View Javadoc

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 }