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}