/** * 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.Closeable; 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.AbstractMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.FinalizableReferenceQueue; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.jolbox.bonecp.hooks.AcquireFailConfig; import com.jolbox.bonecp.hooks.ConnectionHook; /** * Connection pool (main class). * @author wwadge * */ public class BoneCP implements Serializable, Closeable { /** 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 (set lazyInit to true if you expect to start your database after your app). 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. **/ protected int partitionCount; /** Partitions handle. */ protected ConnectionPartition[] partitions; /** Handle to factory that creates 1 thread per partition that periodically wakes up and performs some * activity on the connection. */ @VisibleForTesting protected 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. */ @VisibleForTesting protected BoneCPConfig config; /** Executor service for obtaining a connection in an asynchronous fashion. */ private ListeningExecutorService asyncExecutor; /** Logger class. */ private static final Logger logger = LoggerFactory.getLogger(BoneCP.class); /** JMX support. */ private MBeanServer mbs; /** 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. */ protected 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 transient FinalizableReferenceQueue finalizableRefQueue; /** Time to wait before timing out the connection. Default in config is Long.MAX_VALUE milliseconds. */ protected long connectionTimeoutInMs; /** No of ms to wait for thread.join() in connection watch thread. */ private long closeConnectionWatchTimeoutInMs; /** if true, we care about statistics. */ protected boolean statisticsEnabled; /** statistics handle. */ protected Statistics statistics = new Statistics(this); /** Config setting. */ @VisibleForTesting protected boolean nullOnConnectionTimeout; /** Config setting. */ @VisibleForTesting protected boolean resetConnectionOnClose; /** Config setting. */ protected boolean cachedPoolStrategy; /** Currently active get connection strategy class to use. */ protected ConnectionStrategy connectionStrategy; /** If true, there are no connections to be taken. */ private AtomicBoolean dbIsDown = new AtomicBoolean(); /** Config setting. */ @VisibleForTesting protected Properties clientInfo; /** If false, we haven't made a dummy driver call first. */ @VisibleForTesting protected volatile boolean driverInitialized = false; /** Keep track of our jvm version. */ protected int jvmMajorVersion; /** This is moved here to aid testing. */ protected static String connectionClass = "java.sql.Connection"; /** * 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. this.asyncExecutor.shutdownNow(); try { this.connectionsScheduler.awaitTermination(5, TimeUnit.SECONDS); this.maxAliveScheduler.awaitTermination(5, TimeUnit.SECONDS); this.keepAliveScheduler.awaitTermination(5, TimeUnit.SECONDS); this.asyncExecutor.awaitTermination(5, TimeUnit.SECONDS); if (this.closeConnectionExecutor != null){ this.closeConnectionExecutor.shutdownNow(); this.closeConnectionExecutor.awaitTermination(5, TimeUnit.SECONDS); } } catch (InterruptedException e) { // do nothing } this.connectionStrategy.terminateAllConnections(); unregisterDriver(); registerUnregisterJMX(false); if (finalizableRefQueue != null) { finalizableRefQueue.close(); } logger.info("Connection pool has been shutdown."); } } /** Drops a driver from the DriverManager's list. */ protected void unregisterDriver(){ String jdbcURL = this.config.getJdbcUrl(); if ((jdbcURL != null) && this.config.isDeregisterDriverOnClose()){ logger.info("Unregistering JDBC driver for : "+jdbcURL); try { DriverManager.deregisterDriver(DriverManager.getDriver(jdbcURL)); } catch (SQLException e) { logger.info("Unregistering driver failed.", e); } } } /** Just a synonym to shutdown. */ public void close(){ shutdown(); } /** * Physically close off the internal connection. * @param conn */ protected void destroyConnection(ConnectionHandle conn) { 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); } } /** Update counters and call hooks. * @param handle connection handle. */ protected void postDestroyConnection(ConnectionHandle handle){ ConnectionPartition partition = handle.getOriginatingPartition(); if (this.finalizableRefQueue != null && handle.getInternalConnection() != 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); } } /** Obtains a database connection, retrying if necessary. * @param connectionHandle * @return A DB connection. * @throws SQLException */ protected Connection obtainInternalConnection(ConnectionHandle connectionHandle) throws SQLException { boolean tryAgain = false; Connection result = null; Connection oldRawConnection = connectionHandle.getInternalConnection(); String url = this.getConfig().getJdbcUrl(); int acquireRetryAttempts = this.getConfig().getAcquireRetryAttempts(); long acquireRetryDelayInMs = this.getConfig().getAcquireRetryDelayInMs(); AcquireFailConfig acquireConfig = new AcquireFailConfig(); acquireConfig.setAcquireRetryAttempts(new AtomicInteger(acquireRetryAttempts)); acquireConfig.setAcquireRetryDelayInMs(acquireRetryDelayInMs); acquireConfig.setLogMessage("Failed to acquire connection to "+url); ConnectionHook connectionHook = this.getConfig().getConnectionHook(); do{ result = null; try { // keep track of this hook. result = this.obtainRawInternalConnection(); tryAgain = false; if (acquireRetryAttempts != this.getConfig().getAcquireRetryAttempts()){ logger.info("Successfully re-established connection to "+url); } this.getDbIsDown().set(false); connectionHandle.setInternalConnection(result); // call the hook, if available. if (connectionHook != null){ connectionHook.onAcquire(connectionHandle); } ConnectionHandle.sendInitSQL(result, this.getConfig().getInitSQL()); } catch (SQLException e) { // call the hook, if available. if (connectionHook != null){ tryAgain = connectionHook.onAcquireFail(e, acquireConfig); } else { logger.error(String.format("Failed to acquire connection to %s. Sleeping for %d ms. Attempts left: %d", url, acquireRetryDelayInMs, acquireRetryAttempts), e); try { if (acquireRetryAttempts > 0){ Thread.sleep(acquireRetryDelayInMs); } tryAgain = (acquireRetryAttempts--) > 0; } catch (InterruptedException e1) { tryAgain=false; } } if (!tryAgain){ if (oldRawConnection != null) { oldRawConnection.close(); } if (result != null) { result.close(); } connectionHandle.setInternalConnection(oldRawConnection); throw e; } } } while (tryAgain); return result; } /** 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(); boolean externalAuth = this.config.isExternalAuth(); if (externalAuth && props == null){ props = new Properties(); } if (datasourceBean != null){ return (username == null ? datasourceBean.getConnection() : datasourceBean.getConnection(username, password)); } // just force the driver to init first if (!this.driverInitialized ){ try{ this.driverInitialized = true; if (props != null){ result = DriverManager.getConnection(url, props); } else { result = DriverManager.getConnection(url, username, password); } result.close(); }catch (SQLException t){ // just force the driver to init first // See https://bugs.launchpad.net/bonecp/+bug/876476 } } if (props != null){ result = DriverManager.getConnection(url, props); } else { result = DriverManager.getConnection(url, username, password); } // #ifdef JDK>6 if (this.clientInfo != null){ // we take care of null'ing this in the constructor if jdk < 6 result.setClientInfo(this.clientInfo); } // #endif JDK>6 return result; } /** * Constructor. * @param config Configuration for pool * @throws SQLException on error */ public BoneCP(BoneCPConfig config) throws SQLException { Class<?> clazz; try { jvmMajorVersion = 5; clazz = Class.forName(connectionClass , true, config.getClassLoader()); clazz.getMethod("createClob"); // since 1.6 jvmMajorVersion = 6; clazz.getMethod("getNetworkTimeout"); // since 1.7 jvmMajorVersion = 7; } catch (Exception e) { // do nothing } try { this.config = Preconditions.checkNotNull(config).clone(); // immutable } catch (CloneNotSupportedException e1) { throw new SQLException("Cloning of the config failed"); } this.config.sanitize(); this.statisticsEnabled = this.config.isStatisticsEnabled(); this.closeConnectionWatchTimeoutInMs = this.config.getCloseConnectionWatchTimeoutInMs(); this.poolAvailabilityThreshold = this.config.getPoolAvailabilityThreshold(); this.connectionTimeoutInMs = this.config.getConnectionTimeoutInMs(); if (this.connectionTimeoutInMs == 0){ this.connectionTimeoutInMs = Long.MAX_VALUE; } this.nullOnConnectionTimeout = this.config.isNullOnConnectionTimeout(); this.resetConnectionOnClose = this.config.isResetConnectionOnClose(); this.clientInfo = jvmMajorVersion > 5 ? this.config.getClientInfo() : null; AcquireFailConfig acquireConfig = new AcquireFailConfig(); acquireConfig.setAcquireRetryAttempts(new AtomicInteger(0)); acquireConfig.setAcquireRetryDelayInMs(0); acquireConfig.setLogMessage("Failed to obtain initial connection"); if (!this.config.isLazyInit()){ try{ Connection sanityConnection = obtainRawInternalConnection(); sanityConnection.close(); } catch (Exception e){ if (this.config.getConnectionHook() != null){ this.config.getConnectionHook().onAcquireFail(e, acquireConfig); } throw PoolUtil.generateSQLException(String.format(ERROR_TEST_CONNECTION, this.config.getJdbcUrl(), this.config.getUsername(), PoolUtil.stringifyException(e)), e); } } if (!this.config.isDisableConnectionTracking()){ this.finalizableRefQueue = new FinalizableReferenceQueue(); } this.asyncExecutor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); this.partitions = new ConnectionPartition[this.config.getPartitionCount()]; String suffix = ""; if (this.config.getPoolName()!=null) { suffix="-"+this.config.getPoolName(); } this.keepAliveScheduler = Executors.newScheduledThreadPool(this.config.getPartitionCount(), new CustomThreadFactory("BoneCP-keep-alive-scheduler"+suffix, true)); this.maxAliveScheduler = Executors.newScheduledThreadPool(this.config.getPartitionCount(), new CustomThreadFactory("BoneCP-max-alive-scheduler"+suffix, true)); this.connectionsScheduler = Executors.newFixedThreadPool(this.config.getPartitionCount(), new CustomThreadFactory("BoneCP-pool-watch-thread"+suffix, true)); this.partitionCount = this.config.getPartitionCount(); this.closeConnectionWatch = this.config.isCloseConnectionWatch(); this.cachedPoolStrategy = this.config.getPoolStrategy() != null && this.config.getPoolStrategy().equalsIgnoreCase("CACHED"); if (this.cachedPoolStrategy){ this.connectionStrategy = new CachedConnectionStrategy(this, new DefaultConnectionStrategy(this)); } else { this.connectionStrategy = new DefaultConnectionStrategy(this); } boolean queueLIFO = this.config.getServiceOrder() != null && this.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 < this.config.getPartitionCount(); p++){ ConnectionPartition connectionPartition = new ConnectionPartition(this); this.partitions[p]=connectionPartition; BlockingQueue<ConnectionHandle> connectionHandles = new LinkedBlockingQueue<ConnectionHandle>(this.config.getMaxConnectionsPerPartition()); this.partitions[p].setFreeConnections(connectionHandles); if (!this.config.isLazyInit()){ for (int i=0; i < this.config.getMinConnectionsPerPartition(); i++){ this.partitions[p].addFreeConnection(new ConnectionHandle(null, this.partitions[p], this, false)); } } if (this.config.getIdleConnectionTestPeriod(TimeUnit.SECONDS) > 0 || this.config.getIdleMaxAge(TimeUnit.SECONDS) > 0){ final Runnable connectionTester = new ConnectionTesterThread(connectionPartition, this, this.config.getIdleMaxAge(TimeUnit.MILLISECONDS), this.config.getIdleConnectionTestPeriod(TimeUnit.MILLISECONDS), queueLIFO); long delayInSeconds = this.config.getIdleConnectionTestPeriod(TimeUnit.SECONDS); if (delayInSeconds == 0L){ delayInSeconds = this.config.getIdleMaxAge(TimeUnit.SECONDS); } if (this.config.getIdleMaxAge(TimeUnit.SECONDS) < delayInSeconds && this.config.getIdleConnectionTestPeriod(TimeUnit.SECONDS) != 0 && this.config.getIdleMaxAge(TimeUnit.SECONDS) != 0){ delayInSeconds = this.config.getIdleMaxAge(TimeUnit.SECONDS); } this.keepAliveScheduler.scheduleAtFixedRate(connectionTester,delayInSeconds, delayInSeconds, TimeUnit.SECONDS); } if (this.config.getMaxConnectionAgeInSeconds() > 0){ final Runnable connectionMaxAgeTester = new ConnectionMaxAgeThread(connectionPartition, this, this.config.getMaxConnectionAge(TimeUnit.MILLISECONDS), queueLIFO); this.maxAliveScheduler.scheduleAtFixedRate(connectionMaxAgeTester, this.config.getMaxConnectionAgeInSeconds(), this.config.getMaxConnectionAgeInSeconds(), TimeUnit.SECONDS); } // watch this partition for low no of threads this.connectionsScheduler.execute(new PoolWatchThread(connectionPartition, this)); } if (!this.config.isDisableJMX()){ registerUnregisterJMX(true); } } /** * Initialises JMX stuff. * @param doRegister if true, perform registration, if false unregister */ protected void registerUnregisterJMX(boolean doRegister) { 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 (doRegister){ if (!this.mbs.isRegistered(name)){ this.mbs.registerMBean(this.statistics, name); } if (!this.mbs.isRegistered(configname)){ this.mbs.registerMBean(this.config, configname); } } else { if (this.mbs.isRegistered(name)){ this.mbs.unregisterMBean(name); } if (this.mbs.isRegistered(configname)){ this.mbs.unregisterMBean(configname); } } } catch (Exception e) { logger.error("Unable to start/stop JMX", e); } } /** * Returns a free connection. * @return Connection handle. * @throws SQLException */ public Connection getConnection() throws SQLException { return this.connectionStrategy.getConnection(); } /** Starts off a new thread to monitor this connection attempt. * @param connectionHandle to monitor */ protected 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<Connection> 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 ListenableFuture<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. */ protected 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. This method is not intended to be called by * applications (hence set to protected). Call connection.close() instead which will return * the 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){ 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 { if (!this.cachedPoolStrategy){ connectionHandle.clearStatementCaches(false); } if (connectionHandle.getReplayLog() != null){ connectionHandle.getReplayLog().clear(); connectionHandle.recoveryResult.getReplaceTarget().clear(); } if (connectionHandle.isExpired() || (!this.poolShuttingDown && connectionHandle.isPossiblyBroken() && !isConnectionHandleAlive(connectionHandle))){ if (connectionHandle.isExpired()) { connectionHandle.internalClose(); } ConnectionPartition connectionPartition = connectionHandle.getOriginatingPartition(); postDestroyConnection(connectionHandle); maybeSignalForMoreConnections(connectionPartition); 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 { if (this.cachedPoolStrategy && ((CachedConnectionStrategy)this.connectionStrategy).tlConnections.dumbGet().getValue()){ connectionHandle.logicallyClosed.set(true); ((CachedConnectionStrategy)this.connectionStrategy).tlConnections.set(new AbstractMap.SimpleEntry<ConnectionHandle, Boolean>(connectionHandle, false)); } else { BlockingQueue<ConnectionHandle> queue = connectionHandle.getOriginatingPartition().getFreeConnections(); if (!queue.offer(connectionHandle)){ // this shouldn't fail 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.get(); try { connection.logicallyClosed.compareAndSet(true, 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.set(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 && this.partitions[i] != null; 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 && this.partitions[i] != null ; 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 && this.partitions[i] != null; i++){ total+=this.partitions[i].getCreatedConnections(); } return total; } /** * Gets config object. * * @return config object */ public BoneCPConfig getConfig() { return this.config; } /** 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 a reference to the statistics class. * @return statistics */ public Statistics getStatistics() { return this.statistics; } /** * Returns the dbIsDown field. * @return dbIsDown */ public AtomicBoolean getDbIsDown() { return this.dbIsDown; } }