001package org.avaje.datasource.pool;
002
003import org.avaje.datasource.DataSourceAlert;
004import org.avaje.datasource.DataSourceConfig;
005import org.avaje.datasource.DataSourcePool;
006import org.avaje.datasource.DataSourcePoolListener;
007import org.avaje.datasource.PoolStatistics;
008import org.avaje.datasource.PoolStatus;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012import java.io.PrintWriter;
013import java.sql.Connection;
014import java.sql.DriverManager;
015import java.sql.ResultSet;
016import java.sql.SQLException;
017import java.sql.SQLFeatureNotSupportedException;
018import java.sql.Statement;
019import java.util.Map;
020import java.util.Map.Entry;
021import java.util.Properties;
022import java.util.Set;
023import java.util.Timer;
024import java.util.TimerTask;
025
026/**
027 * A robust DataSource implementation.
028 * <p>
029 * <ul>
030 * <li>Manages the number of connections closing connections that have been idle for some time.</li>
031 * <li>Notifies when the datasource goes down and comes back up.</li>
032 * <li>Provides PreparedStatement caching</li>
033 * <li>Knows the busy connections</li>
034 * <li>Traces connections that have been leaked</li>
035 * </ul>
036 * </p>
037 */
038public class ConnectionPool implements DataSourcePool {
039
040  private static final Logger logger = LoggerFactory.getLogger(ConnectionPool.class);
041
042  /**
043   * The name given to this dataSource.
044   */
045  private final String name;
046
047  /**
048   * Used to notify of changes to the DataSource status.
049   */
050  private final DataSourceAlert notify;
051
052  /**
053   * Optional listener that can be notified when connections are got from and
054   * put back into the pool.
055   */
056  private final DataSourcePoolListener poolListener;
057
058  /**
059   * Properties used to create a Connection.
060   */
061  private final Properties connectionProps;
062
063  /**
064   * The jdbc connection url.
065   */
066  private final String databaseUrl;
067
068  /**
069   * The jdbc driver.
070   */
071  private final String databaseDriver;
072
073  /**
074   * The sql used to test a connection.
075   */
076  private final String heartbeatsql;
077
078  private final int heartbeatFreqSecs;
079
080  private final int heartbeatTimeoutSeconds;
081
082
083  private final long trimPoolFreqMillis;
084
085  /**
086   * The transaction isolation level as per java.sql.Connection.
087   */
088  private final int transactionIsolation;
089
090  /**
091   * The default autoCommit setting for Connections in this pool.
092   */
093  private final boolean autoCommit;
094
095  private final boolean readOnly;
096
097  /**
098   * Max idle time in millis.
099   */
100  private final int maxInactiveMillis;
101
102  /**
103   * Max age a connection is allowed in millis.
104   * A value of 0 means no limit (no trimming based on max age).
105   */
106  private final long maxAgeMillis;
107
108  /**
109   * Flag set to true to capture stackTraces (can be expensive).
110   */
111  private boolean captureStackTrace;
112
113  /**
114   * The max size of the stack trace to report.
115   */
116  private final int maxStackTraceSize;
117
118  /**
119   * flag to indicate we have sent an alert message.
120   */
121  private boolean dataSourceDownAlertSent;
122
123  /**
124   * The time the pool was last trimmed.
125   */
126  private long lastTrimTime;
127
128  /**
129   * Assume that the DataSource is up. heartBeat checking will discover when
130   * it goes down, and comes back up again.
131   */
132  private boolean dataSourceUp = true;
133
134  /**
135   * The current alert.
136   */
137  private boolean inWarningMode;
138
139  /**
140   * The minimum number of connections this pool will maintain.
141   */
142  private int minConnections;
143
144  /**
145   * The maximum number of connections this pool will grow to.
146   */
147  private int maxConnections;
148
149  /**
150   * The number of connections to exceed before a warning Alert is fired.
151   */
152  private int warningSize;
153
154  /**
155   * The time a thread will wait for a connection to become available.
156   */
157  private final int waitTimeoutMillis;
158
159  /**
160   * The size of the preparedStatement cache;
161   */
162  private int pstmtCacheSize;
163
164  private final PooledConnectionQueue queue;
165
166  private final Timer heartBeatTimer;
167
168  /**
169   * Used to find and close() leaked connections. Leaked connections are
170   * thought to be busy but have not been used for some time. Each time a
171   * connection is used it sets it's lastUsedTime.
172   */
173  private long leakTimeMinutes;
174
175  public ConnectionPool(String name, DataSourceConfig params) {
176
177    this.name = name;
178    this.notify = params.getAlert();
179    this.poolListener = params.getListener();
180
181    this.autoCommit = params.isAutoCommit();
182    this.readOnly = params.isReadOnly();
183    this.transactionIsolation = params.getIsolationLevel();
184
185    this.maxInactiveMillis = 1000 * params.getMaxInactiveTimeSecs();
186    this.maxAgeMillis = 60000 * params.getMaxAgeMinutes();
187    this.leakTimeMinutes = params.getLeakTimeMinutes();
188    this.captureStackTrace = params.isCaptureStackTrace();
189    this.maxStackTraceSize = params.getMaxStackTraceSize();
190    this.databaseDriver = params.getDriver();
191    this.databaseUrl = params.getUrl();
192    this.pstmtCacheSize = params.getPstmtCacheSize();
193
194    this.minConnections = params.getMinConnections();
195    this.maxConnections = params.getMaxConnections();
196    this.waitTimeoutMillis = params.getWaitTimeoutMillis();
197    this.heartbeatsql = params.getHeartbeatSql();
198    this.heartbeatFreqSecs = params.getHeartbeatFreqSecs();
199    this.heartbeatTimeoutSeconds = params.getHeartbeatTimeoutSeconds();
200    this.trimPoolFreqMillis = 1000 * params.getTrimPoolFreqSecs();
201
202    queue = new PooledConnectionQueue(this);
203
204    String un = params.getUsername();
205    String pw = params.getPassword();
206    if (un == null) {
207      throw new RuntimeException("DataSource user is null?");
208    }
209    if (pw == null) {
210      throw new RuntimeException("DataSource password is null?");
211    }
212    this.connectionProps = new Properties();
213    this.connectionProps.setProperty("user", un);
214    this.connectionProps.setProperty("password", pw);
215
216    Map<String, String> customProperties = params.getCustomProperties();
217    if (customProperties != null) {
218      Set<Entry<String, String>> entrySet = customProperties.entrySet();
219      for (Entry<String, String> entry : entrySet) {
220        this.connectionProps.setProperty(entry.getKey(), entry.getValue());
221      }
222    }
223
224    try {
225      initialise();
226      int freqMillis = heartbeatFreqSecs * 1000;
227      heartBeatTimer = new Timer(name+".heartBeat", true);
228      if (freqMillis > 0) {
229        heartBeatTimer.scheduleAtFixedRate(new HeartBeatRunnable(), freqMillis, freqMillis);
230      }
231    } catch (SQLException ex) {
232      throw new RuntimeException(ex);
233    }
234  }
235
236  class HeartBeatRunnable extends TimerTask {
237    @Override
238    public void run() {
239      checkDataSource();
240    }
241  }
242
243
244  @Override
245  public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
246    throw new SQLFeatureNotSupportedException("We do not support java.util.logging");
247  }
248
249  private void initialise() throws SQLException {
250
251    // Ensure database driver is loaded
252    try {
253      ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
254      if (contextLoader != null) {
255        Class.forName(databaseDriver, true, contextLoader);
256      } else {
257        Class.forName(databaseDriver, true, this.getClass().getClassLoader());
258      }
259    } catch (Throwable e) {
260      throw new IllegalStateException("Problem loading Database Driver [" + this.databaseDriver + "]: " + e.getMessage(), e);
261    }
262
263    String transIsolation = TransactionIsolation.getDescription(transactionIsolation);
264
265    //noinspection StringBufferReplaceableByString
266    StringBuilder sb = new StringBuilder(70);
267    sb.append("DataSourcePool [").append(name);
268    sb.append("] autoCommit[").append(autoCommit);
269    sb.append("] transIsolation[").append(transIsolation);
270    sb.append("] min[").append(minConnections);
271    sb.append("] max[").append(maxConnections).append("]");
272
273    logger.info(sb.toString());
274
275    queue.ensureMinimumConnections();
276  }
277
278  /**
279   * Returns false.
280   */
281  public boolean isWrapperFor(Class<?> arg0) throws SQLException {
282    return false;
283  }
284
285  /**
286   * Not Implemented.
287   */
288  public <T> T unwrap(Class<T> arg0) throws SQLException {
289    throw new SQLException("Not Implemented");
290  }
291
292  /**
293   * Return the dataSource name.
294   */
295  @Override
296  public String getName() {
297    return name;
298  }
299
300  /**
301   * Return the max size of stack traces used when trying to find connection pool leaks.
302   * <p>
303   * This is only used when {@link #isCaptureStackTrace()} is true.
304   * </p>
305   */
306  int getMaxStackTraceSize() {
307    return maxStackTraceSize;
308  }
309
310  /**
311   * Returns false when the dataSource is down.
312   */
313  public boolean isDataSourceUp() {
314    return dataSourceUp;
315  }
316
317  /**
318   * Called when the pool hits the warning level.
319   */
320  protected void notifyWarning(String msg) {
321
322    if (!inWarningMode) {
323      // send an Error to the event log...
324      inWarningMode = true;
325      logger.warn(msg);
326      if (notify != null) {
327        String subject = "DataSourcePool [" + name + "] warning";
328        notify.dataSourceWarning(subject, msg);
329      }
330    }
331  }
332
333  private void notifyDataSourceIsDown(SQLException ex) {
334
335    if (!dataSourceDownAlertSent) {
336      logger.error("FATAL: DataSourcePool [" + name + "] is down or has network error!!!", ex);
337      if (notify != null) {
338        notify.dataSourceDown(name);
339      }
340      dataSourceDownAlertSent = true;
341    }
342    if (dataSourceUp) {
343      reset();
344    }
345    dataSourceUp = false;
346  }
347
348  private void notifyDataSourceIsUp() {
349    if (dataSourceDownAlertSent) {
350      logger.error("RESOLVED FATAL: DataSourcePool [" + name + "] is back up!");
351      if (notify != null) {
352        notify.dataSourceUp(name);
353      }
354      dataSourceDownAlertSent = false;
355
356    } else if (!dataSourceUp) {
357      logger.info("DataSourcePool [" + name + "] is back up!");
358    }
359
360    if (!dataSourceUp) {
361      dataSourceUp = true;
362      reset();
363    }
364  }
365
366  /**
367   * Trim connections (in the free list) based on idle time and maximum age.
368   */
369  private void trimIdleConnections() {
370    if (System.currentTimeMillis() > (lastTrimTime + trimPoolFreqMillis)) {
371      try {
372        queue.trim(maxInactiveMillis, maxAgeMillis);
373        lastTrimTime = System.currentTimeMillis();
374      } catch (Exception e) {
375        logger.error("Error trying to trim idle connections", e);
376      }
377    }
378  }
379
380  /**
381   * Check the dataSource is up. Trim connections.
382   * <p>
383   * This is called by the HeartbeatRunnable which should be scheduled to
384   * run periodically (every heartbeatFreqSecs seconds actually).
385   * </p>
386   */
387  private void checkDataSource() {
388
389    // first trim idle connections
390    trimIdleConnections();
391
392    Connection conn = null;
393    try {
394      // Get a connection from the pool and test it
395      conn = getConnection();
396      if (testConnection(conn)) {
397        notifyDataSourceIsUp();
398
399      } else {
400        notifyDataSourceIsDown(null);
401      }
402
403    } catch (SQLException ex) {
404      notifyDataSourceIsDown(ex);
405
406    } finally {
407      try {
408        if (conn != null) {
409          conn.close();
410        }
411      } catch (SQLException ex) {
412        logger.warn("Can't close connection in checkDataSource!");
413      }
414    }
415  }
416
417  /**
418   * Create a Connection that will not be part of the connection pool.
419   * <p>
420   * <p>
421   * When this connection is closed it will not go back into the pool.
422   * </p>
423   * <p>
424   * <p>
425   * If withDefaults is true then the Connection will have the autoCommit and
426   * transaction isolation set to the defaults for the pool.
427   * </p>
428   */
429  public Connection createUnpooledConnection() throws SQLException {
430
431    try {
432      Connection conn = DriverManager.getConnection(databaseUrl, connectionProps);
433      conn.setAutoCommit(autoCommit);
434      conn.setTransactionIsolation(transactionIsolation);
435      if (readOnly) {
436        conn.setReadOnly(readOnly);
437      }
438      return conn;
439
440    } catch (SQLException ex) {
441      notifyDataSourceIsDown(null);
442      throw ex;
443    }
444  }
445
446  /**
447   * Set a new maximum size. The pool should respect this new maximum
448   * immediately and not require a restart. You may want to increase the
449   * maxConnections if the pool gets large and hits the warning level.
450   */
451  public void setMaxSize(int max) {
452    queue.setMaxSize(max);
453    this.maxConnections = max;
454  }
455
456  /**
457   * Return the max size this pool can grow to.
458   */
459  public int getMaxSize() {
460    return maxConnections;
461  }
462
463  /**
464   * Set the min size this pool should maintain.
465   */
466  public void setMinSize(int min) {
467    queue.setMinSize(min);
468    this.minConnections = min;
469  }
470
471  /**
472   * Return the min size this pool should maintain.
473   */
474  public int getMinSize() {
475    return minConnections;
476  }
477
478  /**
479   * Set a new maximum size. The pool should respect this new maximum
480   * immediately and not require a restart. You may want to increase the
481   * maxConnections if the pool gets large and hits the warning and or alert
482   * levels.
483   */
484  public void setWarningSize(int warningSize) {
485    queue.setWarningSize(warningSize);
486    this.warningSize = warningSize;
487  }
488
489  /**
490   * Return the warning size. When the pool hits this size it can send a
491   * notify message to an administrator.
492   */
493  public int getWarningSize() {
494    return warningSize;
495  }
496
497  /**
498   * Return the time in millis that threads will wait when the pool has hit
499   * the max size. These threads wait for connections to be returned by the
500   * busy connections.
501   */
502  public int getWaitTimeoutMillis() {
503    return waitTimeoutMillis;
504  }
505
506  /**
507   * Return the time after which inactive connections are trimmed.
508   */
509  public int getMaxInactiveMillis() {
510    return maxInactiveMillis;
511  }
512
513  /**
514   * Return the maximum age a connection is allowed to be before it is trimmed
515   * out of the pool. This value can be 0 which means there is no maximum age.
516   */
517  public long getMaxAgeMillis() {
518    return maxAgeMillis;
519  }
520
521  private boolean testConnection(Connection conn) throws SQLException {
522
523    if (heartbeatsql == null) {
524      return conn.isValid(heartbeatTimeoutSeconds);
525    }
526    Statement stmt = null;
527    ResultSet rset = null;
528    try {
529      // It should only error IF the DataSource is down or a network issue
530      stmt = conn.createStatement();
531      if (heartbeatTimeoutSeconds > 0) {
532        stmt.setQueryTimeout(heartbeatTimeoutSeconds);
533      }
534      rset = stmt.executeQuery(heartbeatsql);
535      conn.commit();
536
537      return true;
538
539    } finally {
540      try {
541        if (rset != null) {
542          rset.close();
543        }
544      } catch (SQLException e) {
545        logger.error(null, e);
546      }
547      try {
548        if (stmt != null) {
549          stmt.close();
550        }
551      } catch (SQLException e) {
552        logger.error(null, e);
553      }
554    }
555  }
556
557  /**
558   * Make sure the connection is still ok to use. If not then remove it from
559   * the pool.
560   */
561  boolean validateConnection(PooledConnection conn) {
562    try {
563      return testConnection(conn);
564
565    } catch (Exception e) {
566      logger.warn("heartbeatsql test failed on connection[" + conn.getName() + "]");
567      return false;
568    }
569  }
570
571  /**
572   * Called by the PooledConnection themselves, returning themselves to the
573   * pool when they have been finished with.
574   * <p>
575   * Note that connections may not be added back to the pool if returnToPool
576   * is false or if they where created before the recycleTime. In both of
577   * these cases the connection is fully closed and not pooled.
578   * </p>
579   *
580   * @param pooledConnection the returning connection
581   */
582  void returnConnection(PooledConnection pooledConnection) {
583
584    // return a normal 'good' connection
585    returnTheConnection(pooledConnection, false);
586  }
587
588  /**
589   * This is a bad connection and must be removed from the pool's busy list and fully closed.
590   */
591  void returnConnectionForceClose(PooledConnection pooledConnection) {
592
593    returnTheConnection(pooledConnection, true);
594  }
595
596  /**
597   * Return connection. If forceClose is true then this is a bad connection that
598   * must be removed and closed fully.
599   */
600  private void returnTheConnection(PooledConnection pooledConnection, boolean forceClose) {
601
602    if (poolListener != null && !forceClose) {
603      poolListener.onBeforeReturnConnection(pooledConnection);
604    }
605    queue.returnPooledConnection(pooledConnection, forceClose);
606
607    if (forceClose) {
608      // Got a bad connection so check the pool
609      checkDataSource();
610    }
611  }
612
613  /**
614   * Collect statistics of a connection that is fully closing
615   */
616  void reportClosingConnection(PooledConnection pooledConnection) {
617
618    queue.reportClosingConnection(pooledConnection);
619  }
620
621  /**
622   * Returns information describing connections that are currently being used.
623   */
624  public String getBusyConnectionInformation() {
625
626    return queue.getBusyConnectionInformation();
627  }
628
629  /**
630   * Dumps the busy connection information to the logs.
631   * <p>
632   * This includes the stackTrace elements if they are being captured. This is
633   * useful when needing to look a potential connection pool leaks.
634   * </p>
635   */
636  public void dumpBusyConnectionInformation() {
637
638    queue.dumpBusyConnectionInformation();
639  }
640
641  /**
642   * Close any busy connections that have not been used for some time.
643   * <p>
644   * These connections are considered to have leaked from the connection pool.
645   * </p>
646   * <p>
647   * Connection leaks occur when code doesn't ensure that connections are
648   * closed() after they have been finished with. There should be an
649   * appropriate try catch finally block to ensure connections are always
650   * closed and put back into the pool.
651   * </p>
652   */
653  public void closeBusyConnections(long leakTimeMinutes) {
654
655    queue.closeBusyConnections(leakTimeMinutes);
656  }
657
658  /**
659   * Grow the pool by creating a new connection. The connection can either be
660   * added to the available list, or returned.
661   * <p>
662   * This method is protected by synchronization in calling methods.
663   * </p>
664   */
665  PooledConnection createConnectionForQueue(int connId) throws SQLException {
666
667    try {
668      Connection c = createUnpooledConnection();
669
670      PooledConnection pc = new PooledConnection(this, connId, c);
671      pc.resetForUse();
672
673      if (!dataSourceUp) {
674        notifyDataSourceIsUp();
675      }
676      return pc;
677
678    } catch (SQLException ex) {
679      notifyDataSourceIsDown(ex);
680      throw ex;
681    }
682  }
683
684  /**
685   * Close all the connections in the pool.
686   * <p>
687   * <ul>
688   * <li>Checks that the database is up.
689   * <li>Resets the Alert level.
690   * <li>Closes busy connections that have not been used for some time (aka
691   * leaks).
692   * <li>This closes all the currently available connections.
693   * <li>Busy connections are closed when they are returned to the pool.
694   * </ul>
695   * </p>
696   */
697  public void reset() {
698    queue.reset(leakTimeMinutes);
699    inWarningMode = false;
700  }
701
702  /**
703   * Return a pooled connection.
704   */
705  public Connection getConnection() throws SQLException {
706    return getPooledConnection();
707  }
708
709  /**
710   * Get a connection from the pool.
711   * <p>
712   * This will grow the pool if all the current connections are busy. This
713   * will go into a wait if the pool has hit its maximum size.
714   * </p>
715   */
716  private PooledConnection getPooledConnection() throws SQLException {
717
718    PooledConnection c = queue.getPooledConnection();
719
720    if (captureStackTrace) {
721      c.setStackTrace(Thread.currentThread().getStackTrace());
722    }
723
724    if (poolListener != null) {
725      poolListener.onAfterBorrowConnection(c);
726    }
727    return c;
728  }
729
730  /**
731   * Send a message to the DataSourceAlertListener to test it. This is so that
732   * you can make sure the alerter is configured correctly etc.
733   */
734  public void testAlert() {
735
736    String subject = "Test DataSourcePool [" + name + "]";
737    String msg = "Just testing if alert message is sent successfully.";
738
739    if (notify != null) {
740      notify.dataSourceWarning(subject, msg);
741    }
742  }
743
744  /**
745   * This will close all the free connections, and then go into a wait loop,
746   * waiting for the busy connections to be freed.
747   * <p>
748   * <p>
749   * The DataSources's should be shutdown AFTER thread pools. Leaked
750   * Connections are not waited on, as that would hang the server.
751   * </p>
752   */
753  @Override
754  public void shutdown(boolean deregisterDriver) {
755    heartBeatTimer.cancel();
756    queue.shutdown();
757    if (deregisterDriver) {
758      deregisterDriver();
759    }
760  }
761
762  /**
763   * Return the default autoCommit setting Connections in this pool will use.
764   *
765   * @return true if the pool defaults autoCommit to true
766   */
767  @Override
768  public boolean isAutoCommit() {
769    return autoCommit;
770  }
771
772  /**
773   * Return the default transaction isolation level connections in this pool
774   * should have.
775   *
776   * @return the default transaction isolation level
777   */
778  int getTransactionIsolation() {
779    return transactionIsolation;
780  }
781
782  /**
783   * Return true if the connection pool is currently capturing the StackTrace
784   * when connections are 'got' from the pool.
785   * <p>
786   * This is set to true to help diagnose connection pool leaks.
787   * </p>
788   */
789  public boolean isCaptureStackTrace() {
790    return captureStackTrace;
791  }
792
793  /**
794   * Set this to true means that the StackElements are captured every time a
795   * connection is retrieved from the pool. This can be used to identify
796   * connection pool leaks.
797   */
798  public void setCaptureStackTrace(boolean captureStackTrace) {
799    this.captureStackTrace = captureStackTrace;
800  }
801
802  /**
803   * Create an un-pooled connection with the given username and password.
804   *
805   * This uses the default isolation level and autocommit mode.
806   */
807  public Connection getConnection(String username, String password) throws SQLException {
808
809    Properties props = new Properties();
810    props.putAll(connectionProps);
811    props.setProperty("user", username);
812    props.setProperty("password", password);
813    return DriverManager.getConnection(databaseUrl, props);
814  }
815
816  /**
817   * Not implemented and shouldn't be used.
818   */
819  public int getLoginTimeout() throws SQLException {
820    throw new SQLException("Method not supported");
821  }
822
823  /**
824   * Not implemented and shouldn't be used.
825   */
826  public void setLoginTimeout(int seconds) throws SQLException {
827    throw new SQLException("Method not supported");
828  }
829
830  /**
831   * Returns null.
832   */
833  public PrintWriter getLogWriter() {
834    return null;
835  }
836
837  /**
838   * Not implemented.
839   */
840  public void setLogWriter(PrintWriter writer) throws SQLException {
841    throw new SQLException("Method not supported");
842  }
843
844  /**
845   * For detecting and closing leaked connections. Connections that have been
846   * busy for more than leakTimeMinutes are considered leaks and will be
847   * closed on a reset().
848   * <p>
849   * If you want to use a connection for that longer then you should consider
850   * creating an unpooled connection or setting longRunning to true on that
851   * connection.
852   * </p>
853   */
854  public void setLeakTimeMinutes(long leakTimeMinutes) {
855    this.leakTimeMinutes = leakTimeMinutes;
856  }
857
858  /**
859   * Return the number of minutes after which a busy connection could be
860   * considered leaked from the connection pool.
861   */
862  public long getLeakTimeMinutes() {
863    return leakTimeMinutes;
864  }
865
866  /**
867   * Return the preparedStatement cache size.
868   */
869  public int getPstmtCacheSize() {
870    return pstmtCacheSize;
871  }
872
873  /**
874   * Set the preparedStatement cache size.
875   */
876  public void setPstmtCacheSize(int pstmtCacheSize) {
877    this.pstmtCacheSize = pstmtCacheSize;
878  }
879
880  /**
881   * Return the current status of the connection pool.
882   * <p>
883   * If you pass reset = true then the counters such as
884   * hitCount, waitCount and highWaterMark are reset.
885   * </p>
886   */
887  @Override
888  public PoolStatus getStatus(boolean reset) {
889    return queue.getStatus(reset);
890  }
891
892  /**
893   * Return the aggregated load statistics collected on all the connections in the pool.
894   */
895  @Override
896  public PoolStatistics getStatistics(boolean reset) {
897    return queue.getStatistics(reset);
898  }
899
900  /**
901   * Deregister the JDBC driver.
902   */
903  private void deregisterDriver() {
904    try {
905      logger.debug("Deregister the JDBC driver " + this.databaseDriver);
906      DriverManager.deregisterDriver(DriverManager.getDriver(this.databaseUrl));
907    } catch (SQLException e) {
908      logger.warn("Error trying to deregister the JDBC driver " + this.databaseDriver, e);
909    }
910  }
911
912  public static class Status implements PoolStatus {
913
914    private final int minSize;
915    private final int maxSize;
916    private final int free;
917    private final int busy;
918    private final int waiting;
919    private final int highWaterMark;
920    private final int waitCount;
921    private final int hitCount;
922
923    protected Status(int minSize, int maxSize, int free, int busy, int waiting, int highWaterMark, int waitCount, int hitCount) {
924      this.minSize = minSize;
925      this.maxSize = maxSize;
926      this.free = free;
927      this.busy = busy;
928      this.waiting = waiting;
929      this.highWaterMark = highWaterMark;
930      this.waitCount = waitCount;
931      this.hitCount = hitCount;
932    }
933
934    public String toString() {
935      return "min[" + minSize + "] max[" + maxSize + "] free[" + free + "] busy[" + busy + "] waiting[" + waiting
936          + "] highWaterMark[" + highWaterMark + "] waitCount[" + waitCount + "] hitCount[" + hitCount + "]";
937    }
938
939    /**
940     * Return the min pool size.
941     */
942    @Override
943    public int getMinSize() {
944      return minSize;
945    }
946
947    /**
948     * Return the max pool size.
949     */
950    @Override
951    public int getMaxSize() {
952      return maxSize;
953    }
954
955    /**
956     * Return the current number of free connections in the pool.
957     */
958    @Override
959    public int getFree() {
960      return free;
961    }
962
963    /**
964     * Return the current number of busy connections in the pool.
965     */
966    @Override
967    public int getBusy() {
968      return busy;
969    }
970
971    /**
972     * Return the current number of threads waiting for a connection.
973     */
974    @Override
975    public int getWaiting() {
976      return waiting;
977    }
978
979    /**
980     * Return the high water mark of busy connections.
981     */
982    @Override
983    public int getHighWaterMark() {
984      return highWaterMark;
985    }
986
987    /**
988     * Return the total number of times a thread had to wait.
989     */
990    @Override
991    public int getWaitCount() {
992      return waitCount;
993    }
994
995    /**
996     * Return the total number of times there was an attempt to get a
997     * connection.
998     * <p>
999     * If the attempt to get a connection failed with a timeout or other
1000     * exception those attempts are still included in this hit count.
1001     * </p>
1002     */
1003    @Override
1004    public int getHitCount() {
1005      return hitCount;
1006    }
1007
1008  }
1009
1010}