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