View Javadoc
1   /*
2    * Copyright 2004-2022 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.ibatis.common.jdbc;
17  
18  import com.ibatis.common.beans.ClassInfo;
19  import com.ibatis.common.logging.Log;
20  import com.ibatis.common.logging.LogFactory;
21  import com.ibatis.common.resources.Resources;
22  
23  import java.io.PrintWriter;
24  import java.lang.reflect.InvocationHandler;
25  import java.lang.reflect.Method;
26  import java.lang.reflect.Proxy;
27  import java.sql.*;
28  import java.util.*;
29  import java.util.logging.Logger;
30  
31  import javax.sql.DataSource;
32  
33  /**
34   * This is a simple, synchronous, thread-safe database connection pool.
35   * <p>
36   * REQUIRED PROPERTIES ------------------- JDBC.Driver JDBC.ConnectionURL JDBC.Username JDBC.Password
37   * <p>
38   * Pool.MaximumActiveConnections Pool.MaximumIdleConnections Pool.MaximumCheckoutTime Pool.TimeToWait Pool.PingQuery
39   * Pool.PingEnabled Pool.PingConnectionsOlderThan Pool.PingConnectionsNotUsedFor Pool.QuietMode
40   */
41  public class SimpleDataSource implements DataSource {
42  
43    /** The Constant log. */
44    private static final Log log = LogFactory.getLog(SimpleDataSource.class);
45  
46    /** The Constant PROP_JDBC_DRIVER. */
47    // Required Properties
48    private static final String PROP_JDBC_DRIVER = "JDBC.Driver";
49  
50    /** The Constant PROP_JDBC_URL. */
51    private static final String PROP_JDBC_URL = "JDBC.ConnectionURL";
52  
53    /** The Constant PROP_JDBC_USERNAME. */
54    private static final String PROP_JDBC_USERNAME = "JDBC.Username";
55  
56    /** The Constant PROP_JDBC_PASSWORD. */
57    private static final String PROP_JDBC_PASSWORD = "JDBC.Password";
58  
59    /** The Constant PROP_JDBC_DEFAULT_AUTOCOMMIT. */
60    private static final String PROP_JDBC_DEFAULT_AUTOCOMMIT = "JDBC.DefaultAutoCommit";
61  
62    /** The Constant PROP_POOL_MAX_ACTIVE_CONN. */
63    // Optional Properties
64    private static final String PROP_POOL_MAX_ACTIVE_CONN = "Pool.MaximumActiveConnections";
65  
66    /** The Constant PROP_POOL_MAX_IDLE_CONN. */
67    private static final String PROP_POOL_MAX_IDLE_CONN = "Pool.MaximumIdleConnections";
68  
69    /** The Constant PROP_POOL_MAX_CHECKOUT_TIME. */
70    private static final String PROP_POOL_MAX_CHECKOUT_TIME = "Pool.MaximumCheckoutTime";
71  
72    /** The Constant PROP_POOL_TIME_TO_WAIT. */
73    private static final String PROP_POOL_TIME_TO_WAIT = "Pool.TimeToWait";
74  
75    /** The Constant PROP_POOL_PING_QUERY. */
76    private static final String PROP_POOL_PING_QUERY = "Pool.PingQuery";
77  
78    /** The Constant PROP_POOL_PING_CONN_OLDER_THAN. */
79    private static final String PROP_POOL_PING_CONN_OLDER_THAN = "Pool.PingConnectionsOlderThan";
80  
81    /** The Constant PROP_POOL_PING_ENABLED. */
82    private static final String PROP_POOL_PING_ENABLED = "Pool.PingEnabled";
83  
84    /** The Constant PROP_POOL_PING_CONN_NOT_USED_FOR. */
85    private static final String PROP_POOL_PING_CONN_NOT_USED_FOR = "Pool.PingConnectionsNotUsedFor";
86  
87    /** The expected connection type code. */
88    private int expectedConnectionTypeCode;
89  
90    /** The Constant ADD_DRIVER_PROPS_PREFIX. */
91    // Additional Driver Properties prefix
92    private static final String ADD_DRIVER_PROPS_PREFIX = "Driver.";
93  
94    /** The Constant ADD_DRIVER_PROPS_PREFIX_LENGTH. */
95    private static final int ADD_DRIVER_PROPS_PREFIX_LENGTH = ADD_DRIVER_PROPS_PREFIX.length();
96  
97    /** The pool lock. */
98    // ----- BEGIN: FIELDS LOCKED BY POOL_LOCK -----
99    private final Object POOL_LOCK = new Object();
100 
101   /** The idle connections. */
102   private List idleConnections = new ArrayList();
103 
104   /** The active connections. */
105   private List activeConnections = new ArrayList();
106 
107   /** The request count. */
108   private long requestCount = 0;
109 
110   /** The accumulated request time. */
111   private long accumulatedRequestTime = 0;
112 
113   /** The accumulated checkout time. */
114   private long accumulatedCheckoutTime = 0;
115 
116   /** The claimed overdue connection count. */
117   private long claimedOverdueConnectionCount = 0;
118 
119   /** The accumulated checkout time of overdue connections. */
120   private long accumulatedCheckoutTimeOfOverdueConnections = 0;
121 
122   /** The accumulated wait time. */
123   private long accumulatedWaitTime = 0;
124 
125   /** The had to wait count. */
126   private long hadToWaitCount = 0;
127 
128   /** The bad connection count. */
129   private long badConnectionCount = 0;
130   // ----- END: FIELDS LOCKED BY POOL_LOCK -----
131 
132   /** The jdbc driver. */
133   // ----- BEGIN: PROPERTY FIELDS FOR CONFIGURATION -----
134   private String jdbcDriver;
135 
136   /** The jdbc url. */
137   private String jdbcUrl;
138 
139   /** The jdbc username. */
140   private String jdbcUsername;
141 
142   /** The jdbc password. */
143   private String jdbcPassword;
144 
145   /** The jdbc default auto commit. */
146   private boolean jdbcDefaultAutoCommit;
147 
148   /** The driver props. */
149   private Properties driverProps;
150 
151   /** The use driver props. */
152   private boolean useDriverProps;
153 
154   /** The pool maximum active connections. */
155   private int poolMaximumActiveConnections;
156 
157   /** The pool maximum idle connections. */
158   private int poolMaximumIdleConnections;
159 
160   /** The pool maximum checkout time. */
161   private int poolMaximumCheckoutTime;
162 
163   /** The pool time to wait. */
164   private int poolTimeToWait;
165 
166   /** The pool ping query. */
167   private String poolPingQuery;
168 
169   /** The pool ping enabled. */
170   private boolean poolPingEnabled;
171 
172   /** The pool ping connections older than. */
173   private int poolPingConnectionsOlderThan;
174 
175   /** The pool ping connections not used for. */
176   private int poolPingConnectionsNotUsedFor;
177 
178   // ----- END: PROPERTY FIELDS FOR CONFIGURATION -----
179 
180   /**
181    * Constructor to allow passing in a map of properties for configuration.
182    *
183    * @param props
184    *          - the configuration parameters
185    */
186   public SimpleDataSource(Map props) {
187     initialize(props);
188   }
189 
190   /**
191    * Initialize.
192    *
193    * @param props
194    *          the props
195    */
196   private void initialize(Map props) {
197     try {
198       String prop_pool_ping_query = null;
199 
200       if (props == null) {
201         throw new RuntimeException("SimpleDataSource: The properties map passed to the initializer was null.");
202       }
203 
204       if (!(props.containsKey(PROP_JDBC_DRIVER) && props.containsKey(PROP_JDBC_URL)
205           && props.containsKey(PROP_JDBC_USERNAME) && props.containsKey(PROP_JDBC_PASSWORD))) {
206         throw new RuntimeException("SimpleDataSource: Some properties were not set.");
207       } else {
208 
209         jdbcDriver = (String) props.get(PROP_JDBC_DRIVER);
210         jdbcUrl = (String) props.get(PROP_JDBC_URL);
211         jdbcUsername = (String) props.get(PROP_JDBC_USERNAME);
212         jdbcPassword = (String) props.get(PROP_JDBC_PASSWORD);
213 
214         poolMaximumActiveConnections = props.containsKey(PROP_POOL_MAX_ACTIVE_CONN)
215             ? Integer.parseInt((String) props.get(PROP_POOL_MAX_ACTIVE_CONN)) : 10;
216 
217         poolMaximumIdleConnections = props.containsKey(PROP_POOL_MAX_IDLE_CONN)
218             ? Integer.parseInt((String) props.get(PROP_POOL_MAX_IDLE_CONN)) : 5;
219 
220         poolMaximumCheckoutTime = props.containsKey(PROP_POOL_MAX_CHECKOUT_TIME)
221             ? Integer.parseInt((String) props.get(PROP_POOL_MAX_CHECKOUT_TIME)) : 20000;
222 
223         poolTimeToWait = props.containsKey(PROP_POOL_TIME_TO_WAIT)
224             ? Integer.parseInt((String) props.get(PROP_POOL_TIME_TO_WAIT)) : 20000;
225 
226         poolPingEnabled = props.containsKey(PROP_POOL_PING_ENABLED)
227             && Boolean.valueOf((String) props.get(PROP_POOL_PING_ENABLED)).booleanValue();
228 
229         prop_pool_ping_query = (String) props.get(PROP_POOL_PING_QUERY);
230         poolPingQuery = props.containsKey(PROP_POOL_PING_QUERY) ? prop_pool_ping_query : "NO PING QUERY SET";
231 
232         poolPingConnectionsOlderThan = props.containsKey(PROP_POOL_PING_CONN_OLDER_THAN)
233             ? Integer.parseInt((String) props.get(PROP_POOL_PING_CONN_OLDER_THAN)) : 0;
234 
235         poolPingConnectionsNotUsedFor = props.containsKey(PROP_POOL_PING_CONN_NOT_USED_FOR)
236             ? Integer.parseInt((String) props.get(PROP_POOL_PING_CONN_NOT_USED_FOR)) : 0;
237 
238         jdbcDefaultAutoCommit = props.containsKey(PROP_JDBC_DEFAULT_AUTOCOMMIT)
239             && Boolean.valueOf((String) props.get(PROP_JDBC_DEFAULT_AUTOCOMMIT)).booleanValue();
240 
241         useDriverProps = false;
242         Iterator propIter = props.keySet().iterator();
243         driverProps = new Properties();
244         driverProps.put("user", jdbcUsername);
245         driverProps.put("password", jdbcPassword);
246         while (propIter.hasNext()) {
247           String name = (String) propIter.next();
248           String value = (String) props.get(name);
249           if (name.startsWith(ADD_DRIVER_PROPS_PREFIX)) {
250             driverProps.put(name.substring(ADD_DRIVER_PROPS_PREFIX_LENGTH), value);
251             useDriverProps = true;
252           }
253         }
254 
255         expectedConnectionTypeCode = assembleConnectionTypeCode(jdbcUrl, jdbcUsername, jdbcPassword);
256 
257         Resources.instantiate(jdbcDriver);
258 
259         if (poolPingEnabled
260             && (!props.containsKey(PROP_POOL_PING_QUERY) || prop_pool_ping_query.trim().length() == 0)) {
261           throw new RuntimeException("SimpleDataSource: property '" + PROP_POOL_PING_ENABLED
262               + "' is true, but property '" + PROP_POOL_PING_QUERY + "' is not set correctly.");
263         }
264       }
265 
266     } catch (Exception e) {
267       log.error("SimpleDataSource: Error while loading properties. Cause: " + e.toString(), e);
268       throw new RuntimeException("SimpleDataSource: Error while loading properties. Cause: " + e, e);
269     }
270   }
271 
272   /**
273    * Assemble connection type code.
274    *
275    * @param url
276    *          the url
277    * @param username
278    *          the username
279    * @param password
280    *          the password
281    *
282    * @return the int
283    */
284   private int assembleConnectionTypeCode(String url, String username, String password) {
285     return ("" + url + username + password).hashCode();
286   }
287 
288   /**
289    * @see javax.sql.DataSource#getConnection()
290    */
291   public Connection getConnection() throws SQLException {
292     return popConnection(jdbcUsername, jdbcPassword).getProxyConnection();
293   }
294 
295   /**
296    * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
297    */
298   public Connection getConnection(String username, String password) throws SQLException {
299     return popConnection(username, password).getProxyConnection();
300   }
301 
302   /**
303    * @see javax.sql.DataSource#setLoginTimeout(int)
304    */
305   public void setLoginTimeout(int loginTimeout) throws SQLException {
306     DriverManager.setLoginTimeout(loginTimeout);
307   }
308 
309   /**
310    * @see javax.sql.DataSource#getLoginTimeout()
311    */
312   public int getLoginTimeout() throws SQLException {
313     return DriverManager.getLoginTimeout();
314   }
315 
316   /**
317    * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter)
318    */
319   public void setLogWriter(PrintWriter logWriter) throws SQLException {
320     DriverManager.setLogWriter(logWriter);
321   }
322 
323   /**
324    * @see javax.sql.DataSource#getLogWriter()
325    */
326   public PrintWriter getLogWriter() throws SQLException {
327     return DriverManager.getLogWriter();
328   }
329 
330   /**
331    * If a connection has not been used in this many milliseconds, ping the database to make sure the connection is still
332    * good.
333    *
334    * @return the number of milliseconds of inactivity that will trigger a ping
335    */
336   public int getPoolPingConnectionsNotUsedFor() {
337     return poolPingConnectionsNotUsedFor;
338   }
339 
340   /**
341    * Getter for the name of the JDBC driver class used.
342    *
343    * @return The name of the class
344    */
345   public String getJdbcDriver() {
346     return jdbcDriver;
347   }
348 
349   /**
350    * Getter of the JDBC URL used.
351    *
352    * @return The JDBC URL
353    */
354   public String getJdbcUrl() {
355     return jdbcUrl;
356   }
357 
358   /**
359    * Getter for the JDBC user name used.
360    *
361    * @return The user name
362    */
363   public String getJdbcUsername() {
364     return jdbcUsername;
365   }
366 
367   /**
368    * Getter for the JDBC password used.
369    *
370    * @return The password
371    */
372   public String getJdbcPassword() {
373     return jdbcPassword;
374   }
375 
376   /**
377    * Getter for the maximum number of active connections.
378    *
379    * @return The maximum number of active connections
380    */
381   public int getPoolMaximumActiveConnections() {
382     return poolMaximumActiveConnections;
383   }
384 
385   /**
386    * Getter for the maximum number of idle connections.
387    *
388    * @return The maximum number of idle connections
389    */
390   public int getPoolMaximumIdleConnections() {
391     return poolMaximumIdleConnections;
392   }
393 
394   /**
395    * Getter for the maximum time a connection can be used before it *may* be given away again.
396    *
397    * @return The maximum time
398    */
399   public int getPoolMaximumCheckoutTime() {
400     return poolMaximumCheckoutTime;
401   }
402 
403   /**
404    * Getter for the time to wait before retrying to get a connection.
405    *
406    * @return The time to wait
407    */
408   public int getPoolTimeToWait() {
409     return poolTimeToWait;
410   }
411 
412   /**
413    * Getter for the query to be used to check a connection.
414    *
415    * @return The query
416    */
417   public String getPoolPingQuery() {
418     return poolPingQuery;
419   }
420 
421   /**
422    * Getter to tell if we should use the ping query.
423    *
424    * @return True if we need to check a connection before using it
425    */
426   public boolean isPoolPingEnabled() {
427     return poolPingEnabled;
428   }
429 
430   /**
431    * Getter for the age of connections that should be pinged before using.
432    *
433    * @return The age
434    */
435   public int getPoolPingConnectionsOlderThan() {
436     return poolPingConnectionsOlderThan;
437   }
438 
439   /**
440    * Gets the expected connection type code.
441    *
442    * @return the expected connection type code
443    */
444   private int getExpectedConnectionTypeCode() {
445     return expectedConnectionTypeCode;
446   }
447 
448   /**
449    * Getter for the number of connection requests made.
450    *
451    * @return The number of connection requests made
452    */
453   public long getRequestCount() {
454     synchronized (POOL_LOCK) {
455       return requestCount;
456     }
457   }
458 
459   /**
460    * Getter for the average time required to get a connection to the database.
461    *
462    * @return The average time
463    */
464   public long getAverageRequestTime() {
465     synchronized (POOL_LOCK) {
466       return requestCount == 0 ? 0 : accumulatedRequestTime / requestCount;
467     }
468   }
469 
470   /**
471    * Getter for the average time spent waiting for connections that were in use.
472    *
473    * @return The average time
474    */
475   public long getAverageWaitTime() {
476     synchronized (POOL_LOCK) {
477       return hadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount;
478     }
479   }
480 
481   /**
482    * Getter for the number of requests that had to wait for connections that were in use.
483    *
484    * @return The number of requests that had to wait
485    */
486   public long getHadToWaitCount() {
487     synchronized (POOL_LOCK) {
488       return hadToWaitCount;
489     }
490   }
491 
492   /**
493    * Getter for the number of invalid connections that were found in the pool.
494    *
495    * @return The number of invalid connections
496    */
497   public long getBadConnectionCount() {
498     synchronized (POOL_LOCK) {
499       return badConnectionCount;
500     }
501   }
502 
503   /**
504    * Getter for the number of connections that were claimed before they were returned.
505    *
506    * @return The number of connections
507    */
508   public long getClaimedOverdueConnectionCount() {
509     synchronized (POOL_LOCK) {
510       return claimedOverdueConnectionCount;
511     }
512   }
513 
514   /**
515    * Getter for the average age of overdue connections.
516    *
517    * @return The average age
518    */
519   public long getAverageOverdueCheckoutTime() {
520     synchronized (POOL_LOCK) {
521       return claimedOverdueConnectionCount == 0 ? 0
522           : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount;
523     }
524   }
525 
526   /**
527    * Getter for the average age of a connection checkout.
528    *
529    * @return The average age
530    */
531   public long getAverageCheckoutTime() {
532     synchronized (POOL_LOCK) {
533       return requestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount;
534     }
535   }
536 
537   /**
538    * Returns the status of the connection pool.
539    *
540    * @return The status
541    */
542   public String getStatus() {
543     StringBuilder builder = new StringBuilder();
544 
545     builder.append("\n===============================================================");
546     builder.append("\n jdbcDriver                     ").append(jdbcDriver);
547     builder.append("\n jdbcUrl                        ").append(jdbcUrl);
548     builder.append("\n jdbcUsername                   ").append(jdbcUsername);
549     builder.append("\n jdbcPassword                   ").append((jdbcPassword == null ? "NULL" : "************"));
550     builder.append("\n poolMaxActiveConnections       ").append(poolMaximumActiveConnections);
551     builder.append("\n poolMaxIdleConnections         ").append(poolMaximumIdleConnections);
552     builder.append("\n poolMaxCheckoutTime            " + poolMaximumCheckoutTime);
553     builder.append("\n poolTimeToWait                 " + poolTimeToWait);
554     builder.append("\n poolPingEnabled                " + poolPingEnabled);
555     builder.append("\n poolPingQuery                  " + poolPingQuery);
556     builder.append("\n poolPingConnectionsOlderThan   " + poolPingConnectionsOlderThan);
557     builder.append("\n poolPingConnectionsNotUsedFor  " + poolPingConnectionsNotUsedFor);
558     builder.append("\n --------------------------------------------------------------");
559     builder.append("\n activeConnections              " + activeConnections.size());
560     builder.append("\n idleConnections                " + idleConnections.size());
561     builder.append("\n requestCount                   " + getRequestCount());
562     builder.append("\n averageRequestTime             " + getAverageRequestTime());
563     builder.append("\n averageCheckoutTime            " + getAverageCheckoutTime());
564     builder.append("\n claimedOverdue                 " + getClaimedOverdueConnectionCount());
565     builder.append("\n averageOverdueCheckoutTime     " + getAverageOverdueCheckoutTime());
566     builder.append("\n hadToWait                      " + getHadToWaitCount());
567     builder.append("\n averageWaitTime                " + getAverageWaitTime());
568     builder.append("\n badConnectionCount             " + getBadConnectionCount());
569     builder.append("\n===============================================================");
570     return builder.toString();
571   }
572 
573   /**
574    * Closes all of the connections in the pool.
575    */
576   public void forceCloseAll() {
577     synchronized (POOL_LOCK) {
578       for (int i = activeConnections.size(); i > 0; i--) {
579         try {
580           SimplePooledConnection conn = (SimplePooledConnection) activeConnections.remove(i - 1);
581           conn.invalidate();
582 
583           Connection realConn = conn.getRealConnection();
584           if (!realConn.getAutoCommit()) {
585             realConn.rollback();
586           }
587           realConn.close();
588         } catch (Exception e) {
589           // ignore
590         }
591       }
592       for (int i = idleConnections.size(); i > 0; i--) {
593         try {
594           SimplePooledConnection conn = (SimplePooledConnection) idleConnections.remove(i - 1);
595           conn.invalidate();
596 
597           Connection realConn = conn.getRealConnection();
598           if (!realConn.getAutoCommit()) {
599             realConn.rollback();
600           }
601           realConn.close();
602         } catch (Exception e) {
603           // ignore
604         }
605       }
606     }
607     if (log.isDebugEnabled()) {
608       log.debug("SimpleDataSource forcefully closed/removed all connections.");
609     }
610   }
611 
612   /**
613    * Push connection.
614    *
615    * @param conn
616    *          the conn
617    *
618    * @throws SQLException
619    *           the SQL exception
620    */
621   private void pushConnection(SimplePooledConnection conn) throws SQLException {
622 
623     synchronized (POOL_LOCK) {
624       activeConnections.remove(conn);
625       if (conn.isValid()) {
626         if (idleConnections.size() < poolMaximumIdleConnections
627             && conn.getConnectionTypeCode() == getExpectedConnectionTypeCode()) {
628           accumulatedCheckoutTime += conn.getCheckoutTime();
629           if (!conn.getRealConnection().getAutoCommit()) {
630             conn.getRealConnection().rollback();
631           }
632           SimplePooledConnection newConn = new SimplePooledConnection(conn.getRealConnection(), this);
633           idleConnections.add(newConn);
634           newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
635           newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
636           conn.invalidate();
637           if (log.isDebugEnabled()) {
638             log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
639           }
640           POOL_LOCK.notifyAll();
641         } else {
642           accumulatedCheckoutTime += conn.getCheckoutTime();
643           if (!conn.getRealConnection().getAutoCommit()) {
644             conn.getRealConnection().rollback();
645           }
646           conn.getRealConnection().close();
647           if (log.isDebugEnabled()) {
648             log.debug("Closed connection " + conn.getRealHashCode() + ".");
649           }
650           conn.invalidate();
651         }
652       } else {
653         if (log.isDebugEnabled()) {
654           log.debug("A bad connection (" + conn.getRealHashCode()
655               + ") attempted to return to the pool, discarding connection.");
656         }
657         badConnectionCount++;
658       }
659     }
660   }
661 
662   /**
663    * Pop connection.
664    *
665    * @param username
666    *          the username
667    * @param password
668    *          the password
669    *
670    * @return the simple pooled connection
671    *
672    * @throws SQLException
673    *           the SQL exception
674    */
675   private SimplePooledConnection popConnection(String username, String password) throws SQLException {
676     boolean countedWait = false;
677     SimplePooledConnection conn = null;
678     long t = System.currentTimeMillis();
679     int localBadConnectionCount = 0;
680 
681     while (conn == null) {
682       synchronized (POOL_LOCK) {
683         if (idleConnections.size() > 0) {
684           // Pool has available connection
685           conn = (SimplePooledConnection) idleConnections.remove(0);
686           if (log.isDebugEnabled()) {
687             log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
688           }
689         } else {
690           // Pool does not have available connection
691           if (activeConnections.size() < poolMaximumActiveConnections) {
692             // Can create new connection
693             if (useDriverProps) {
694               conn = new SimplePooledConnection(DriverManager.getConnection(jdbcUrl, driverProps), this);
695             } else {
696               conn = new SimplePooledConnection(DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword), this);
697             }
698             Connection realConn = conn.getRealConnection();
699             if (realConn.getAutoCommit() != jdbcDefaultAutoCommit) {
700               realConn.setAutoCommit(jdbcDefaultAutoCommit);
701             }
702             if (log.isDebugEnabled()) {
703               log.debug("Created connection " + conn.getRealHashCode() + ".");
704             }
705           } else {
706             // Cannot create new connection
707             SimplePooledConnection oldestActiveConnection = (SimplePooledConnection) activeConnections.get(0);
708             long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
709             if (longestCheckoutTime > poolMaximumCheckoutTime) {
710               // Can claim overdue connection
711               claimedOverdueConnectionCount++;
712               accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
713               accumulatedCheckoutTime += longestCheckoutTime;
714               activeConnections.remove(oldestActiveConnection);
715               if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
716                 oldestActiveConnection.getRealConnection().rollback();
717               }
718               conn = new SimplePooledConnection(oldestActiveConnection.getRealConnection(), this);
719               oldestActiveConnection.invalidate();
720               if (log.isDebugEnabled()) {
721                 log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
722               }
723             } else {
724               // Must wait
725               try {
726                 if (!countedWait) {
727                   hadToWaitCount++;
728                   countedWait = true;
729                 }
730                 if (log.isDebugEnabled()) {
731                   log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
732                 }
733                 long wt = System.currentTimeMillis();
734                 POOL_LOCK.wait(poolTimeToWait);
735                 accumulatedWaitTime += System.currentTimeMillis() - wt;
736               } catch (InterruptedException e) {
737                 break;
738               }
739             }
740           }
741         }
742         if (conn != null) {
743           if (conn.isValid()) {
744             if (!conn.getRealConnection().getAutoCommit()) {
745               conn.getRealConnection().rollback();
746             }
747             conn.setConnectionTypeCode(assembleConnectionTypeCode(jdbcUrl, username, password));
748             conn.setCheckoutTimestamp(System.currentTimeMillis());
749             conn.setLastUsedTimestamp(System.currentTimeMillis());
750             activeConnections.add(conn);
751             requestCount++;
752             accumulatedRequestTime += System.currentTimeMillis() - t;
753           } else {
754             if (log.isDebugEnabled()) {
755               log.debug("A bad connection (" + conn.getRealHashCode()
756                   + ") was returned from the pool, getting another connection.");
757             }
758             badConnectionCount++;
759             localBadConnectionCount++;
760             conn = null;
761             if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
762               if (log.isDebugEnabled()) {
763                 log.debug("SimpleDataSource: Could not get a good connection to the database.");
764               }
765               throw new SQLException("SimpleDataSource: Could not get a good connection to the database.");
766             }
767           }
768         }
769       }
770 
771     }
772 
773     if (conn == null) {
774       if (log.isDebugEnabled()) {
775         log.debug("SimpleDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
776       }
777       throw new SQLException(
778           "SimpleDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
779     }
780 
781     return conn;
782   }
783 
784   /**
785    * Method to check to see if a connection is still usable.
786    *
787    * @param conn
788    *          - the connection to check
789    *
790    * @return True if the connection is still usable
791    */
792   private boolean pingConnection(SimplePooledConnection conn) {
793     boolean result = true;
794 
795     try {
796       result = !conn.getRealConnection().isClosed();
797     } catch (SQLException e) {
798       if (log.isDebugEnabled()) {
799         log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
800       }
801       result = false;
802     }
803 
804     if (result) {
805       if (poolPingEnabled) {
806         if ((poolPingConnectionsOlderThan > 0 && conn.getAge() > poolPingConnectionsOlderThan)
807             || (poolPingConnectionsNotUsedFor > 0
808                 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor)) {
809 
810           try {
811             if (log.isDebugEnabled()) {
812               log.debug("Testing connection " + conn.getRealHashCode() + " ...");
813             }
814             Connection realConn = conn.getRealConnection();
815             Statement statement = realConn.createStatement();
816             ResultSet rs = statement.executeQuery(poolPingQuery);
817             rs.close();
818             statement.close();
819             if (!realConn.getAutoCommit()) {
820               realConn.rollback();
821             }
822             result = true;
823             if (log.isDebugEnabled()) {
824               log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
825             }
826           } catch (Exception e) {
827             log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
828             try {
829               conn.getRealConnection().close();
830             } catch (Exception e2) {
831               // ignore
832             }
833             result = false;
834             if (log.isDebugEnabled()) {
835               log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
836             }
837           }
838         }
839       }
840     }
841     return result;
842   }
843 
844   /**
845    * Unwraps a pooled connection to get to the 'real' connection.
846    *
847    * @param conn
848    *          - the pooled connection to unwrap
849    *
850    * @return The 'real' connection
851    */
852   public static Connection unwrapConnection(Connection conn) {
853     if (conn instanceof SimplePooledConnection) {
854       return ((SimplePooledConnection) conn).getRealConnection();
855     } else {
856       return conn;
857     }
858   }
859 
860   protected void finalize() throws Throwable {
861     forceCloseAll();
862   }
863 
864   /**
865    * --------------------------------------------------------------------------------------- SimplePooledConnection
866    * ---------------------------------------------------------------------------------------.
867    */
868   public static class SimplePooledConnection implements InvocationHandler {
869 
870     /** The Constant CLOSE. */
871     private static final String CLOSE = "close";
872 
873     /** The Constant IFACES. */
874     private static final Class[] IFACES = new Class[] { Connection.class };
875 
876     /** The hash code. */
877     private int hashCode = 0;
878 
879     /** The data source. */
880     private SimpleDataSource dataSource;
881 
882     /** The real connection. */
883     private Connection realConnection;
884 
885     /** The proxy connection. */
886     private Connection proxyConnection;
887 
888     /** The checkout timestamp. */
889     private long checkoutTimestamp;
890 
891     /** The created timestamp. */
892     private long createdTimestamp;
893 
894     /** The last used timestamp. */
895     private long lastUsedTimestamp;
896 
897     /** The connection type code. */
898     private int connectionTypeCode;
899 
900     /** The valid. */
901     private boolean valid;
902 
903     /**
904      * Constructor for SimplePooledConnection that uses the Connection and SimpleDataSource passed in.
905      *
906      * @param connection
907      *          - the connection that is to be presented as a pooled connection
908      * @param dataSource
909      *          - the dataSource that the connection is from
910      */
911     public SimplePooledConnection(Connection connection, SimpleDataSource dataSource) {
912       this.hashCode = connection.hashCode();
913       this.realConnection = connection;
914       this.dataSource = dataSource;
915       this.createdTimestamp = System.currentTimeMillis();
916       this.lastUsedTimestamp = System.currentTimeMillis();
917       this.valid = true;
918 
919       proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
920     }
921 
922     /**
923      * Invalidates the connection.
924      */
925     public void invalidate() {
926       valid = false;
927     }
928 
929     /**
930      * Method to see if the connection is usable.
931      *
932      * @return True if the connection is usable
933      */
934     public boolean isValid() {
935       return valid && realConnection != null && dataSource.pingConnection(this);
936     }
937 
938     /**
939      * Getter for the *real* connection that this wraps.
940      *
941      * @return The connection
942      */
943     public Connection getRealConnection() {
944       return realConnection;
945     }
946 
947     /**
948      * Getter for the proxy for the connection.
949      *
950      * @return The proxy
951      */
952     public Connection getProxyConnection() {
953       return proxyConnection;
954     }
955 
956     /**
957      * Gets the hashcode of the real connection (or 0 if it is null).
958      *
959      * @return The hashcode of the real connection (or 0 if it is null)
960      */
961     public int getRealHashCode() {
962       if (realConnection == null) {
963         return 0;
964       } else {
965         return realConnection.hashCode();
966       }
967     }
968 
969     /**
970      * Getter for the connection type (based on url + user + password).
971      *
972      * @return The connection type
973      */
974     public int getConnectionTypeCode() {
975       return connectionTypeCode;
976     }
977 
978     /**
979      * Setter for the connection type.
980      *
981      * @param connectionTypeCode
982      *          - the connection type
983      */
984     public void setConnectionTypeCode(int connectionTypeCode) {
985       this.connectionTypeCode = connectionTypeCode;
986     }
987 
988     /**
989      * Getter for the time that the connection was created.
990      *
991      * @return The creation timestamp
992      */
993     public long getCreatedTimestamp() {
994       return createdTimestamp;
995     }
996 
997     /**
998      * Setter for the time that the connection was created.
999      *
1000      * @param createdTimestamp
1001      *          - the timestamp
1002      */
1003     public void setCreatedTimestamp(long createdTimestamp) {
1004       this.createdTimestamp = createdTimestamp;
1005     }
1006 
1007     /**
1008      * Getter for the time that the connection was last used.
1009      *
1010      * @return - the timestamp
1011      */
1012     public long getLastUsedTimestamp() {
1013       return lastUsedTimestamp;
1014     }
1015 
1016     /**
1017      * Setter for the time that the connection was last used.
1018      *
1019      * @param lastUsedTimestamp
1020      *          - the timestamp
1021      */
1022     public void setLastUsedTimestamp(long lastUsedTimestamp) {
1023       this.lastUsedTimestamp = lastUsedTimestamp;
1024     }
1025 
1026     /**
1027      * Getter for the time since this connection was last used.
1028      *
1029      * @return - the time since the last use
1030      */
1031     public long getTimeElapsedSinceLastUse() {
1032       return System.currentTimeMillis() - lastUsedTimestamp;
1033     }
1034 
1035     /**
1036      * Getter for the age of the connection.
1037      *
1038      * @return the age
1039      */
1040     public long getAge() {
1041       return System.currentTimeMillis() - createdTimestamp;
1042     }
1043 
1044     /**
1045      * Getter for the timestamp that this connection was checked out.
1046      *
1047      * @return the timestamp
1048      */
1049     public long getCheckoutTimestamp() {
1050       return checkoutTimestamp;
1051     }
1052 
1053     /**
1054      * Setter for the timestamp that this connection was checked out.
1055      *
1056      * @param timestamp
1057      *          the timestamp
1058      */
1059     public void setCheckoutTimestamp(long timestamp) {
1060       this.checkoutTimestamp = timestamp;
1061     }
1062 
1063     /**
1064      * Getter for the time that this connection has been checked out.
1065      *
1066      * @return the time
1067      */
1068     public long getCheckoutTime() {
1069       return System.currentTimeMillis() - checkoutTimestamp;
1070     }
1071 
1072     /**
1073      * Gets the valid connection.
1074      *
1075      * @return the valid connection
1076      */
1077     private Connection getValidConnection() {
1078       if (!valid) {
1079         throw new RuntimeException("Error accessing SimplePooledConnection. Connection is invalid.");
1080       }
1081       return realConnection;
1082     }
1083 
1084     public int hashCode() {
1085       return hashCode;
1086     }
1087 
1088     /**
1089      * Allows comparing this connection to another
1090      *
1091      * @param obj
1092      *          - the other connection to test for equality
1093      *
1094      * @see java.lang.Object#equals(java.lang.Object)
1095      */
1096     public boolean equals(Object obj) {
1097       if (obj instanceof SimplePooledConnection) {
1098         return realConnection.hashCode() == (((SimplePooledConnection) obj).realConnection.hashCode());
1099       } else if (obj instanceof Connection) {
1100         return hashCode == obj.hashCode();
1101       } else {
1102         return false;
1103       }
1104     }
1105 
1106     // **********************************
1107     // Implemented Connection Methods -- Now handled by proxy
1108     // **********************************
1109 
1110     /**
1111      * Required for InvocationHandler implementation.
1112      *
1113      * @param proxy
1114      *          - not used
1115      * @param method
1116      *          - the method to be executed
1117      * @param args
1118      *          - the parameters to be passed to the method
1119      *
1120      * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
1121      */
1122     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1123       String methodName = method.getName();
1124       if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
1125         dataSource.pushConnection(this);
1126         return null;
1127       } else {
1128         try {
1129           return method.invoke(getValidConnection(), args);
1130         } catch (Throwable t) {
1131           throw ClassInfo.unwrapThrowable(t);
1132         }
1133       }
1134     }
1135 
1136     /**
1137      * Creates the statement.
1138      *
1139      * @return the statement
1140      *
1141      * @throws SQLException
1142      *           the SQL exception
1143      */
1144     public Statement createStatement() throws SQLException {
1145       return getValidConnection().createStatement();
1146     }
1147 
1148     /**
1149      * Prepare statement.
1150      *
1151      * @param sql
1152      *          the sql
1153      *
1154      * @return the prepared statement
1155      *
1156      * @throws SQLException
1157      *           the SQL exception
1158      */
1159     public PreparedStatement prepareStatement(String sql) throws SQLException {
1160       return getValidConnection().prepareStatement(sql);
1161     }
1162 
1163     /**
1164      * Prepare call.
1165      *
1166      * @param sql
1167      *          the sql
1168      *
1169      * @return the callable statement
1170      *
1171      * @throws SQLException
1172      *           the SQL exception
1173      */
1174     public CallableStatement prepareCall(String sql) throws SQLException {
1175       return getValidConnection().prepareCall(sql);
1176     }
1177 
1178     /**
1179      * Native SQL.
1180      *
1181      * @param sql
1182      *          the sql
1183      *
1184      * @return the string
1185      *
1186      * @throws SQLException
1187      *           the SQL exception
1188      */
1189     public String nativeSQL(String sql) throws SQLException {
1190       return getValidConnection().nativeSQL(sql);
1191     }
1192 
1193     /**
1194      * Sets the auto commit.
1195      *
1196      * @param autoCommit
1197      *          the new auto commit
1198      *
1199      * @throws SQLException
1200      *           the SQL exception
1201      */
1202     public void setAutoCommit(boolean autoCommit) throws SQLException {
1203       getValidConnection().setAutoCommit(autoCommit);
1204     }
1205 
1206     /**
1207      * Gets the auto commit.
1208      *
1209      * @return the auto commit
1210      *
1211      * @throws SQLException
1212      *           the SQL exception
1213      */
1214     public boolean getAutoCommit() throws SQLException {
1215       return getValidConnection().getAutoCommit();
1216     }
1217 
1218     /**
1219      * Commit.
1220      *
1221      * @throws SQLException
1222      *           the SQL exception
1223      */
1224     public void commit() throws SQLException {
1225       getValidConnection().commit();
1226     }
1227 
1228     /**
1229      * Rollback.
1230      *
1231      * @throws SQLException
1232      *           the SQL exception
1233      */
1234     public void rollback() throws SQLException {
1235       getValidConnection().rollback();
1236     }
1237 
1238     /**
1239      * Close.
1240      *
1241      * @throws SQLException
1242      *           the SQL exception
1243      */
1244     public void close() throws SQLException {
1245       dataSource.pushConnection(this);
1246     }
1247 
1248     /**
1249      * Checks if is closed.
1250      *
1251      * @return true, if is closed
1252      *
1253      * @throws SQLException
1254      *           the SQL exception
1255      */
1256     public boolean isClosed() throws SQLException {
1257       return getValidConnection().isClosed();
1258     }
1259 
1260     /**
1261      * Gets the meta data.
1262      *
1263      * @return the meta data
1264      *
1265      * @throws SQLException
1266      *           the SQL exception
1267      */
1268     public DatabaseMetaData getMetaData() throws SQLException {
1269       return getValidConnection().getMetaData();
1270     }
1271 
1272     /**
1273      * Sets the read only.
1274      *
1275      * @param readOnly
1276      *          the new read only
1277      *
1278      * @throws SQLException
1279      *           the SQL exception
1280      */
1281     public void setReadOnly(boolean readOnly) throws SQLException {
1282       getValidConnection().setReadOnly(readOnly);
1283     }
1284 
1285     /**
1286      * Checks if is read only.
1287      *
1288      * @return true, if is read only
1289      *
1290      * @throws SQLException
1291      *           the SQL exception
1292      */
1293     public boolean isReadOnly() throws SQLException {
1294       return getValidConnection().isReadOnly();
1295     }
1296 
1297     /**
1298      * Sets the catalog.
1299      *
1300      * @param catalog
1301      *          the new catalog
1302      *
1303      * @throws SQLException
1304      *           the SQL exception
1305      */
1306     public void setCatalog(String catalog) throws SQLException {
1307       getValidConnection().setCatalog(catalog);
1308     }
1309 
1310     /**
1311      * Gets the catalog.
1312      *
1313      * @return the catalog
1314      *
1315      * @throws SQLException
1316      *           the SQL exception
1317      */
1318     public String getCatalog() throws SQLException {
1319       return getValidConnection().getCatalog();
1320     }
1321 
1322     /**
1323      * Sets the transaction isolation.
1324      *
1325      * @param level
1326      *          the new transaction isolation
1327      *
1328      * @throws SQLException
1329      *           the SQL exception
1330      */
1331     public void setTransactionIsolation(int level) throws SQLException {
1332       getValidConnection().setTransactionIsolation(level);
1333     }
1334 
1335     /**
1336      * Gets the transaction isolation.
1337      *
1338      * @return the transaction isolation
1339      *
1340      * @throws SQLException
1341      *           the SQL exception
1342      */
1343     public int getTransactionIsolation() throws SQLException {
1344       return getValidConnection().getTransactionIsolation();
1345     }
1346 
1347     /**
1348      * Gets the warnings.
1349      *
1350      * @return the warnings
1351      *
1352      * @throws SQLException
1353      *           the SQL exception
1354      */
1355     public SQLWarning getWarnings() throws SQLException {
1356       return getValidConnection().getWarnings();
1357     }
1358 
1359     /**
1360      * Clear warnings.
1361      *
1362      * @throws SQLException
1363      *           the SQL exception
1364      */
1365     public void clearWarnings() throws SQLException {
1366       getValidConnection().clearWarnings();
1367     }
1368 
1369     /**
1370      * Creates the statement.
1371      *
1372      * @param resultSetType
1373      *          the result set type
1374      * @param resultSetConcurrency
1375      *          the result set concurrency
1376      *
1377      * @return the statement
1378      *
1379      * @throws SQLException
1380      *           the SQL exception
1381      */
1382     public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
1383       return getValidConnection().createStatement(resultSetType, resultSetConcurrency);
1384     }
1385 
1386     /**
1387      * Prepare statement.
1388      *
1389      * @param sql
1390      *          the sql
1391      * @param resultSetType
1392      *          the result set type
1393      * @param resultSetConcurrency
1394      *          the result set concurrency
1395      *
1396      * @return the prepared statement
1397      *
1398      * @throws SQLException
1399      *           the SQL exception
1400      */
1401     public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
1402         throws SQLException {
1403       return getValidConnection().prepareCall(sql, resultSetType, resultSetConcurrency);
1404     }
1405 
1406     /**
1407      * Prepare call.
1408      *
1409      * @param sql
1410      *          the sql
1411      * @param resultSetType
1412      *          the result set type
1413      * @param resultSetConcurrency
1414      *          the result set concurrency
1415      *
1416      * @return the callable statement
1417      *
1418      * @throws SQLException
1419      *           the SQL exception
1420      */
1421     public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
1422       return getValidConnection().prepareCall(sql, resultSetType, resultSetConcurrency);
1423     }
1424 
1425     /**
1426      * Gets the type map.
1427      *
1428      * @return the type map
1429      *
1430      * @throws SQLException
1431      *           the SQL exception
1432      */
1433     public Map getTypeMap() throws SQLException {
1434       return getValidConnection().getTypeMap();
1435     }
1436 
1437     /**
1438      * Sets the type map.
1439      *
1440      * @param map
1441      *          the new type map
1442      *
1443      * @throws SQLException
1444      *           the SQL exception
1445      */
1446     public void setTypeMap(Map map) throws SQLException {
1447       getValidConnection().setTypeMap(map);
1448     }
1449 
1450     // **********************************
1451     // JDK 1.4 JDBC 3.0 Methods below
1452     // **********************************
1453 
1454     /**
1455      * Sets the holdability.
1456      *
1457      * @param holdability
1458      *          the new holdability
1459      *
1460      * @throws SQLException
1461      *           the SQL exception
1462      */
1463     public void setHoldability(int holdability) throws SQLException {
1464       getValidConnection().setHoldability(holdability);
1465     }
1466 
1467     /**
1468      * Gets the holdability.
1469      *
1470      * @return the holdability
1471      *
1472      * @throws SQLException
1473      *           the SQL exception
1474      */
1475     public int getHoldability() throws SQLException {
1476       return getValidConnection().getHoldability();
1477     }
1478 
1479     /**
1480      * Sets the savepoint.
1481      *
1482      * @return the savepoint
1483      *
1484      * @throws SQLException
1485      *           the SQL exception
1486      */
1487     public Savepoint setSavepoint() throws SQLException {
1488       return getValidConnection().setSavepoint();
1489     }
1490 
1491     /**
1492      * Sets the savepoint.
1493      *
1494      * @param name
1495      *          the name
1496      *
1497      * @return the savepoint
1498      *
1499      * @throws SQLException
1500      *           the SQL exception
1501      */
1502     public Savepoint setSavepoint(String name) throws SQLException {
1503       return getValidConnection().setSavepoint(name);
1504     }
1505 
1506     /**
1507      * Rollback.
1508      *
1509      * @param savepoint
1510      *          the savepoint
1511      *
1512      * @throws SQLException
1513      *           the SQL exception
1514      */
1515     public void rollback(Savepoint savepoint) throws SQLException {
1516       getValidConnection().rollback(savepoint);
1517     }
1518 
1519     /**
1520      * Release savepoint.
1521      *
1522      * @param savepoint
1523      *          the savepoint
1524      *
1525      * @throws SQLException
1526      *           the SQL exception
1527      */
1528     public void releaseSavepoint(Savepoint savepoint) throws SQLException {
1529       getValidConnection().releaseSavepoint(savepoint);
1530     }
1531 
1532     /**
1533      * Creates the statement.
1534      *
1535      * @param resultSetType
1536      *          the result set type
1537      * @param resultSetConcurrency
1538      *          the result set concurrency
1539      * @param resultSetHoldability
1540      *          the result set holdability
1541      *
1542      * @return the statement
1543      *
1544      * @throws SQLException
1545      *           the SQL exception
1546      */
1547     public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
1548         throws SQLException {
1549       return getValidConnection().createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
1550     }
1551 
1552     /**
1553      * Prepare statement.
1554      *
1555      * @param sql
1556      *          the sql
1557      * @param resultSetType
1558      *          the result set type
1559      * @param resultSetConcurrency
1560      *          the result set concurrency
1561      * @param resultSetHoldability
1562      *          the result set holdability
1563      *
1564      * @return the prepared statement
1565      *
1566      * @throws SQLException
1567      *           the SQL exception
1568      */
1569     public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
1570         int resultSetHoldability) throws SQLException {
1571       return getValidConnection().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
1572     }
1573 
1574     /**
1575      * Prepare call.
1576      *
1577      * @param sql
1578      *          the sql
1579      * @param resultSetType
1580      *          the result set type
1581      * @param resultSetConcurrency
1582      *          the result set concurrency
1583      * @param resultSetHoldability
1584      *          the result set holdability
1585      *
1586      * @return the callable statement
1587      *
1588      * @throws SQLException
1589      *           the SQL exception
1590      */
1591     public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
1592         int resultSetHoldability) throws SQLException {
1593       return getValidConnection().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
1594     }
1595 
1596     /**
1597      * Prepare statement.
1598      *
1599      * @param sql
1600      *          the sql
1601      * @param autoGeneratedKeys
1602      *          the auto generated keys
1603      *
1604      * @return the prepared statement
1605      *
1606      * @throws SQLException
1607      *           the SQL exception
1608      */
1609     public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
1610       return getValidConnection().prepareStatement(sql, autoGeneratedKeys);
1611     }
1612 
1613     /**
1614      * Prepare statement.
1615      *
1616      * @param sql
1617      *          the sql
1618      * @param columnIndexes
1619      *          the column indexes
1620      *
1621      * @return the prepared statement
1622      *
1623      * @throws SQLException
1624      *           the SQL exception
1625      */
1626     public PreparedStatement prepareStatement(String sql, int columnIndexes[]) throws SQLException {
1627       return getValidConnection().prepareStatement(sql, columnIndexes);
1628     }
1629 
1630     /**
1631      * Prepare statement.
1632      *
1633      * @param sql
1634      *          the sql
1635      * @param columnNames
1636      *          the column names
1637      *
1638      * @return the prepared statement
1639      *
1640      * @throws SQLException
1641      *           the SQL exception
1642      */
1643     public PreparedStatement prepareStatement(String sql, String columnNames[]) throws SQLException {
1644       return getValidConnection().prepareStatement(sql, columnNames);
1645     }
1646 
1647   }
1648 
1649   public Logger getParentLogger() throws SQLFeatureNotSupportedException {
1650     // TODO Auto-generated method stub
1651     return null;
1652   }
1653 
1654   public <T> T unwrap(Class<T> iface) throws SQLException {
1655     // TODO Auto-generated method stub
1656     return null;
1657   }
1658 
1659   public boolean isWrapperFor(Class<?> iface) throws SQLException {
1660     // TODO Auto-generated method stub
1661     return false;
1662   }
1663 }