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.datasources;
19  
20  import java.io.Serializable;
21  import java.io.PrintWriter;
22  import java.sql.Connection;
23  import java.sql.SQLException;
24  import java.util.NoSuchElementException;
25  import java.util.Properties;
26  
27  import javax.naming.Context;
28  import javax.naming.InitialContext;
29  import javax.naming.NamingException;
30  import javax.naming.Reference;
31  import javax.naming.StringRefAddr;
32  import javax.naming.Referenceable;
33  import javax.sql.ConnectionPoolDataSource;
34  import javax.sql.DataSource;
35  import javax.sql.PooledConnection;
36  
37  import org.apache.commons.dbcp.SQLNestedException;
38  import org.apache.commons.pool.impl.GenericObjectPool;
39  
40  /**
41   * <p>The base class for <code>SharedPoolDataSource</code> and 
42   * <code>PerUserPoolDataSource</code>.  Many of the configuration properties
43   * are shared and defined here.  This class is declared public in order
44   * to allow particular usage with commons-beanutils; do not make direct
45   * use of it outside of commons-dbcp.
46   * </p>
47   *
48   * <p>
49   * A J2EE container will normally provide some method of initializing the
50   * <code>DataSource</code> whose attributes are presented
51   * as bean getters/setters and then deploying it via JNDI.  It is then
52   * available to an application as a source of pooled logical connections to 
53   * the database.  The pool needs a source of physical connections.  This
54   * source is in the form of a <code>ConnectionPoolDataSource</code> that
55   * can be specified via the {@link #setDataSourceName(String)} used to
56   * lookup the source via JNDI.
57   * </p>
58   *
59   * <p>
60   * Although normally used within a JNDI environment, A DataSource
61   * can be instantiated and initialized as any bean.  In this case the 
62   * <code>ConnectionPoolDataSource</code> will likely be instantiated in
63   * a similar manner.  This class allows the physical source of connections
64   * to be attached directly to this pool using the 
65   * {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)} method.
66   * </p>
67   *
68   * <p>
69   * The dbcp package contains an adapter, 
70   * {@link org.apache.commons.dbcp.cpdsadapter.DriverAdapterCPDS},
71   * that can be used to allow the use of <code>DataSource</code>'s based on this
72   * class with jdbc driver implementations that do not supply a 
73   * <code>ConnectionPoolDataSource</code>, but still
74   * provide a {@link java.sql.Driver} implementation.
75   * </p>
76   *
77   * <p>
78   * The <a href="package-summary.html">package documentation</a> contains an 
79   * example using Apache Tomcat and JNDI and it also contains a non-JNDI example. 
80   * </p>
81   *
82   * @author John D. McNally
83   * @version $Revision: 1023401 $ $Date: 2010-10-16 21:54:24 -0400 (Sat, 16 Oct 2010) $
84   */
85  public abstract class InstanceKeyDataSource
86          implements DataSource, Referenceable, Serializable {
87      private static final long serialVersionUID = -4243533936955098795L;
88      private static final String GET_CONNECTION_CALLED 
89              = "A Connection was already requested from this source, " 
90              + "further initialization is not allowed.";
91      private static final String BAD_TRANSACTION_ISOLATION
92          = "The requested TransactionIsolation level is invalid.";
93      /**
94      * Internal constant to indicate the level is not set. 
95      */
96      protected static final int UNKNOWN_TRANSACTIONISOLATION = -1;
97      
98      /** Guards property setters - once true, setters throw IllegalStateException */
99      private volatile boolean getConnectionCalled = false;
100 
101     /** Underlying source of PooledConnections */
102     private ConnectionPoolDataSource dataSource = null;
103     
104     /** DataSource Name used to find the ConnectionPoolDataSource */
105     private String dataSourceName = null;
106     
107     // Default connection properties
108     private boolean defaultAutoCommit = false;
109     private int defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION;
110     private boolean defaultReadOnly = false;
111     
112     /** Description */
113     private String description = null;
114     
115     /** Environment that may be used to set up a jndi initial context. */
116     Properties jndiEnvironment = null;
117     
118     /** Login TimeOut in seconds */
119     private int loginTimeout = 0;
120     
121     /** Log stream */
122     private PrintWriter logWriter = null;
123     
124     // Pool properties
125     private boolean _testOnBorrow = GenericObjectPool.DEFAULT_TEST_ON_BORROW;
126     private boolean _testOnReturn = GenericObjectPool.DEFAULT_TEST_ON_RETURN;
127     private int _timeBetweenEvictionRunsMillis = (int)
128         Math.min(Integer.MAX_VALUE,
129                  GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
130     private int _numTestsPerEvictionRun = 
131         GenericObjectPool.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
132     private int _minEvictableIdleTimeMillis = (int)
133     Math.min(Integer.MAX_VALUE,
134              GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
135     private boolean _testWhileIdle = GenericObjectPool.DEFAULT_TEST_WHILE_IDLE;
136     private String validationQuery = null;
137     private boolean rollbackAfterValidation = false;
138     
139     /** true iff one of the setters for testOnBorrow, testOnReturn, testWhileIdle has been called. */
140     private boolean testPositionSet = false;
141 
142     /** Instance key */
143     protected String instanceKey = null;
144 
145     /**
146      * Default no-arg constructor for Serialization
147      */
148     public InstanceKeyDataSource() {
149         defaultAutoCommit = true;
150     }
151 
152     /**
153      * Throws an IllegalStateException, if a PooledConnection has already
154      * been requested.
155      */
156     protected void assertInitializationAllowed()
157         throws IllegalStateException {
158         if (getConnectionCalled) {
159             throw new IllegalStateException(GET_CONNECTION_CALLED);
160         }
161     }
162 
163     /**
164      * Close the connection pool being maintained by this datasource.
165      */
166     public abstract void close() throws Exception;
167     
168     protected abstract PooledConnectionManager getConnectionManager(UserPassKey upkey);
169 
170     /* JDBC_4_ANT_KEY_BEGIN */
171     public boolean isWrapperFor(Class<?> iface) throws SQLException {
172         return false;
173     }
174 
175     public <T> T unwrap(Class<T> iface) throws SQLException {
176         throw new SQLException("InstanceKeyDataSource is not a wrapper.");
177     }
178     /* JDBC_4_ANT_KEY_END */
179 
180     // -------------------------------------------------------------------
181     // Properties
182 
183     /**
184      * Get the value of connectionPoolDataSource.  This method will return
185      * null, if the backing datasource is being accessed via jndi.
186      *
187      * @return value of connectionPoolDataSource.
188      */
189     public ConnectionPoolDataSource getConnectionPoolDataSource() {
190         return dataSource;
191     }
192     
193     /**
194      * Set the backend ConnectionPoolDataSource.  This property should not be
195      * set if using jndi to access the datasource.
196      *
197      * @param v  Value to assign to connectionPoolDataSource.
198      */
199     public void setConnectionPoolDataSource(ConnectionPoolDataSource v) {
200         assertInitializationAllowed();
201         if (dataSourceName != null) {
202             throw new IllegalStateException(
203                 "Cannot set the DataSource, if JNDI is used.");
204         }
205         if (dataSource != null) 
206         {
207             throw new IllegalStateException(
208                 "The CPDS has already been set. It cannot be altered.");
209         }
210         dataSource = v;
211         instanceKey = InstanceKeyObjectFactory.registerNewInstance(this);
212     }
213 
214     /**
215      * Get the name of the ConnectionPoolDataSource which backs this pool.
216      * This name is used to look up the datasource from a jndi service 
217      * provider.
218      *
219      * @return value of dataSourceName.
220      */
221     public String getDataSourceName() {
222         return dataSourceName;
223     }
224     
225     /**
226      * Set the name of the ConnectionPoolDataSource which backs this pool.
227      * This name is used to look up the datasource from a jndi service 
228      * provider.
229      *
230      * @param v  Value to assign to dataSourceName.
231      */
232     public void setDataSourceName(String v) {
233         assertInitializationAllowed();
234         if (dataSource != null) {
235             throw new IllegalStateException(
236                 "Cannot set the JNDI name for the DataSource, if already " +
237                 "set using setConnectionPoolDataSource.");
238         }
239         if (dataSourceName != null) 
240         {
241             throw new IllegalStateException(
242                 "The DataSourceName has already been set. " + 
243                 "It cannot be altered.");
244         }
245         this.dataSourceName = v;
246         instanceKey = InstanceKeyObjectFactory.registerNewInstance(this);
247     }
248 
249     /** 
250      * Get the value of defaultAutoCommit, which defines the state of 
251      * connections handed out from this pool.  The value can be changed
252      * on the Connection using Connection.setAutoCommit(boolean).
253      * The default is true.
254      *
255      * @return value of defaultAutoCommit.
256      */
257     public boolean isDefaultAutoCommit() {
258         return defaultAutoCommit;
259     }
260     
261     /**
262      * Set the value of defaultAutoCommit, which defines the state of 
263      * connections handed out from this pool.  The value can be changed
264      * on the Connection using Connection.setAutoCommit(boolean).
265      * The default is true.
266      *
267      * @param v  Value to assign to defaultAutoCommit.
268      */
269     public void setDefaultAutoCommit(boolean v) {
270         assertInitializationAllowed();
271         this.defaultAutoCommit = v;
272     }
273 
274     /**
275      * Get the value of defaultReadOnly, which defines the state of 
276      * connections handed out from this pool.  The value can be changed
277      * on the Connection using Connection.setReadOnly(boolean).
278      * The default is false.
279      *
280      * @return value of defaultReadOnly.
281      */
282     public boolean isDefaultReadOnly() {
283         return defaultReadOnly;
284     }
285     
286     /**
287      * Set the value of defaultReadOnly, which defines the state of 
288      * connections handed out from this pool.  The value can be changed
289      * on the Connection using Connection.setReadOnly(boolean).
290      * The default is false.
291      *
292      * @param v  Value to assign to defaultReadOnly.
293      */
294     public void setDefaultReadOnly(boolean v) {
295         assertInitializationAllowed();
296         this.defaultReadOnly = v;
297     }
298 
299     /**
300      * Get the value of defaultTransactionIsolation, which defines the state of
301      * connections handed out from this pool.  The value can be changed
302      * on the Connection using Connection.setTransactionIsolation(int).
303      * If this method returns -1, the default is JDBC driver dependent.
304      * 
305      * @return value of defaultTransactionIsolation.
306      */
307     public int getDefaultTransactionIsolation() {
308             return defaultTransactionIsolation;
309     }
310 
311     /**
312      * Set the value of defaultTransactionIsolation, which defines the state of
313      * connections handed out from this pool.  The value can be changed
314      * on the Connection using Connection.setTransactionIsolation(int).
315      * The default is JDBC driver dependent.
316      * 
317      * @param v  Value to assign to defaultTransactionIsolation
318      */
319     public void setDefaultTransactionIsolation(int v) {
320         assertInitializationAllowed();
321         switch (v) {
322         case Connection.TRANSACTION_NONE:
323         case Connection.TRANSACTION_READ_COMMITTED:
324         case Connection.TRANSACTION_READ_UNCOMMITTED:
325         case Connection.TRANSACTION_REPEATABLE_READ:
326         case Connection.TRANSACTION_SERIALIZABLE:
327             break;
328         default:
329             throw new IllegalArgumentException(BAD_TRANSACTION_ISOLATION);
330         }
331         this.defaultTransactionIsolation = v;
332     }
333     
334     /**
335      * Get the description.  This property is defined by jdbc as for use with
336      * GUI (or other) tools that might deploy the datasource.  It serves no
337      * internal purpose.
338      *
339      * @return value of description.
340      */
341     public String getDescription() {
342         return description;
343     }
344     
345     /**
346      * Set the description.  This property is defined by jdbc as for use with
347      * GUI (or other) tools that might deploy the datasource.  It serves no
348      * internal purpose.
349      * 
350      * @param v  Value to assign to description.
351      */
352     public void setDescription(String v) {
353         this.description = v;
354     }
355         
356     /**
357      * Get the value of jndiEnvironment which is used when instantiating
358      * a jndi InitialContext.  This InitialContext is used to locate the
359      * backend ConnectionPoolDataSource.
360      *
361      * @return value of jndiEnvironment.
362      */
363     public String getJndiEnvironment(String key) {
364         String value = null;
365         if (jndiEnvironment != null) {
366             value = jndiEnvironment.getProperty(key);
367         }
368         return value;
369     }
370     
371     /**
372      * Sets the value of the given JNDI environment property to be used when
373      * instantiating a JNDI InitialContext. This InitialContext is used to
374      * locate the backend ConnectionPoolDataSource.
375      * 
376      * @param key the JNDI environment property to set.
377      * @param value the value assigned to specified JNDI environment property.
378      */
379     public void setJndiEnvironment(String key, String value) {
380         if (jndiEnvironment == null) {
381             jndiEnvironment = new Properties();
382         }
383         jndiEnvironment.setProperty(key, value);
384     }
385     
386     /**
387      * Get the value of loginTimeout.
388      * @return value of loginTimeout.
389      */
390     public int getLoginTimeout() {
391         return loginTimeout;
392     }
393     
394     /**
395      * Set the value of loginTimeout.
396      * @param v  Value to assign to loginTimeout.
397      */
398     public void setLoginTimeout(int v) {
399         this.loginTimeout = v;
400     }
401         
402     /**
403      * Get the value of logWriter.
404      * @return value of logWriter.
405      */
406     public PrintWriter getLogWriter() {
407         if (logWriter == null) {
408             logWriter = new PrintWriter(System.out);
409         }        
410         return logWriter;
411     }
412     
413     /**
414      * Set the value of logWriter.
415      * @param v  Value to assign to logWriter.
416      */
417     public void setLogWriter(PrintWriter v) {
418         this.logWriter = v;
419     }
420     
421     /**
422      * @see #getTestOnBorrow
423      */
424     public final boolean isTestOnBorrow() {
425         return getTestOnBorrow();
426     }
427     
428     /**
429      * When <tt>true</tt>, objects will be
430      * {*link PoolableObjectFactory#validateObject validated}
431      * before being returned by the {*link #borrowObject}
432      * method.  If the object fails to validate,
433      * it will be dropped from the pool, and we will attempt
434      * to borrow another.
435      *
436      * @see #setTestOnBorrow
437      */
438     public boolean getTestOnBorrow() {
439         return _testOnBorrow;
440     }
441 
442     /**
443      * When <tt>true</tt>, objects will be
444      * {*link PoolableObjectFactory#validateObject validated}
445      * before being returned by the {*link #borrowObject}
446      * method.  If the object fails to validate,
447      * it will be dropped from the pool, and we will attempt
448      * to borrow another. For a <code>true</code> value to have any effect,
449      * the <code>validationQuery</code> property must be set to a non-null
450      * string.
451      *
452      * @see #getTestOnBorrow
453      */
454     public void setTestOnBorrow(boolean testOnBorrow) {
455         assertInitializationAllowed();
456         _testOnBorrow = testOnBorrow;
457         testPositionSet = true;
458     }
459 
460     /**
461      * @see #getTestOnReturn
462      */
463     public final boolean isTestOnReturn() {
464         return getTestOnReturn();
465     }
466     
467     /**
468      * When <tt>true</tt>, objects will be
469      * {*link PoolableObjectFactory#validateObject validated}
470      * before being returned to the pool within the
471      * {*link #returnObject}.
472      *
473      * @see #setTestOnReturn
474      */
475     public boolean getTestOnReturn() {
476         return _testOnReturn;
477     }
478 
479     /**
480      * When <tt>true</tt>, objects will be
481      * {*link PoolableObjectFactory#validateObject validated}
482      * before being returned to the pool within the
483      * {*link #returnObject}. For a <code>true</code> value to have any effect,
484      * the <code>validationQuery</code> property must be set to a non-null
485      * string.
486      *
487      * @see #getTestOnReturn
488      */
489     public void setTestOnReturn(boolean testOnReturn) {
490         assertInitializationAllowed();
491         _testOnReturn = testOnReturn;
492         testPositionSet = true;
493     }
494 
495     /**
496      * Returns the number of milliseconds to sleep between runs of the
497      * idle object evictor thread.
498      * When non-positive, no idle object evictor thread will be
499      * run.
500      *
501      * @see #setTimeBetweenEvictionRunsMillis
502      */
503     public int getTimeBetweenEvictionRunsMillis() {
504         return _timeBetweenEvictionRunsMillis;
505     }
506 
507     /**
508      * Sets the number of milliseconds to sleep between runs of the
509      * idle object evictor thread.
510      * When non-positive, no idle object evictor thread will be
511      * run.
512      *
513      * @see #getTimeBetweenEvictionRunsMillis
514      */
515     public void 
516         setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) {
517         assertInitializationAllowed();
518             _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
519     }
520 
521     /**
522      * Returns the number of objects to examine during each run of the
523      * idle object evictor thread (if any).
524      *
525      * @see #setNumTestsPerEvictionRun
526      * @see #setTimeBetweenEvictionRunsMillis
527      */
528     public int getNumTestsPerEvictionRun() {
529         return _numTestsPerEvictionRun;
530     }
531 
532     /**
533      * Sets the number of objects to examine during each run of the
534      * idle object evictor thread (if any).
535      * <p>
536      * When a negative value is supplied, <tt>ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun})</tt>
537      * tests will be run.  I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the
538      * idle objects will be tested per run.
539      *
540      * @see #getNumTestsPerEvictionRun
541      * @see #setTimeBetweenEvictionRunsMillis
542      */
543     public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
544         assertInitializationAllowed();
545         _numTestsPerEvictionRun = numTestsPerEvictionRun;
546     }
547 
548     /**
549      * Returns the minimum amount of time an object may sit idle in the pool
550      * before it is eligable for eviction by the idle object evictor
551      * (if any).
552      *
553      * @see #setMinEvictableIdleTimeMillis
554      * @see #setTimeBetweenEvictionRunsMillis
555      */
556     public int getMinEvictableIdleTimeMillis() {
557         return _minEvictableIdleTimeMillis;
558     }
559 
560     /**
561      * Sets the minimum amount of time an object may sit idle in the pool
562      * before it is eligable for eviction by the idle object evictor
563      * (if any).
564      * When non-positive, no objects will be evicted from the pool
565      * due to idle time alone.
566      *
567      * @see #getMinEvictableIdleTimeMillis
568      * @see #setTimeBetweenEvictionRunsMillis
569      */
570     public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
571         assertInitializationAllowed();
572         _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
573     }
574 
575     /**
576      * @see #getTestWhileIdle
577      */
578     public final boolean isTestWhileIdle() {
579         return getTestWhileIdle();
580     }
581     
582     /**
583      * When <tt>true</tt>, objects will be
584      * {*link PoolableObjectFactory#validateObject validated}
585      * by the idle object evictor (if any).  If an object
586      * fails to validate, it will be dropped from the pool.
587      *
588      * @see #setTestWhileIdle
589      * @see #setTimeBetweenEvictionRunsMillis
590      */
591     public boolean getTestWhileIdle() {
592         return _testWhileIdle;
593     }
594 
595     /**
596      * When <tt>true</tt>, objects will be
597      * {*link PoolableObjectFactory#validateObject validated}
598      * by the idle object evictor (if any).  If an object
599      * fails to validate, it will be dropped from the pool. For a
600      * <code>true</code> value to have any effect,
601      * the <code>validationQuery</code> property must be set to a non-null
602      * string.
603      *
604      * @see #getTestWhileIdle
605      * @see #setTimeBetweenEvictionRunsMillis
606      */
607     public void setTestWhileIdle(boolean testWhileIdle) {
608         assertInitializationAllowed();
609         _testWhileIdle = testWhileIdle;
610         testPositionSet = true;
611     }
612 
613     /**
614      * The SQL query that will be used to validate connections from this pool
615      * before returning them to the caller.  If specified, this query
616      * <strong>MUST</strong> be an SQL SELECT statement that returns at least
617      * one row.
618      */
619     public String getValidationQuery() {
620         return (this.validationQuery);
621     }
622 
623     /**
624      * The SQL query that will be used to validate connections from this pool
625      * before returning them to the caller.  If specified, this query
626      * <strong>MUST</strong> be an SQL SELECT statement that returns at least
627      * one row. If none of the properties, testOnBorow, testOnReturn, testWhileIdle
628      * have been previously set, calling this method sets testOnBorrow to true.
629      */
630     public void setValidationQuery(String validationQuery) {
631         assertInitializationAllowed();
632         this.validationQuery = validationQuery;
633         if (!testPositionSet) {
634             setTestOnBorrow(true);
635         }
636     }
637 
638     /**
639      * Whether a rollback will be issued after executing the SQL query 
640      * that will be used to validate connections from this pool
641      * before returning them to the caller.
642      * 
643      * @return true if a rollback will be issued after executing the
644      * validation query
645      * @since 1.2.2
646      */
647     public boolean isRollbackAfterValidation() {
648         return (this.rollbackAfterValidation);
649     }
650 
651     /**
652      * Whether a rollback will be issued after executing the SQL query 
653      * that will be used to validate connections from this pool
654      * before returning them to the caller. Default behavior is NOT
655      * to issue a rollback. The setting will only have an effect
656      * if a validation query is set
657      * 
658      * @param rollbackAfterValidation new property value
659      * @since 1.2.2
660      */
661     public void setRollbackAfterValidation(boolean rollbackAfterValidation) {
662         assertInitializationAllowed();
663         this.rollbackAfterValidation = rollbackAfterValidation;
664     }
665 
666     // ----------------------------------------------------------------------
667     // Instrumentation Methods
668 
669     // ----------------------------------------------------------------------
670     // DataSource implementation 
671 
672     /**
673      * Attempt to establish a database connection.
674      */
675     public Connection getConnection() throws SQLException {
676         return getConnection(null, null);
677     }
678 
679     /**
680      * Attempt to retrieve a database connection using {@link #getPooledConnectionAndInfo(String, String)}
681      * with the provided username and password.  The password on the {@link PooledConnectionAndInfo}
682      * instance returned by <code>getPooledConnectionAndInfo</code> is compared to the <code>password</code>
683      * parameter.  If the comparison fails, a database connection using the supplied username and password
684      * is attempted.  If the connection attempt fails, an SQLException is thrown, indicating that the given password
685      * did not match the password used to create the pooled connection.  If the connection attempt succeeds, this
686      * means that the database password has been changed.  In this case, the <code>PooledConnectionAndInfo</code>
687      * instance retrieved with the old password is destroyed and the <code>getPooledConnectionAndInfo</code> is
688      * repeatedly invoked until a <code>PooledConnectionAndInfo</code> instance with the new password is returned. 
689      * 
690      */
691     public Connection getConnection(String username, String password)
692             throws SQLException {        
693         if (instanceKey == null) {
694             throw new SQLException("Must set the ConnectionPoolDataSource " 
695                     + "through setDataSourceName or setConnectionPoolDataSource"
696                     + " before calling getConnection.");
697         }
698         getConnectionCalled = true;
699         PooledConnectionAndInfo info = null;
700         try {
701             info = getPooledConnectionAndInfo(username, password);
702         } catch (NoSuchElementException e) {
703             closeDueToException(info);
704             throw new SQLNestedException("Cannot borrow connection from pool", e);
705         } catch (RuntimeException e) {
706             closeDueToException(info);
707             throw e;
708         } catch (SQLException e) {            
709             closeDueToException(info);
710             throw e;
711         } catch (Exception e) {
712             closeDueToException(info);
713             throw new SQLNestedException("Cannot borrow connection from pool", e);
714         }
715         
716         if (!(null == password ? null == info.getPassword() 
717                 : password.equals(info.getPassword()))) {  // Password on PooledConnectionAndInfo does not match
718             try { // See if password has changed by attempting connection
719                 testCPDS(username, password);
720             } catch (SQLException ex) {
721                 // Password has not changed, so refuse client, but return connection to the pool
722                 closeDueToException(info);
723                 throw new SQLException("Given password did not match password used"
724                                        + " to create the PooledConnection.");
725             } catch (javax.naming.NamingException ne) {
726                 throw (SQLException) new SQLException(
727                         "NamingException encountered connecting to database").initCause(ne);
728             }
729             /*
730              * Password must have changed -> destroy connection and keep retrying until we get a new, good one,
731              * destroying any idle connections with the old passowrd as we pull them from the pool.
732              */
733             final UserPassKey upkey = info.getUserPassKey();
734             final PooledConnectionManager manager = getConnectionManager(upkey);
735             manager.invalidate(info.getPooledConnection()); // Destroy and remove from pool
736             manager.setPassword(upkey.getPassword()); // Reset the password on the factory if using CPDSConnectionFactory
737             info = null;
738             for (int i = 0; i < 10; i++) { // Bound the number of retries - only needed if bad instances return 
739                 try {
740                     info = getPooledConnectionAndInfo(username, password);
741                 } catch (NoSuchElementException e) {
742                     closeDueToException(info);
743                     throw new SQLNestedException("Cannot borrow connection from pool", e);
744                 } catch (RuntimeException e) {
745                     closeDueToException(info);
746                     throw e;
747                 } catch (SQLException e) {            
748                     closeDueToException(info);
749                     throw e;
750                 } catch (Exception e) {
751                     closeDueToException(info);
752                     throw new SQLNestedException("Cannot borrow connection from pool", e);
753                 }
754                 if (info != null && password.equals(info.getPassword())) {
755                     break;
756                 } else {
757                     if (info != null) {
758                         manager.invalidate(info.getPooledConnection());
759                     }
760                     info = null;
761                 }
762             }  
763             if (info == null) {
764                 throw new SQLException("Cannot borrow connection from pool - password change failure.");
765             }
766         }
767 
768         Connection con = info.getPooledConnection().getConnection();
769         try { 
770             setupDefaults(con, username);
771             con.clearWarnings();
772             return con;
773         } catch (SQLException ex) {  
774             try {
775                 con.close();
776             } catch (Exception exc) { 
777                 getLogWriter().println(
778                      "ignoring exception during close: " + exc);
779             }
780             throw ex;
781         }
782     }
783 
784     protected abstract PooledConnectionAndInfo 
785         getPooledConnectionAndInfo(String username, String password)
786         throws SQLException;
787 
788     protected abstract void setupDefaults(Connection con, String username) 
789         throws SQLException;
790 
791         
792     private void closeDueToException(PooledConnectionAndInfo info) {
793         if (info != null) {
794             try {
795                 info.getPooledConnection().getConnection().close();
796             } catch (Exception e) {
797                 // do not throw this exception because we are in the middle
798                 // of handling another exception.  But record it because
799                 // it potentially leaks connections from the pool.
800                 getLogWriter().println("[ERROR] Could not return connection to "
801                     + "pool during exception handling. " + e.getMessage());   
802             }
803         }
804     }
805 
806     protected ConnectionPoolDataSource 
807         testCPDS(String username, String password)
808         throws javax.naming.NamingException, SQLException {
809         // The source of physical db connections
810         ConnectionPoolDataSource cpds = this.dataSource;
811         if (cpds == null) {            
812             Context ctx = null;
813             if (jndiEnvironment == null) {
814                 ctx = new InitialContext();                
815             } else {
816                 ctx = new InitialContext(jndiEnvironment);
817             }
818             Object ds = ctx.lookup(dataSourceName);
819             if (ds instanceof ConnectionPoolDataSource) {
820                 cpds = (ConnectionPoolDataSource) ds;
821             } else {
822                 throw new SQLException("Illegal configuration: "
823                     + "DataSource " + dataSourceName
824                     + " (" + ds.getClass().getName() + ")"
825                     + " doesn't implement javax.sql.ConnectionPoolDataSource");
826             }
827         }
828         
829         // try to get a connection with the supplied username/password
830         PooledConnection conn = null;
831         try {
832             if (username != null) {
833                 conn = cpds.getPooledConnection(username, password);
834             }
835             else {
836                 conn = cpds.getPooledConnection();
837             }
838             if (conn == null) {
839                 throw new SQLException(
840                     "Cannot connect using the supplied username/password");
841             }
842         }
843         finally {
844             if (conn != null) {
845                 try {
846                     conn.close();
847                 }
848                 catch (SQLException e) {
849                     // at least we could connect
850                 }
851             }
852         }
853         return cpds;
854     }
855 
856     protected byte whenExhaustedAction(int maxActive, int maxWait) {
857         byte whenExhausted = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
858         if (maxActive <= 0) {
859             whenExhausted = GenericObjectPool.WHEN_EXHAUSTED_GROW;
860         } else if (maxWait == 0) {
861             whenExhausted = GenericObjectPool.WHEN_EXHAUSTED_FAIL;
862         }
863         return whenExhausted;
864     }    
865 
866     // ----------------------------------------------------------------------
867     // Referenceable implementation 
868 
869     /**
870      * Retrieves the Reference of this object.
871      * <strong>Note:</strong> <code>InstanceKeyDataSource</code> subclasses
872      * should override this method. The implementaion included below
873      * is not robust and will be removed at the next major version DBCP
874      * release.
875      *
876      * @return The non-null Reference of this object.
877      * @exception NamingException If a naming exception was encountered
878      *      while retrieving the reference.
879      */
880     // TODO: Remove the implementation of this method at next major
881     // version release.
882     
883     public Reference getReference() throws NamingException {
884         final String className = getClass().getName();
885         final String factoryName = className + "Factory"; // XXX: not robust 
886         Reference ref = new Reference(className, factoryName, null);
887         ref.add(new StringRefAddr("instanceKey", instanceKey));
888         return ref;
889     }
890 }