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