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