Package com.jolbox.bonecp

Source Code of com.jolbox.bonecp.BoneCP

/**
*  Copyright 2010 Wallace Wadge
*
*    Licensed under the Apache License, Version 2.0 (the "License");
*    you may not use this file except in compliance with the License.
*    You may obtain a copy of the License at
*
*        http://www.apache.org/licenses/LICENSE-2.0
*
*    Unless required by applicable law or agreed to in writing, software
*    distributed under the License is distributed on an "AS IS" BASIS,
*    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*    See the License for the specific language governing permissions and
*    limitations under the License.
*/

package com.jolbox.bonecp;

import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.lang.ref.Reference;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.sql.DataSource;

import jsr166y.LinkedTransferQueue;
import jsr166y.TransferQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.FinalizableReferenceQueue;
import com.jolbox.bonecp.hooks.AcquireFailConfig;



/**
* Connection pool (main class).
* @author wwadge
*
*/
public class BoneCP implements Serializable {
  /** Warning message. */
  private static final String THREAD_CLOSE_CONNECTION_WARNING = "Thread close connection monitoring has been enabled. This will negatively impact on your performance. Only enable this option for debugging purposes!";
  /** Serialization UID */
  private static final long serialVersionUID = -8386816681977604817L;
  /** Exception message. */
  private static final String ERROR_TEST_CONNECTION = "Unable to open a test connection to the given database. JDBC url = %s, username = %s. Terminating connection pool. Original Exception: %s";
  /** Exception message. */
  private static final String SHUTDOWN_LOCATION_TRACE = "Attempting to obtain a connection from a pool that has already been shutdown. \nStack trace of location where pool was shutdown follows:\n";
  /** Exception message. */
  private static final String UNCLOSED_EXCEPTION_MESSAGE = "Connection obtained from thread [%s] was never closed. \nStack trace of location where connection was obtained follows:\n";
  /** JMX constant. */
  public static final String MBEAN_CONFIG = "com.jolbox.bonecp:type=BoneCPConfig";
  /** JMX constant. */
  public static final String MBEAN_BONECP = "com.jolbox.bonecp:type=BoneCP";
  /** Constant for keep-alive test */
  private static final String[] METADATATABLE = new String[] {"TABLE"};
  /** Constant for keep-alive test */
  private static final String KEEPALIVEMETADATA = "BONECPKEEPALIVE";
  /** Create more connections when we hit x% of our possible number of connections. */
  protected final int poolAvailabilityThreshold;
  /** Number of partitions passed in constructor. **/
  private int partitionCount;
  /** Partitions handle. */
  private ConnectionPartition[] partitions;
  /** Handle to factory that creates 1 thread per partition that periodically wakes up and performs some
   * activity on the connection.
   */
  private ScheduledExecutorService keepAliveScheduler;
  /** Handle to factory that creates 1 thread per partition that periodically wakes up and performs some
   * activity on the connection.
   */
  private ScheduledExecutorService maxAliveScheduler;
  /** Executor for threads watching each partition to dynamically create new threads/kill off excess ones.
   */
  private ExecutorService connectionsScheduler;
  /** Configuration object used in constructor. */
  private BoneCPConfig config;
  /** If set to true, config has specified the use of helper threads. */
  private boolean releaseHelperThreadsConfigured;
  /** pointer to the thread containing the release helper threads. */
  private ExecutorService releaseHelper;
  /** pointer to the service containing the statement close helper threads. */
  private ExecutorService statementCloseHelperExecutor;
  /** Executor service for obtaining a connection in an asynchronous fashion. */
  private ExecutorService asyncExecutor;
  /** Logger class. */
  private static Logger logger = LoggerFactory.getLogger(BoneCP.class);
  /** JMX support. */
  private MBeanServer mbs;
  /** Prevent repeated termination of all connections when the DB goes down. */
  protected Lock terminationLock = new ReentrantLock();
  /** If set to true, create a new thread that monitors a connection and displays warnings if application failed to
   * close the connection.
   */
  protected boolean closeConnectionWatch = false;
  /** Threads monitoring for bad connection requests. */
  private ExecutorService closeConnectionExecutor;
  /** set to true if the connection pool has been flagged as shutting down. */
  protected volatile boolean poolShuttingDown;
  /** Placeholder to give more useful info in case of a double shutdown. */
  private String shutdownStackTrace;
  /** Reference of objects that are to be watched. */
  private final Map<Connection, Reference<ConnectionHandle>> finalizableRefs = new ConcurrentHashMap<Connection, Reference<ConnectionHandle>>();
  /** Watch for connections that should have been safely closed but the application forgot. */
  private FinalizableReferenceQueue finalizableRefQueue;
  /** Time to wait before timing out the connection. Default in config is Long.MAX_VALUE milliseconds. */
  private long connectionTimeoutInMs;
  /** No of ms to wait for thread.join() in connection watch thread. */
  private long closeConnectionWatchTimeoutInMs;
  /** If set to true, config has specified the use of statement release helper threads. */
  private boolean statementReleaseHelperThreadsConfigured;
  /** Scratch queue of statments awaiting to be closed. */
  private LinkedTransferQueue<StatementHandle> statementsPendingRelease;
  /** if true, we care about statistics. */
  private boolean statisticsEnabled;
  /** statistics handle. */
  private Statistics statistics = new Statistics(this);
  /** Config setting. */
  private Boolean defaultReadOnly;
  /** Config setting. */
  private String defaultCatalog;
  /** Config setting. */
  private int defaultTransactionIsolationValue;
  /** Config setting. */
  private Boolean defaultAutoCommit;
  /** Config setting. */
  @VisibleForTesting protected boolean externalAuth;
 
  /**
   * Closes off this connection pool.
   */
  public synchronized void shutdown(){

    if (!this.poolShuttingDown){
      logger.info("Shutting down connection pool...");
      this.poolShuttingDown = true;

      this.shutdownStackTrace = captureStackTrace(SHUTDOWN_LOCATION_TRACE);
      this.keepAliveScheduler.shutdownNow(); // stop threads from firing.
      this.maxAliveScheduler.shutdownNow(); // stop threads from firing.
      this.connectionsScheduler.shutdownNow(); // stop threads from firing.

      if (this.releaseHelperThreadsConfigured){
        this.releaseHelper.shutdownNow();
      }
      if (this.statementReleaseHelperThreadsConfigured){
        this.statementCloseHelperExecutor.shutdownNow();
      }
      if (this.asyncExecutor != null){
        this.asyncExecutor.shutdownNow();
      }
      if (this.closeConnectionExecutor != null){
        this.closeConnectionExecutor.shutdownNow();
      }
      terminateAllConnections();
      logger.info("Connection pool has been shutdown.");
    }
  }

  /** Just a synonym to shutdown. */
  public void close(){
    shutdown();
  }

  /** Closes off all connections in all partitions. */
  protected void terminateAllConnections(){
    this.terminationLock.lock();
    try{
      // close off all connections.
      for (int i=0; i < this.partitionCount; i++) {
        ConnectionHandle conn;
        while ((conn = this.partitions[i].getFreeConnections().poll()) != null){
          postDestroyConnection(conn);
          conn.setInReplayMode(true); // we're dead, stop attempting to replay anything
          try {
            conn.internalClose();
          } catch (SQLException e) {
            logger.error("Error in attempting to close connection", e);
          }
        }

      }
    } finally {
      this.terminationLock.unlock();
    }
  }

  /** Update counters and call hooks.
   * @param handle connection handle.
   */
  protected void postDestroyConnection(ConnectionHandle handle){
    ConnectionPartition partition = handle.getOriginatingPartition();
 
    if (this.finalizableRefQueue != null){ //safety
      this.finalizableRefs.remove(handle.getInternalConnection());
//      assert o != null : "Did not manage to remove connection from finalizable ref queue";
    }
   
    partition.updateCreatedConnections(-1);
    partition.setUnableToCreateMoreTransactions(false); // we can create new ones now, this is an optimization

   
    // "Destroying" for us means: don't put it back in the pool.
    if (handle.getConnectionHook() != null){
      handle.getConnectionHook().onDestroy(handle);
    }

  }

  /** Returns a database connection by using Driver.getConnection() or DataSource.getConnection()
   * @return Connection handle
   * @throws SQLException on error
   */
  protected Connection obtainRawInternalConnection()
  throws SQLException {
    Connection result = null;

    DataSource datasourceBean = this.config.getDatasourceBean();
    String url = this.config.getJdbcUrl();
    String username = this.config.getUsername();
    String password = this.config.getPassword();
    Properties props = this.config.getDriverProperties();

    if (this.externalAuth && props == null){
      props = new Properties();
    }
   
    if (datasourceBean != null){
      return (username == null ? datasourceBean.getConnection() : datasourceBean.getConnection(username, password));
    }

    if (props != null){
      result = DriverManager.getConnection(url, props);
    } else {
      result = DriverManager.getConnection(url, username, password);
    }

    if (this.defaultAutoCommit != null){
      result.setAutoCommit(this.defaultAutoCommit);
    }
    if (this.defaultReadOnly != null){
      result.setReadOnly(this.defaultReadOnly);
    }
    if (this.defaultCatalog != null){
      result.setCatalog(this.defaultCatalog);
    }
    if (this.defaultTransactionIsolationValue != -1){
      result.setTransactionIsolation(this.defaultTransactionIsolationValue);
    }

    return result;
  }

  /**
   * Constructor.
   * @param config Configuration for pool
   * @throws SQLException on error
   */
  public BoneCP(BoneCPConfig config) throws SQLException {
    this.config = config;
    config.sanitize();

    this.statisticsEnabled = config.isStatisticsEnabled();
    this.closeConnectionWatchTimeoutInMs = config.getCloseConnectionWatchTimeoutInMs();
    this.poolAvailabilityThreshold = config.getPoolAvailabilityThreshold();
    this.connectionTimeoutInMs = config.getConnectionTimeoutInMs();
    this.externalAuth = config.isExternalAuth();
   
    if (this.connectionTimeoutInMs == 0){
      this.connectionTimeoutInMs = Long.MAX_VALUE;
    }
    this.defaultReadOnly = config.getDefaultReadOnly();
    this.defaultCatalog = config.getDefaultCatalog();
    this.defaultTransactionIsolationValue = config.getDefaultTransactionIsolationValue();
    this.defaultAutoCommit = config.getDefaultAutoCommit();
   
    AcquireFailConfig acquireConfig = new AcquireFailConfig();
    acquireConfig.setAcquireRetryAttempts(new AtomicInteger(0));
    acquireConfig.setAcquireRetryDelayInMs(0);
    acquireConfig.setLogMessage("Failed to obtain initial connection");
   
    if (!config.isLazyInit()){
      try{
        Connection sanityConnection = obtainRawInternalConnection();
        sanityConnection.close();
      } catch (Exception e){
        if (config.getConnectionHook() != null){
          config.getConnectionHook().onAcquireFail(e, acquireConfig);
        }
// #ifdef JDK6
        throw new SQLException(String.format(ERROR_TEST_CONNECTION, config.getJdbcUrl(), config.getUsername(), PoolUtil.stringifyException(e)), e);
// #endif
/* #ifdef JDK5
        throw new SQLException(String.format(ERROR_TEST_CONNECTION, config.getJdbcUrl(), config.getUsername(), PoolUtil.stringifyException(e)));
#endif JDK5 */

      }
    }
    if (!config.isDisableConnectionTracking()){
      this.finalizableRefQueue = new FinalizableReferenceQueue();
    }
   
    this.asyncExecutor = Executors.newCachedThreadPool();
    int helperThreads = config.getReleaseHelperThreads();
    this.releaseHelperThreadsConfigured = helperThreads > 0;
   
    this.statementReleaseHelperThreadsConfigured = config.getStatementReleaseHelperThreads() > 0;
    this.config = config;
    this.partitions = new ConnectionPartition[config.getPartitionCount()];
    String suffix = "";

    if (config.getPoolName()!=null) {
      suffix="-"+config.getPoolName();
    }
   
   
     
    if (this.releaseHelperThreadsConfigured){
      this.releaseHelper = Executors.newFixedThreadPool(helperThreads*config.getPartitionCount(), new CustomThreadFactory("BoneCP-release-thread-helper-thread"+suffix, true));
    }
    this.keepAliveScheduler =  Executors.newScheduledThreadPool(config.getPartitionCount(), new CustomThreadFactory("BoneCP-keep-alive-scheduler"+suffix, true));
    this.maxAliveScheduler =  Executors.newScheduledThreadPool(config.getPartitionCount(), new CustomThreadFactory("BoneCP-max-alive-scheduler"+suffix, true));
    this.connectionsScheduler =  Executors.newFixedThreadPool(config.getPartitionCount(), new CustomThreadFactory("BoneCP-pool-watch-thread"+suffix, true));

    this.partitionCount = config.getPartitionCount();
    this.closeConnectionWatch = config.isCloseConnectionWatch();
    boolean queueLIFO = config.getServiceOrder() != null && config.getServiceOrder().equalsIgnoreCase("LIFO");
    if (this.closeConnectionWatch){
      logger.warn(THREAD_CLOSE_CONNECTION_WARNING);
      this.closeConnectionExecutor =  Executors.newCachedThreadPool(new CustomThreadFactory("BoneCP-connection-watch-thread"+suffix, true));

    }
    for (int p=0; p < config.getPartitionCount(); p++){

      ConnectionPartition connectionPartition = new ConnectionPartition(this);
      this.partitions[p]=connectionPartition;
      TransferQueue<ConnectionHandle> connectionHandles;
      if (config.getMaxConnectionsPerPartition() == config.getMinConnectionsPerPartition()){
        // if we have a pool that we don't want resized, make it even faster by ignoring
        // the size constraints.
        connectionHandles = queueLIFO ? new LIFOQueue<ConnectionHandle>() new LinkedTransferQueue<ConnectionHandle>();
      } else {
        connectionHandles = queueLIFO ? new LIFOQueue<ConnectionHandle>(this.config.getMaxConnectionsPerPartition()) : new BoundedLinkedTransferQueue<ConnectionHandle>(this.config.getMaxConnectionsPerPartition());
      }
     
      this.partitions[p].setFreeConnections(connectionHandles);

      if (!config.isLazyInit()){
        for (int i=0; i < config.getMinConnectionsPerPartition(); i++){
          final ConnectionHandle handle = new ConnectionHandle(config.getJdbcUrl(), config.getUsername(), config.getPassword(), this);
          this.partitions[p].addFreeConnection(handle);
        }

      }

     
      if (config.getIdleConnectionTestPeriodInMinutes() > 0 || config.getIdleMaxAgeInMinutes() > 0){
       
        final Runnable connectionTester = new ConnectionTesterThread(connectionPartition, this.keepAliveScheduler, this, config.getIdleMaxAge(TimeUnit.MILLISECONDS), config.getIdleConnectionTestPeriod(TimeUnit.MILLISECONDS), queueLIFO);
        long delayInMinutes = config.getIdleConnectionTestPeriodInMinutes();
        if (delayInMinutes == 0L){
          delayInMinutes = config.getIdleMaxAgeInMinutes();
        }
        if (config.getIdleMaxAgeInMinutes() != 0 && config.getIdleConnectionTestPeriodInMinutes() != 0 && config.getIdleMaxAgeInMinutes() < delayInMinutes){
          delayInMinutes = config.getIdleMaxAgeInMinutes();
        }
        this.keepAliveScheduler.schedule(connectionTester, delayInMinutes, TimeUnit.MINUTES);
      }

      if (config.getMaxConnectionAgeInSeconds() > 0){
        final Runnable connectionMaxAgeTester = new ConnectionMaxAgeThread(connectionPartition, this.maxAliveScheduler, this, config.getMaxConnectionAge(TimeUnit.MILLISECONDS), queueLIFO);
        this.maxAliveScheduler.schedule(connectionMaxAgeTester, config.getMaxConnectionAgeInSeconds(), TimeUnit.SECONDS);
      }
      // watch this partition for low no of threads
      this.connectionsScheduler.execute(new PoolWatchThread(connectionPartition, this));
    }

    initStmtReleaseHelper(suffix);

    if (!this.config.isDisableJMX()){
      initJMX();
    }
  }

  /** Starts off threads released to statement release helpers.
   * @param suffix of pool
   */
  protected void initStmtReleaseHelper(String suffix) {
    // we pick a max size of maxConnections * 3 i.e. 3 statements per connection as our limit
    // anything more than that will mean the statements will start being closed off straight away
    // without enqueing
    this.statementsPendingRelease = new BoundedLinkedTransferQueue<StatementHandle>(this.config.getMaxConnectionsPerPartition()*3);
    int statementReleaseHelperThreads = this.config.getStatementReleaseHelperThreads();

    if (statementReleaseHelperThreads > 0) {
      this.setStatementCloseHelperExecutor(Executors.newFixedThreadPool(statementReleaseHelperThreads, new CustomThreadFactory("BoneCP-statement-close-helper-thread"+suffix, true)));

      for (int i = 0; i < statementReleaseHelperThreads; i++) {
        // go through pool  rather than statementReleaseHelper directly to aid unit testing (i.e. mocking)
        getStatementCloseHelperExecutor().execute(new StatementReleaseHelperThread(this.statementsPendingRelease, this));
      }
    }
  }

  /**
   * Initialises JMX stuff. 
   */
  protected void initJMX() {
    if (this.mbs == null){ // this way makes it easier for mocking.
      this.mbs = ManagementFactory.getPlatformMBeanServer();
    }
    try {
      String suffix = "";

      if (this.config.getPoolName()!=null){
        suffix="-"+this.config.getPoolName();
      }

      ObjectName name = new ObjectName(MBEAN_BONECP +suffix);
      ObjectName configname = new ObjectName(MBEAN_CONFIG + suffix);


      if (!this.mbs.isRegistered(name)){
        this.mbs.registerMBean(this.statistics, name);
      }
      if (!this.mbs.isRegistered(configname)){
        this.mbs.registerMBean(this.config, configname);
      }
    } catch (Exception e) {
      logger.error("Unable to start JMX", e);
    }
  }



  /**
   * Returns a free connection.
   * @return Connection handle.
   * @throws SQLException
   */
  public Connection getConnection() throws SQLException {
    ConnectionHandle result;
    long statsObtainTime = 0;
   
    if (this.poolShuttingDown){
      throw new SQLException(this.shutdownStackTrace);
    }
 
    int partition = (int) (Thread.currentThread().getId() % this.partitionCount);
    ConnectionPartition connectionPartition = this.partitions[partition];

    if (this.statisticsEnabled){
      statsObtainTime = System.nanoTime();
      this.statistics.incrementConnectionsRequested();
    }
    result = connectionPartition.getFreeConnections().poll();

    if (result == null) {
      // we ran out of space on this partition, pick another free one
      for (int i=0; i < this.partitionCount; i++){
        if (i == partition) {
          continue; // we already determined it's not here
        }
        result = this.partitions[i].getFreeConnections().poll(); // try our luck with this partition
        connectionPartition = this.partitions[i];
        if (result != null) {
          break// we found a connection
        }
      }
    }

    if (!connectionPartition.isUnableToCreateMoreTransactions()){ // unless we can't create any more connections...  
      maybeSignalForMoreConnections(connectionPartition)// see if we need to create more
    }

    // we still didn't find an empty one, wait forever (or as per config) until our partition is free
    if (result == null) {
      try {
        result = connectionPartition.getFreeConnections().poll(this.connectionTimeoutInMs, TimeUnit.MILLISECONDS);
        if (result == null){
          // 08001 = The application requester is unable to establish the connection.
          throw new SQLException("Timed out waiting for a free available connection.", "08001");
        }
      }
      catch (InterruptedException e) {
        throw new SQLException(e.getMessage());
      }
    }
    result.renewConnection(); // mark it as being logically "open"

    // Give an application a chance to do something with it.
    if (result.getConnectionHook() != null){
      result.getConnectionHook().onCheckOut(result);
    }

    if (this.closeConnectionWatch){ // a debugging tool
      watchConnection(result);
    }

    if (this.statisticsEnabled){
      this.statistics.addCumulativeConnectionWaitTime(System.nanoTime()-statsObtainTime);
    }
    return result;
  }


  /** Starts off a new thread to monitor this connection attempt.
   * @param connectionHandle to monitor
   */
  private void watchConnection(ConnectionHandle connectionHandle) {
    String message = captureStackTrace(UNCLOSED_EXCEPTION_MESSAGE);
    this.closeConnectionExecutor.submit(new CloseThreadMonitor(Thread.currentThread(), connectionHandle, message, this.closeConnectionWatchTimeoutInMs));
  }

  /** Throw an exception to capture it so as to be able to print it out later on
   * @param message message to display
   * @return Stack trace message
   *
   */
  protected String captureStackTrace(String message) {
    StringBuilder stringBuilder = new StringBuilder(String.format(message, Thread.currentThread().getName()));   
    StackTraceElement[] trace = Thread.currentThread().getStackTrace();
    for(int i = 0; i < trace.length; i++){
      stringBuilder.append(" "+trace[i]+"\r\n");
    }

    stringBuilder.append("");

    return stringBuilder.toString();
  }

  /** Obtain a connection asynchronously by queueing a request to obtain a connection in a separate thread.
   *
   *  Use as follows:<p>
   *      Future&lt;Connection&gt; result = pool.getAsyncConnection();<p>
   *       ... do something else in your application here ...<p>
   *      Connection connection = result.get(); // get the connection<p>
   *     
   * @return A Future task returning a connection.
   */
  public Future<Connection> getAsyncConnection(){

    return this.asyncExecutor.submit(new Callable<Connection>() {

      public Connection call() throws Exception {
        return getConnection();
      }});
  }

  /**
   * Tests if this partition has hit a threshold and signal to the pool watch thread to create new connections
   * @param connectionPartition to test for.
   */
  private void maybeSignalForMoreConnections(ConnectionPartition connectionPartition) {

    if (!connectionPartition.isUnableToCreateMoreTransactions() && !this.poolShuttingDown && 
        connectionPartition.getAvailableConnections()*100/connectionPartition.getMaxConnections() <= this.poolAvailabilityThreshold){
      connectionPartition.getPoolWatchThreadSignalQueue().offer(new Object()); // item being pushed is not important.
    }
  }

  /**
   * Releases the given connection back to the pool.
   *
   * @param connection to release
   * @throws SQLException
   */
  protected void releaseConnection(Connection connection) throws SQLException {
    ConnectionHandle handle = (ConnectionHandle)connection;

    // hook calls
    if (handle.getConnectionHook() != null){
      handle.getConnectionHook().onCheckIn(handle);
    }

    // release immediately or place it in a queue so that another thread will eventually close it. If we're shutting down,
    // close off the connection right away because the helper threads have gone away.
    if (!this.poolShuttingDown && this.releaseHelperThreadsConfigured){
      if (!handle.getOriginatingPartition().getConnectionsPendingRelease().tryTransfer(handle)){
        handle.getOriginatingPartition().getConnectionsPendingRelease().put(handle);
      }
    } else {
      internalReleaseConnection(handle);
    }
  }

  /** Release a connection by placing the connection back in the pool.
   * @param connectionHandle Connection being released.
   * @throws SQLException
   **/
  protected void internalReleaseConnection(ConnectionHandle connectionHandle) throws SQLException {
    connectionHandle.clearStatementCaches(false);

    if (connectionHandle.getReplayLog() != null){
      connectionHandle.getReplayLog().clear();
      connectionHandle.recoveryResult.getReplaceTarget().clear();
    }

    if (connectionHandle.isExpired() || (!this.poolShuttingDown && connectionHandle.isPossiblyBroken()
        && !isConnectionHandleAlive(connectionHandle))){

      ConnectionPartition connectionPartition = connectionHandle.getOriginatingPartition();
      maybeSignalForMoreConnections(connectionPartition);
     
      postDestroyConnection(connectionHandle);
      connectionHandle.clearStatementCaches(true);
      return; // don't place back in queue - connection is broken or expired.
    }


    connectionHandle.setConnectionLastUsedInMs(System.currentTimeMillis());
    if (!this.poolShuttingDown){

      putConnectionBackInPartition(connectionHandle);
    } else {
      connectionHandle.internalClose();
    }
  }

 

  /** Places a connection back in the originating partition.
   * @param connectionHandle to place back
   * @throws SQLException on error
   */
  protected void putConnectionBackInPartition(ConnectionHandle connectionHandle) throws SQLException {
    TransferQueue<ConnectionHandle> queue = connectionHandle.getOriginatingPartition().getFreeConnections();

    if (!queue.tryTransfer(connectionHandle)){
      if (!queue.offer(connectionHandle)){
        connectionHandle.internalClose();
      }
    }


  }


  /** Sends a dummy statement to the server to keep the connection alive
   * @param connection Connection handle to perform activity on
   * @return true if test query worked, false otherwise
   */
  public boolean isConnectionHandleAlive(ConnectionHandle connection) {
    Statement stmt = null;
    boolean result = false;
    boolean logicallyClosed = connection.logicallyClosed;
    try {
      if (logicallyClosed){
        connection.logicallyClosed = false; // avoid checks later on if it's marked as closed.
      }
      String testStatement = this.config.getConnectionTestStatement();
      ResultSet rs = null;

      if (testStatement == null) {
        // Make a call to fetch the metadata instead of a dummy query.
        rs = connection.getMetaData().getTables( null, null, KEEPALIVEMETADATA, METADATATABLE );
      } else {
        stmt = connection.createStatement();
        stmt.execute(testStatement);
      }


      if (rs != null) {
        rs.close();
      }

      result = true;
    } catch (SQLException e) {
      // connection must be broken!
      result = false;
    } finally {
      connection.logicallyClosed = logicallyClosed;
      connection.setConnectionLastResetInMs(System.currentTimeMillis());
      result = closeStatement(stmt, result);
    }
    return result;
  }

  /**
   * @param stmt
   * @param result
   * @return false on failure.
   */
  private boolean closeStatement(Statement stmt, boolean result) {
    if (stmt != null) {
      try {
        stmt.close();
      } catch (SQLException e) {
        return false;
      }
    }
    return result;
  }

  /** Return total number of connections currently in use by an application
   * @return no of leased connections
   */
  public int getTotalLeased(){
    int total=0;
    for (int i=0; i < this.partitionCount; i++){
      total+=this.partitions[i].getCreatedConnections()-this.partitions[i].getAvailableConnections();
    }
    return total;
  }

  /** Return the number of free connections available to an application right away (excluding connections that can be
   * created dynamically)
   * @return number of free connections
   */
  public int getTotalFree(){
    int total=0;
    for (int i=0; i < this.partitionCount; i++){
      total+=this.partitions[i].getAvailableConnections();
    }
    return total;
  }

  /**
   * Return total number of connections created in all partitions.
   *
   * @return number of created connections
   */
  public int getTotalCreatedConnections(){
    int total=0;
    for (int i=0; i < this.partitionCount; i++){
      total+=this.partitions[i].getCreatedConnections();
    }
    return total;
  }


  /**
   * Gets config object.
   *
   * @return config object
   */
  public BoneCPConfig getConfig() {
    return this.config;
  }

  /**
   * @return the releaseHelper
   */
  protected ExecutorService getReleaseHelper() {
    return this.releaseHelper;
  }

  /**
   * @param releaseHelper the releaseHelper to set
   */
  protected void setReleaseHelper(ExecutorService releaseHelper) {
    this.releaseHelper = releaseHelper;
  }

  /** Return the finalizable refs handle.
   * @return the finalizableRefs value.
   */
  protected Map<Connection, Reference<ConnectionHandle>> getFinalizableRefs() {
    return this.finalizableRefs;
  }

  /** Watch for connections that should have been safely closed but the application forgot.
   * @return the finalizableRefQueue
   */
  protected FinalizableReferenceQueue getFinalizableRefQueue() {
    return this.finalizableRefQueue;
  }

  /**
   * Returns the statementCloseHelper field.
   * @return statementCloseHelper
   */
  protected ExecutorService getStatementCloseHelperExecutor() {
    return this.statementCloseHelperExecutor;
  }


  /**
   * Sets the statementCloseHelper field.
   * @param statementCloseHelper the statementCloseHelper to set
   */
  protected void setStatementCloseHelperExecutor(ExecutorService statementCloseHelper) {
    this.statementCloseHelperExecutor = statementCloseHelper;
  }

  /**
   * Returns the releaseHelperThreadsConfigured field.
   * @return releaseHelperThreadsConfigured
   */
  protected boolean isReleaseHelperThreadsConfigured() {
    return this.releaseHelperThreadsConfigured;
  }

  /**
   * Returns the statementReleaseHelperThreadsConfigured field.
   * @return statementReleaseHelperThreadsConfigured
   */
  protected boolean isStatementReleaseHelperThreadsConfigured() {
    return this.statementReleaseHelperThreadsConfigured;
  }

  /**
   * Returns the statementsPendingRelease field.
   * @return statementsPendingRelease
   */
  protected LinkedTransferQueue<StatementHandle> getStatementsPendingRelease() {
    return this.statementsPendingRelease;
  }

  /**
   * Returns a reference to the statistics class.
   * @return statistics
   */
  public Statistics getStatistics() {
    return this.statistics;
  }

}
TOP

Related Classes of com.jolbox.bonecp.BoneCP

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.