KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ibatis > common > jdbc > SimpleDataSource


1 /*
2  * Copyright 2004 Clinton Begin
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  * http://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.exception.NestedRuntimeException;
20 import com.ibatis.common.resources.Resources;
21 import com.ibatis.common.logging.LogFactory;
22 import com.ibatis.common.logging.Log;
23
24 import javax.sql.DataSource JavaDoc;
25 import java.io.PrintWriter JavaDoc;
26 import java.lang.reflect.InvocationHandler JavaDoc;
27 import java.lang.reflect.Method JavaDoc;
28 import java.lang.reflect.Proxy JavaDoc;
29 import java.sql.*;
30 import java.util.*;
31
32 /**
33  * This is a simple, synchronous, thread-safe database connection pool.
34  * <p/>
35  * REQUIRED PROPERTIES
36  * -------------------
37  * JDBC.Driver
38  * JDBC.ConnectionURL
39  * JDBC.Username
40  * JDBC.Password
41  * <p/>
42  * Pool.MaximumActiveConnections
43  * Pool.MaximumIdleConnections
44  * Pool.MaximumCheckoutTime
45  * Pool.TimeToWait
46  * Pool.PingQuery
47  * Pool.PingEnabled
48  * Pool.PingConnectionsOlderThan
49  * Pool.PingConnectionsNotUsedFor
50  * Pool.QuietMode
51  */

52 public class SimpleDataSource implements DataSource JavaDoc {
53
54   private static final Log log = LogFactory.getLog(SimpleDataSource.class);
55
56   // Required Properties
57
private static final String JavaDoc PROP_JDBC_DRIVER = "JDBC.Driver";
58   private static final String JavaDoc PROP_JDBC_URL = "JDBC.ConnectionURL";
59   private static final String JavaDoc PROP_JDBC_USERNAME = "JDBC.Username";
60   private static final String JavaDoc PROP_JDBC_PASSWORD = "JDBC.Password";
61   private static final String JavaDoc PROP_JDBC_DEFAULT_AUTOCOMMIT = "JDBC.DefaultAutoCommit";
62
63   // Optional Properties
64
private static final String JavaDoc PROP_POOL_MAX_ACTIVE_CONN = "Pool.MaximumActiveConnections";
65   private static final String JavaDoc PROP_POOL_MAX_IDLE_CONN = "Pool.MaximumIdleConnections";
66   private static final String JavaDoc PROP_POOL_MAX_CHECKOUT_TIME = "Pool.MaximumCheckoutTime";
67   private static final String JavaDoc PROP_POOL_TIME_TO_WAIT = "Pool.TimeToWait";
68   private static final String JavaDoc PROP_POOL_PING_QUERY = "Pool.PingQuery";
69   private static final String JavaDoc PROP_POOL_PING_CONN_OLDER_THAN = "Pool.PingConnectionsOlderThan";
70   private static final String JavaDoc PROP_POOL_PING_ENABLED = "Pool.PingEnabled";
71   private static final String JavaDoc PROP_POOL_PING_CONN_NOT_USED_FOR = "Pool.PingConnectionsNotUsedFor";
72   private int expectedConnectionTypeCode;
73   // Additional Driver Properties prefix
74
private static final String JavaDoc ADD_DRIVER_PROPS_PREFIX = "Driver.";
75   private static final int ADD_DRIVER_PROPS_PREFIX_LENGTH = ADD_DRIVER_PROPS_PREFIX.length();
76
77   // ----- BEGIN: FIELDS LOCKED BY POOL_LOCK -----
78
private final Object JavaDoc POOL_LOCK = new Object JavaDoc();
79   private List idleConnections = new ArrayList();
80   private List activeConnections = new ArrayList();
81   private long requestCount = 0;
82   private long accumulatedRequestTime = 0;
83   private long accumulatedCheckoutTime = 0;
84   private long claimedOverdueConnectionCount = 0;
85   private long accumulatedCheckoutTimeOfOverdueConnections = 0;
86   private long accumulatedWaitTime = 0;
87   private long hadToWaitCount = 0;
88   private long badConnectionCount = 0;
89   // ----- END: FIELDS LOCKED BY POOL_LOCK -----
90

91   // ----- BEGIN: PROPERTY FIELDS FOR CONFIGURATION -----
92
private String JavaDoc jdbcDriver;
93   private String JavaDoc jdbcUrl;
94   private String JavaDoc jdbcUsername;
95   private String JavaDoc jdbcPassword;
96   private boolean jdbcDefaultAutoCommit;
97   private Properties driverProps;
98   private boolean useDriverProps;
99
100   private int poolMaximumActiveConnections;
101   private int poolMaximumIdleConnections;
102   private int poolMaximumCheckoutTime;
103   private int poolTimeToWait;
104   private String JavaDoc poolPingQuery;
105   private boolean poolPingEnabled;
106   private int poolPingConnectionsOlderThan;
107   private int poolPingConnectionsNotUsedFor;
108   //----- END: PROPERTY FIELDS FOR CONFIGURATION -----
109

110   /**
111    * Constructor to allow passing in a map of properties for configuration
112    *
113    * @param props - the configuration parameters
114    */

115   public SimpleDataSource(Map props) {
116     initialize(props);
117   }
118
119   private void initialize(Map props) {
120     try {
121       if (props == null) {
122         throw new NestedRuntimeException("SimpleDataSource: The properties map passed to the initializer was null.");
123       }
124
125       if (!(props.containsKey(PROP_JDBC_DRIVER)
126           && props.containsKey(PROP_JDBC_URL)
127           && props.containsKey(PROP_JDBC_USERNAME)
128           && props.containsKey(PROP_JDBC_PASSWORD))) {
129         throw new NestedRuntimeException("SimpleDataSource: Some properties were not set.");
130       } else {
131
132         jdbcDriver = (String JavaDoc) props.get(PROP_JDBC_DRIVER);
133         jdbcUrl = (String JavaDoc) props.get(PROP_JDBC_URL);
134         jdbcUsername = (String JavaDoc) props.get(PROP_JDBC_USERNAME);
135         jdbcPassword = (String JavaDoc) props.get(PROP_JDBC_PASSWORD);
136
137         poolMaximumActiveConnections =
138             props.containsKey(PROP_POOL_MAX_ACTIVE_CONN)
139             ? Integer.parseInt((String JavaDoc) props.get(PROP_POOL_MAX_ACTIVE_CONN))
140             : 10;
141
142         poolMaximumIdleConnections =
143             props.containsKey(PROP_POOL_MAX_IDLE_CONN)
144             ? Integer.parseInt((String JavaDoc) props.get(PROP_POOL_MAX_IDLE_CONN))
145             : 5;
146
147         poolMaximumCheckoutTime =
148             props.containsKey(PROP_POOL_MAX_CHECKOUT_TIME)
149             ? Integer.parseInt((String JavaDoc) props.get(PROP_POOL_MAX_CHECKOUT_TIME))
150             : 20000;
151
152         poolTimeToWait =
153             props.containsKey(PROP_POOL_TIME_TO_WAIT)
154             ? Integer.parseInt((String JavaDoc) props.get(PROP_POOL_TIME_TO_WAIT))
155             : 20000;
156
157         poolPingEnabled =
158             props.containsKey(PROP_POOL_PING_ENABLED)
159             ? Boolean.valueOf((String JavaDoc) props.get(PROP_POOL_PING_ENABLED)).booleanValue()
160             : false;
161
162         poolPingQuery =
163             props.containsKey(PROP_POOL_PING_QUERY)
164             ? (String JavaDoc) props.get(PROP_POOL_PING_QUERY)
165             : "NO PING QUERY SET";
166
167         poolPingConnectionsOlderThan =
168             props.containsKey(PROP_POOL_PING_CONN_OLDER_THAN)
169             ? Integer.parseInt((String JavaDoc) props.get(PROP_POOL_PING_CONN_OLDER_THAN))
170             : 0;
171
172         poolPingConnectionsNotUsedFor =
173             props.containsKey(PROP_POOL_PING_CONN_NOT_USED_FOR)
174             ? Integer.parseInt((String JavaDoc) props.get(PROP_POOL_PING_CONN_NOT_USED_FOR))
175             : 0;
176
177         jdbcDefaultAutoCommit =
178             props.containsKey(PROP_JDBC_DEFAULT_AUTOCOMMIT)
179             ? Boolean.valueOf((String JavaDoc) props.get(PROP_JDBC_DEFAULT_AUTOCOMMIT)).booleanValue()
180             : false;
181
182         useDriverProps = false;
183         Iterator propIter = props.keySet().iterator();
184         driverProps = new Properties();
185         driverProps.put("user", jdbcUsername);
186         driverProps.put("password", jdbcPassword);
187         while (propIter.hasNext()) {
188           String JavaDoc name = (String JavaDoc) propIter.next();
189           String JavaDoc value = (String JavaDoc) props.get(name);
190           if (name.startsWith(ADD_DRIVER_PROPS_PREFIX)) {
191             driverProps.put(name.substring(ADD_DRIVER_PROPS_PREFIX_LENGTH), value);
192             useDriverProps = true;
193           }
194         }
195
196         expectedConnectionTypeCode = assembleConnectionTypeCode(jdbcUrl, jdbcUsername, jdbcPassword);
197
198         Resources.instantiate(jdbcDriver);
199       }
200
201     } catch (Exception JavaDoc e) {
202       log.error("SimpleDataSource: Error while loading properties. Cause: " + e.toString(), e);
203       throw new NestedRuntimeException("SimpleDataSource: Error while loading properties. Cause: " + e, e);
204     }
205   }
206
207   private int assembleConnectionTypeCode(String JavaDoc url, String JavaDoc username, String JavaDoc password) {
208     return ("" + url + username + password).hashCode();
209   }
210
211   /**
212    * @see javax.sql.DataSource#getConnection()
213    */

214   public Connection getConnection() throws SQLException {
215     return popConnection(jdbcUsername, jdbcPassword).getProxyConnection();
216   }
217
218   /**
219    * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
220    */

221   public Connection getConnection(String JavaDoc username, String JavaDoc password) throws SQLException {
222     return popConnection(username, password).getProxyConnection();
223   }
224
225   /**
226    * @see javax.sql.DataSource#setLoginTimeout(int)
227    */

228   public void setLoginTimeout(int loginTimeout) throws SQLException {
229     DriverManager.setLoginTimeout(loginTimeout);
230   }
231
232   /**
233    * @see javax.sql.DataSource#getLoginTimeout()
234    */

235   public int getLoginTimeout() throws SQLException {
236     return DriverManager.getLoginTimeout();
237   }
238
239   /**
240    * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter)
241    */

242   public void setLogWriter(PrintWriter JavaDoc logWriter) throws SQLException {
243     DriverManager.setLogWriter(logWriter);
244   }
245
246   /**
247    * @see javax.sql.DataSource#getLogWriter()
248    */

249   public PrintWriter JavaDoc getLogWriter() throws SQLException {
250     return DriverManager.getLogWriter();
251   }
252
253   /**
254    * No idea what this is used for...
255    *
256    * @return
257    */

258   public int getPoolPingConnectionsNotUsedFor() {
259     return poolPingConnectionsNotUsedFor;
260   }
261
262   /**
263    * Getter for the name of the JDBC driver class used
264    * @return The name of the class
265    */

266   public String JavaDoc getJdbcDriver() {
267     return jdbcDriver;
268   }
269
270   /**
271    * Getter of the JDBC URL used
272    * @return The JDBC URL
273    */

274   public String JavaDoc getJdbcUrl() {
275     return jdbcUrl;
276   }
277
278   /**
279    * Getter for the JDBC user name used
280    * @return The user name
281    */

282   public String JavaDoc getJdbcUsername() {
283     return jdbcUsername;
284   }
285
286   /**
287    * Getter for the JDBC password used
288    * @return The password
289    */

290   public String JavaDoc getJdbcPassword() {
291     return jdbcPassword;
292   }
293
294   /**
295    * Getter for the maximum number of active connections
296    * @return The maximum number of active connections
297    */

298   public int getPoolMaximumActiveConnections() {
299     return poolMaximumActiveConnections;
300   }
301
302   /**
303    * Getter for the maximum number of idle connections
304    * @return The maximum number of idle connections
305    */

306   public int getPoolMaximumIdleConnections() {
307     return poolMaximumIdleConnections;
308   }
309
310   /**
311    * Getter for the maximum time a connection can be used before it *may* be
312    * given away again.
313    * @return The maximum time
314    */

315   public int getPoolMaximumCheckoutTime() {
316     return poolMaximumCheckoutTime;
317   }
318
319   /**
320    * Getter for the time to wait before retrying to get a connection
321    * @return The time to wait
322    */

323   public int getPoolTimeToWait() {
324     return poolTimeToWait;
325   }
326
327   /**
328    * Getter for the query to be used to check a connection
329    * @return The query
330    */

331   public String JavaDoc getPoolPingQuery() {
332     return poolPingQuery;
333   }
334
335   /**
336    * Getter to tell if we should use the ping query
337    * @return True if we need to check a connection before using it
338    */

339   public boolean isPoolPingEnabled() {
340     return poolPingEnabled;
341   }
342
343   /**
344    * Getter for the age of connections that should be pinged before using
345    * @return The age
346    */

347   public int getPoolPingConnectionsOlderThan() {
348     return poolPingConnectionsOlderThan;
349   }
350
351   private int getExpectedConnectionTypeCode() {
352     return expectedConnectionTypeCode;
353   }
354
355   /**
356    * Getter for the number of connection requests made
357    * @return The number of connection requests made
358    */

359   public long getRequestCount() {
360     synchronized (POOL_LOCK) {
361       return requestCount;
362     }
363   }
364
365   /**
366    * Getter for the average time required to get a connection to the database
367    * @return The average time
368    */

369   public long getAverageRequestTime() {
370     synchronized (POOL_LOCK) {
371       return requestCount == 0 ? 0 : accumulatedRequestTime / requestCount;
372     }
373   }
374
375   /**
376    * Getter for the average time spent waiting for connections that were in use
377    * @return The average time
378    */

379   public long getAverageWaitTime() {
380     synchronized (POOL_LOCK) {
381       return hadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount;
382     }
383   }
384
385   /**
386    * Getter for the number of requests that had to wait for connections that were in use
387    * @return The number of requests that had to wait
388    */

389   public long getHadToWaitCount() {
390     synchronized (POOL_LOCK) {
391       return hadToWaitCount;
392     }
393   }
394
395   /**
396    * Getter for the number of invalid connections that were found in the pool
397    * @return The number of invalid connections
398    */

399   public long getBadConnectionCount() {
400     synchronized (POOL_LOCK) {
401       return badConnectionCount;
402     }
403   }
404
405   /**
406    * Getter for the number of connections that were claimed before they were returned
407    * @return The number of connections
408    */

409   public long getClaimedOverdueConnectionCount() {
410     synchronized (POOL_LOCK) {
411       return claimedOverdueConnectionCount;
412     }
413   }
414
415   /**
416    * Getter for the average age of overdue connections
417    * @return The average age
418    */

419   public long getAverageOverdueCheckoutTime() {
420     synchronized (POOL_LOCK) {
421       return claimedOverdueConnectionCount == 0 ? 0 : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount;
422     }
423   }
424
425
426   /**
427    * Getter for the average age of a connection checkout
428    * @return The average age
429    */

430   public long getAverageCheckoutTime() {
431     synchronized (POOL_LOCK) {
432       return requestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount;
433     }
434   }
435
436   /**
437    * Returns the status of the connection pool
438    * @return The status
439    */

440   public String JavaDoc getStatus() {
441     StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
442
443     buffer.append("\n===============================================================");
444     buffer.append("\n jdbcDriver " + jdbcDriver);
445     buffer.append("\n jdbcUrl " + jdbcUrl);
446     buffer.append("\n jdbcUsername " + jdbcUsername);
447     buffer.append("\n jdbcPassword " + (jdbcPassword == null ? "NULL" : "************"));
448     buffer.append("\n poolMaxActiveConnections " + poolMaximumActiveConnections);
449     buffer.append("\n poolMaxIdleConnections " + poolMaximumIdleConnections);
450     buffer.append("\n poolMaxCheckoutTime " + poolMaximumCheckoutTime);
451     buffer.append("\n poolTimeToWait " + poolTimeToWait);
452     buffer.append("\n poolPingEnabled " + poolPingEnabled);
453     buffer.append("\n poolPingQuery " + poolPingQuery);
454     buffer.append("\n poolPingConnectionsOlderThan " + poolPingConnectionsOlderThan);
455     buffer.append("\n poolPingConnectionsNotUsedFor " + poolPingConnectionsNotUsedFor);
456     buffer.append("\n --------------------------------------------------------------");
457     buffer.append("\n activeConnections " + activeConnections.size());
458     buffer.append("\n idleConnections " + idleConnections.size());
459     buffer.append("\n requestCount " + getRequestCount());
460     buffer.append("\n averageRequestTime " + getAverageRequestTime());
461     buffer.append("\n averageCheckoutTime " + getAverageCheckoutTime());
462     buffer.append("\n claimedOverdue " + getClaimedOverdueConnectionCount());
463     buffer.append("\n averageOverdueCheckoutTime " + getAverageOverdueCheckoutTime());
464     buffer.append("\n hadToWait " + getHadToWaitCount());
465     buffer.append("\n averageWaitTime " + getAverageWaitTime());
466     buffer.append("\n badConnectionCount " + getBadConnectionCount());
467     buffer.append("\n===============================================================");
468     return buffer.toString();
469   }
470
471   /**
472    * Closes all of the connections in the pool
473    */

474   public void forceCloseAll() {
475     synchronized (POOL_LOCK) {
476       for (int i = activeConnections.size(); i > 0; i--) {
477         try {
478           SimplePooledConnection conn = (SimplePooledConnection) activeConnections.remove(i - 1);
479           conn.invalidate();
480
481           Connection realConn = conn.getRealConnection();
482           if (!realConn.getAutoCommit()) {
483             realConn.rollback();
484           }
485           realConn.close();
486         } catch (Exception JavaDoc e) {
487           // ignore
488
}
489       }
490       for (int i = idleConnections.size(); i > 0; i--) {
491         try {
492           SimplePooledConnection conn = (SimplePooledConnection) idleConnections.remove(i - 1);
493           conn.invalidate();
494
495           Connection realConn = conn.getRealConnection();
496           if (!realConn.getAutoCommit()) {
497             realConn.rollback();
498           }
499           realConn.close();
500         } catch (Exception JavaDoc e) {
501           // ignore
502
}
503       }
504     }
505     if (log.isDebugEnabled()) {
506       log.debug("SimpleDataSource forcefully closed/removed all connections.");
507     }
508   }
509
510   private void pushConnection(SimplePooledConnection conn)
511       throws SQLException {
512
513     synchronized (POOL_LOCK) {
514       activeConnections.remove(conn);
515       if (conn.isValid()) {
516         if (idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == getExpectedConnectionTypeCode()) {
517           accumulatedCheckoutTime += conn.getCheckoutTime();
518           if (!conn.getRealConnection().getAutoCommit()) {
519             conn.getRealConnection().rollback();
520           }
521           SimplePooledConnection newConn = new SimplePooledConnection(conn.getRealConnection(), this);
522           idleConnections.add(newConn);
523           newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
524           newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
525           conn.invalidate();
526           if (log.isDebugEnabled()) {
527             log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
528           }
529           POOL_LOCK.notifyAll();
530         } else {
531           accumulatedCheckoutTime += conn.getCheckoutTime();
532           if (!conn.getRealConnection().getAutoCommit()) {
533             conn.getRealConnection().rollback();
534           }
535           conn.getRealConnection().close();
536           if (log.isDebugEnabled()) {
537             log.debug("Closed connection " + conn.getRealHashCode() + ".");
538           }
539           conn.invalidate();
540         }
541       } else {
542         if (log.isDebugEnabled()) {
543           log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
544         }
545         badConnectionCount++;
546       }
547     }
548   }
549
550   private SimplePooledConnection popConnection(String JavaDoc username, String JavaDoc password)
551       throws SQLException {
552     boolean countedWait = false;
553     SimplePooledConnection conn = null;
554     long t = System.currentTimeMillis();
555     int localBadConnectionCount = 0;
556
557     while (conn == null) {
558       synchronized (POOL_LOCK) {
559         if (idleConnections.size() > 0) {
560           // Pool has available connection
561
conn = (SimplePooledConnection) idleConnections.remove(0);
562           if (log.isDebugEnabled()) {
563             log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
564           }
565         } else {
566           // Pool does not have available connection
567
if (activeConnections.size() < poolMaximumActiveConnections) {
568             // Can create new connection
569
if (useDriverProps) {
570               conn = new SimplePooledConnection(DriverManager.getConnection(jdbcUrl, driverProps), this);
571             } else {
572               conn = new SimplePooledConnection(DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword), this);
573             }
574             Connection realConn = conn.getRealConnection();
575             if (realConn.getAutoCommit() != jdbcDefaultAutoCommit) {
576               realConn.setAutoCommit(jdbcDefaultAutoCommit);
577             }
578             if (log.isDebugEnabled()) {
579               log.debug("Created connection " + conn.getRealHashCode() + ".");
580             }
581           } else {
582             // Cannot create new connection
583
SimplePooledConnection oldestActiveConnection = (SimplePooledConnection) activeConnections.get(0);
584             long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
585             if (longestCheckoutTime > poolMaximumCheckoutTime) {
586               // Can claim overdue connection
587
claimedOverdueConnectionCount++;
588               accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
589               accumulatedCheckoutTime += longestCheckoutTime;
590               activeConnections.remove(oldestActiveConnection);
591               if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
592                 oldestActiveConnection.getRealConnection().rollback();
593               }
594               conn = new SimplePooledConnection(oldestActiveConnection.getRealConnection(), this);
595               oldestActiveConnection.invalidate();
596               if (log.isDebugEnabled()) {
597                 log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
598               }
599             } else {
600               // Must wait
601
try {
602                 if (!countedWait) {
603                   hadToWaitCount++;
604                   countedWait = true;
605                 }
606                 if (log.isDebugEnabled()) {
607                   log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
608                 }
609                 long wt = System.currentTimeMillis();
610                 POOL_LOCK.wait(poolTimeToWait);
611                 accumulatedWaitTime += System.currentTimeMillis() - wt;
612               } catch (InterruptedException JavaDoc e) {
613                 break;
614               }
615             }
616           }
617         }
618         if (conn != null) {
619           if (conn.isValid()) {
620             if (!conn.getRealConnection().getAutoCommit()) {
621               conn.getRealConnection().rollback();
622             }
623             conn.setConnectionTypeCode(assembleConnectionTypeCode(jdbcUrl, username, password));
624             conn.setCheckoutTimestamp(System.currentTimeMillis());
625             conn.setLastUsedTimestamp(System.currentTimeMillis());
626             activeConnections.add(conn);
627             requestCount++;
628             accumulatedRequestTime += System.currentTimeMillis() - t;
629           } else {
630             if (log.isDebugEnabled()) {
631               log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
632             }
633             badConnectionCount++;
634             localBadConnectionCount++;
635             conn = null;
636             if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
637               if (log.isDebugEnabled()) {
638                 log.debug("SimpleDataSource: Could not get a good connection to the database.");
639               }
640               throw new SQLException("SimpleDataSource: Could not get a good connection to the database.");
641             }
642           }
643         }
644       }
645
646     }
647
648     if (conn == null) {
649       if (log.isDebugEnabled()) {
650         log.debug("SimpleDataSource: Unknown severe error condition. The connection pool returned a null connection.");
651       }
652       throw new SQLException("SimpleDataSource: Unknown severe error condition. The connection pool returned a null connection.");
653     }
654
655     return conn;
656   }
657
658   /**
659    * Method to check to see if a connection is still usable
660    *
661    * @param conn - the connection to check
662    * @return True if the connection is still usable
663    */

664   private boolean pingConnection(SimplePooledConnection conn) {
665     boolean result = true;
666
667     try {
668       result = !conn.getRealConnection().isClosed();
669     } catch (SQLException e) {
670       result = false;
671     }
672
673     if (result) {
674       if (poolPingEnabled) {
675         if ((poolPingConnectionsOlderThan > 0 && conn.getAge() > poolPingConnectionsOlderThan)
676             || (poolPingConnectionsNotUsedFor > 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor)) {
677
678           try {
679             if (log.isDebugEnabled()) {
680               log.debug("Testing connection " + conn.getRealHashCode() + "...");
681             }
682             Connection realConn = conn.getRealConnection();
683             Statement statement = realConn.createStatement();
684             ResultSet rs = statement.executeQuery(poolPingQuery);
685             rs.close();
686             statement.close();
687             if (!realConn.getAutoCommit()) {
688               realConn.rollback();
689             }
690             result = true;
691             if (log.isDebugEnabled()) {
692               log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
693             }
694           } catch (Exception JavaDoc e) {
695             try {
696               conn.getRealConnection().close();
697             } catch (Exception JavaDoc e2) {
698               //ignore
699
}
700             result = false;
701             if (log.isDebugEnabled()) {
702               log.debug("Connection " + conn.getRealHashCode() + " is BAD!");
703             }
704           }
705         }
706       }
707     }
708     return result;
709   }
710
711   /**
712    * Unwraps a pooled connection to get to the 'real' connection
713    *
714    * @param conn - the pooled connection to unwrap
715    * @return The 'real' connection
716    */

717   public static Connection unwrapConnection(Connection conn) {
718     if (conn instanceof SimplePooledConnection) {
719       return ((SimplePooledConnection) conn).getRealConnection();
720     } else {
721       return conn;
722     }
723   }
724
725   protected void finalize() throws Throwable JavaDoc {
726     forceCloseAll();
727   }
728
729   /**
730    * ---------------------------------------------------------------------------------------
731    * SimplePooledConnection
732    * ---------------------------------------------------------------------------------------
733    */

734   private static class SimplePooledConnection implements InvocationHandler JavaDoc {
735
736     private static final String JavaDoc CLOSE = "close";
737     private static final Class JavaDoc[] IFACES = new Class JavaDoc[]{Connection.class};
738
739     private int hashCode = 0;
740     private SimpleDataSource dataSource;
741     private Connection realConnection;
742     private Connection proxyConnection;
743     private long checkoutTimestamp;
744     private long createdTimestamp;
745     private long lastUsedTimestamp;
746     private int connectionTypeCode;
747     private boolean valid;
748
749     /**
750      * Constructor for SimplePooledConnection that uses the Connection and SimpleDataSource passed in
751      *
752      * @param connection - the connection that is to be presented as a pooled connection
753      * @param dataSource - the dataSource that the connection is from
754      */

755     public SimplePooledConnection(Connection connection, SimpleDataSource dataSource) {
756       this.hashCode = connection.hashCode();
757       this.realConnection = connection;
758       this.dataSource = dataSource;
759       this.createdTimestamp = System.currentTimeMillis();
760       this.lastUsedTimestamp = System.currentTimeMillis();
761       this.valid = true;
762
763       proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
764     }
765
766     /**
767      * Invalidates the connection
768      */

769     public void invalidate() {
770       valid = false;
771     }
772
773     /**
774      * Method to see if the connection is usable
775      *
776      * @return True if the connection is usable
777      */

778     public boolean isValid() {
779       return valid && realConnection != null && dataSource.pingConnection(this);
780     }
781
782     /**
783      * Getter for the *real* connection that this wraps
784      * @return The connection
785      */

786     public Connection getRealConnection() {
787       return realConnection;
788     }
789
790     /**
791      * Getter for the proxy for the connection
792      * @return The proxy
793      */

794     public Connection getProxyConnection() {
795       return proxyConnection;
796     }
797
798     /**
799      * Gets the hashcode of the real connection (or 0 if it is null)
800      *
801      * @return The hashcode of the real connection (or 0 if it is null)
802      */

803     public int getRealHashCode() {
804       if (realConnection == null) {
805         return 0;
806       } else {
807         return realConnection.hashCode();
808       }
809     }
810
811     /**
812      * Getter for the connection type (based on url + user + password)
813      * @return The connection type
814      */

815     public int getConnectionTypeCode() {
816       return connectionTypeCode;
817     }
818
819     /**
820      * Setter for the connection type
821      * @param connectionTypeCode - the connection type
822      */

823     public void setConnectionTypeCode(int connectionTypeCode) {
824       this.connectionTypeCode = connectionTypeCode;
825     }
826
827     /**
828      * Getter for the time that the connection was created
829      * @return The creation timestamp
830      */

831     public long getCreatedTimestamp() {
832       return createdTimestamp;
833     }
834
835     /**
836      * Setter for the time that the connection was created
837      * @param createdTimestamp - the timestamp
838      */

839     public void setCreatedTimestamp(long createdTimestamp) {
840       this.createdTimestamp = createdTimestamp;
841     }
842
843     /**
844      * Getter for the time that the connection was last used
845      * @return - the timestamp
846      */

847     public long getLastUsedTimestamp() {
848       return lastUsedTimestamp;
849     }
850
851     /**
852      * Setter for the time that the connection was last used
853      * @param lastUsedTimestamp - the timestamp
854      */

855     public void setLastUsedTimestamp(long lastUsedTimestamp) {
856       this.lastUsedTimestamp = lastUsedTimestamp;
857     }
858
859     /**
860      * Getter for the time since this connection was last used
861      * @return - the time since the last use
862      */

863     public long getTimeElapsedSinceLastUse() {
864       return System.currentTimeMillis() - lastUsedTimestamp;
865     }
866
867     /**
868      * Getter for the age of the connection
869      * @return the age
870      */

871     public long getAge() {
872       return System.currentTimeMillis() - createdTimestamp;
873     }
874
875     /**
876      * Getter for the timestamp that this connection was checked out
877      * @return the timestamp
878      */

879     public long getCheckoutTimestamp() {
880       return checkoutTimestamp;
881     }
882
883     /**
884      * Setter for the timestamp that this connection was checked out
885      * @param timestamp the timestamp
886      */

887     public void setCheckoutTimestamp(long timestamp) {
888       this.checkoutTimestamp = timestamp;
889     }
890
891     /**
892      * Getter for the time that this connection has been checked out
893      * @return the time
894      */

895     public long getCheckoutTime() {
896       return System.currentTimeMillis() - checkoutTimestamp;
897     }
898
899     private Connection getValidConnection() {
900       if (!valid) {
901         throw new NestedRuntimeException("Error accessing SimplePooledConnection. Connection has been invalidated (probably released back to the pool).");
902       }
903       return realConnection;
904     }
905
906     public int hashCode() {
907       return hashCode;
908     }
909
910     /**
911      * Allows comparing this connection to another
912      *
913      * @param obj - the other connection to test for equality
914      * @see java.lang.Object#equals(java.lang.Object)
915      */

916     public boolean equals(Object JavaDoc obj) {
917       if (obj instanceof SimplePooledConnection) {
918         return realConnection.hashCode() == (((SimplePooledConnection) obj).realConnection.hashCode());
919       } else if (obj instanceof Connection) {
920         return hashCode == obj.hashCode();
921       } else {
922         return false;
923       }
924     }
925
926     // **********************************
927
// Implemented Connection Methods -- Now handled by proxy
928
// **********************************
929

930     /**
931      * Required for InvocationHandler inplementaion.
932      *
933      * @param proxy - not used
934      * @param method - the method to be executed
935      * @param args - the parameters to be passed to the method
936      * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
937      */

938     public Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args)
939         throws Throwable JavaDoc {
940       String JavaDoc methodName = method.getName();
941       if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
942         dataSource.pushConnection(this);
943         return null;
944       } else {
945         try {
946           return method.invoke(getValidConnection(), args);
947         } catch (Throwable JavaDoc t) {
948           throw ClassInfo.unwrapThrowable(t);
949         }
950       }
951     }
952
953     public Statement createStatement() throws SQLException {
954       return getValidConnection().createStatement();
955     }
956
957     public PreparedStatement prepareStatement(String JavaDoc sql) throws SQLException {
958       return getValidConnection().prepareStatement(sql);
959     }
960
961     public CallableStatement prepareCall(String JavaDoc sql) throws SQLException {
962       return getValidConnection().prepareCall(sql);
963     }
964
965     public String JavaDoc nativeSQL(String JavaDoc sql) throws SQLException {
966       return getValidConnection().nativeSQL(sql);
967     }
968
969     public void setAutoCommit(boolean autoCommit) throws SQLException {
970       getValidConnection().setAutoCommit(autoCommit);
971     }
972
973     public boolean getAutoCommit() throws SQLException {
974       return getValidConnection().getAutoCommit();
975     }
976
977     public void commit() throws SQLException {
978       getValidConnection().commit();
979     }
980
981     public void rollback() throws SQLException {
982       getValidConnection().rollback();
983     }
984
985     public void close() throws SQLException {
986       dataSource.pushConnection(this);
987     }
988
989     public boolean isClosed() throws SQLException {
990       return getValidConnection().isClosed();
991     }
992
993     public DatabaseMetaData getMetaData() throws SQLException {
994       return getValidConnection().getMetaData();
995     }
996
997     public void setReadOnly(boolean readOnly) throws SQLException {
998       getValidConnection().setReadOnly(readOnly);
999     }
1000
1001    public boolean isReadOnly() throws SQLException {
1002      return getValidConnection().isReadOnly();
1003    }
1004
1005    public void setCatalog(String JavaDoc catalog) throws SQLException {
1006      getValidConnection().setCatalog(catalog);
1007    }
1008
1009    public String JavaDoc getCatalog() throws SQLException {
1010      return getValidConnection().getCatalog();
1011    }
1012
1013    public void setTransactionIsolation(int level) throws SQLException {
1014      getValidConnection().setTransactionIsolation(level);
1015    }
1016
1017    public int getTransactionIsolation() throws SQLException {
1018      return getValidConnection().getTransactionIsolation();
1019    }
1020
1021    public SQLWarning getWarnings() throws SQLException {
1022      return getValidConnection().getWarnings();
1023    }
1024
1025    public void clearWarnings() throws SQLException {
1026      getValidConnection().clearWarnings();
1027    }
1028
1029    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
1030      return getValidConnection().createStatement(resultSetType, resultSetConcurrency);
1031    }
1032
1033    public PreparedStatement prepareStatement(String JavaDoc sql, int resultSetType, int resultSetConcurrency) throws SQLException {
1034      return getValidConnection().prepareCall(sql, resultSetType, resultSetConcurrency);
1035    }
1036
1037    public CallableStatement prepareCall(String JavaDoc sql, int resultSetType, int resultSetConcurrency) throws SQLException {
1038      return getValidConnection().prepareCall(sql, resultSetType, resultSetConcurrency);
1039    }
1040
1041    public Map getTypeMap() throws SQLException {
1042      return getValidConnection().getTypeMap();
1043    }
1044
1045    public void setTypeMap(Map map) throws SQLException {
1046      getValidConnection().setTypeMap(map);
1047    }
1048
1049    // **********************************
1050
// JDK 1.4 JDBC 3.0 Methods below
1051
// **********************************
1052

1053    public void setHoldability(int holdability) throws SQLException {
1054      getValidConnection().setHoldability(holdability);
1055    }
1056
1057    public int getHoldability() throws SQLException {
1058      return getValidConnection().getHoldability();
1059    }
1060
1061    public Savepoint setSavepoint() throws SQLException {
1062      return getValidConnection().setSavepoint();
1063    }
1064
1065    public Savepoint setSavepoint(String JavaDoc name) throws SQLException {
1066      return getValidConnection().setSavepoint(name);
1067    }
1068
1069    public void rollback(Savepoint savepoint) throws SQLException {
1070      getValidConnection().rollback(savepoint);
1071    }
1072
1073    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
1074      getValidConnection().releaseSavepoint(savepoint);
1075    }
1076
1077    public Statement createStatement(int resultSetType, int resultSetConcurrency,
1078                                     int resultSetHoldability) throws SQLException {
1079      return getValidConnection().createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
1080    }
1081
1082    public PreparedStatement prepareStatement(String JavaDoc sql, int resultSetType,
1083                                              int resultSetConcurrency, int resultSetHoldability)
1084        throws SQLException {
1085      return getValidConnection().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
1086    }
1087
1088    public CallableStatement prepareCall(String JavaDoc sql, int resultSetType,
1089                                         int resultSetConcurrency,
1090                                         int resultSetHoldability) throws SQLException {
1091      return getValidConnection().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
1092    }
1093
1094    public PreparedStatement prepareStatement(String JavaDoc sql, int autoGeneratedKeys)
1095        throws SQLException {
1096      return getValidConnection().prepareStatement(sql, autoGeneratedKeys);
1097    }
1098
1099    public PreparedStatement prepareStatement(String JavaDoc sql, int columnIndexes[])
1100        throws SQLException {
1101      return getValidConnection().prepareStatement(sql, columnIndexes);
1102    }
1103
1104    public PreparedStatement prepareStatement(String JavaDoc sql, String JavaDoc columnNames[])
1105        throws SQLException {
1106      return getValidConnection().prepareStatement(sql, columnNames);
1107    }
1108
1109
1110  }
1111}
1112
Popular Tags