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}