Projects >> bonecp >>7511106a89c61c9881fed65b43ec13735bda87fb

Chunk
Conflicting content
<<<<<<< HEAD
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;

/**
 * Connection pool.
 * @author wwadge
 *
 */
public class 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 threads when we hit x% of our possible number of connections. */
	public static final int HIT_THRESHOLD = 20;
	/** 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;
	/** 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;
	/** almost full locking. **/
	final Lock connectionsObtainedLock = new ReentrantLock();
	/** If set to true, we have run out of connections at some point. */
	private volatile boolean connectionStarvationTriggered = false;
	/** 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;
	/** Logger class. */
	private static Logger logger = Logger.getLogger(BoneCP.class);


	/**
	 * Closes off this connection pool.
	 */
	public void shutdown(){
		this.keepAliveScheduler.shutdownNow(); // stop threads from firing.
		this.connectionsScheduler.shutdownNow(); // stop threads from firing.

		terminateAllConnections();
	}

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

	/** Closes off all connections in all partitions. */
	protected void terminateAllConnections(){
		// close off all connections.
		for (int i=0; i < this.partitionCount; i++) {
			ConnectionHandle conn;
			while ((conn = this.partitions[i].getFreeConnections().poll()) != null){
				this.partitions[i].updateCreatedConnections(-1);
				try {
					conn.internalClose();
				} catch (SQLException e) {
					logger.error(e);
				}
			}
			this.partitions[i].setUnableToCreateMoreTransactions(false); // we can create new ones now
		}
	}

	/**
	 * Constructor.
	 * @param config Configuration for pool
	 * @throws SQLException on error
	 */
	public BoneCP(BoneCPConfig config) throws SQLException {
		
		config.sanitize();
		this.releaseHelperThreadsConfigured = config.getReleaseHelperThreads() > 0;
		this.config = config;
		this.partitions = new ConnectionPartition[config.getPartitionCount()];
			}
		this.keepAliveScheduler =  Executors.newScheduledThreadPool(config.getPartitionCount(), new CustomThreadFactory("TinyCP-keep-alive-scheduler", true));
		this.connectionsScheduler =  Executors.newFixedThreadPool(config.getPartitionCount(), new CustomThreadFactory("TinyCP-pool-watch-thread", true));
		this.partitionCount = config.getPartitionCount();

		for (int p=0; p < config.getPartitionCount(); p++){
			ConnectionPartition connectionPartition = new ConnectionPartition(this);
			final Runnable connectionTester = new ConnectionTesterThread(connectionPartition, this.keepAliveScheduler, this);
			this.partitions[p]=connectionPartition;
			this.partitions[p].setFreeConnections(new ArrayBlockingQueue(config.getMaxConnectionsPerPartition()));

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

			if (config.getIdleConnectionTestPeriod() > 0){
				this.keepAliveScheduler.scheduleAtFixedRate(connectionTester, config.getIdleConnectionTestPeriod(), config.getIdleConnectionTestPeriod(), TimeUnit.MILLISECONDS);

			// watch this partition for low no of threads
			this.connectionsScheduler.execute(new PoolWatchThread(connectionPartition, this));
		}
	}



	/**
	 * Returns a free connection.
	 * @return Connection handle.
	 * @throws SQLException 
	 */
	public Connection getConnection() throws SQLException {
		int partition = (int) (Thread.currentThread().getId() % this.partitionCount);

		ConnectionPartition connectionPartition = this.partitions[partition];
		if (!connectionPartition.isUnableToCreateMoreTransactions() ){
			try{
				this.connectionsObtainedLock.lock();
	 				maybeSignalForMoreConnections(connectionPartition);
			} finally {
				this.connectionsObtainedLock.unlock(); 
			}
		}

		ConnectionHandle result;
		if (this.connectionStarvationTriggered) {
			try{
				result = connectionPartition.getFreeConnections().take();
			}
			catch (InterruptedException e) {
				throw new SQLException(e);
			}
		} else { 
			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();
				connectionPartition = this.partitions[i];
				if (result != null) {
					break;
				}
			}
		}

		// we still didn't find an empty one, wait forever until our partition is free
		if (result == null) {
			try {
				this.connectionStarvationTriggered   = true; 
				result = connectionPartition.getFreeConnections().take();
			}
			catch (InterruptedException e) {
				throw new SQLException(e);
			}
		}
		result.setOriginatingPartition(connectionPartition);
		result.renewConnection();
		return result;
	}

	/**
	 * 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() && connectionPartition.getFreeConnections().size()*100/connectionPartition.getMaxConnections() < HIT_THRESHOLD){
			try{
				connectionPartition.lockAlmostFullLock();
				connectionPartition.almostFullSignal();
			} finally {
				connectionPartition.unlockAlmostFullLock(); 
			}
		}


	}

	/**
	 * Move the incoming connection unto a different queue pending release.
	 *
	 * @param conn to release
	 * @throws SQLException
	 */
	public void releaseConnection(Connection conn) throws SQLException {
		try {
			if (this.releaseHelperThreadsConfigured){
				((ConnectionHandle)conn).getOriginatingPartition().getConnectionsPendingRelease().put((ConnectionHandle) conn);
			} else {
				internalReleaseConnection(conn);
			}
		}
		catch (InterruptedException e) {
			throw new SQLException(e);
		}
	}

	/** Release a connection by placing the connection back in the pool.
	 * @param conn Connection being released.
	 * @throws InterruptedException 
	 * @throws SQLException 
	 **/
	protected void internalReleaseConnection(Connection conn) throws InterruptedException, SQLException {

		ConnectionHandle connectionHandle = (ConnectionHandle)conn;
		connectionHandle.clearStatementHandles(false);
		if (connectionHandle.isPossiblyBroken() && !isConnectionHandleAlive(connectionHandle)){

			ConnectionPartition connectionPartition = connectionHandle.getOriginatingPartition();
			connectionPartition.setUnableToCreateMoreTransactions(false);
			maybeSignalForMoreConnections(connectionPartition);
			connectionPartition.updateCreatedConnections(-1);
			return; // don't place back in queue - connection is broken!
		}

		connectionHandle.setConnectionLastUsed(System.currentTimeMillis());
		releaseInAnyFreePartition(connectionHandle, connectionHandle.getOriginatingPartition());
	}

	/**
	 * Releases the given connection in any available partition, starting off
	 * with the active one.
	 *
	 * @param connectionHandle to release
	 * @param activePartition Preferred partition to release this connection to
	 * @throws InterruptedException on break
	 */
	protected void releaseInAnyFreePartition(ConnectionHandle connectionHandle, ConnectionPartition activePartition) throws InterruptedException  {

		ConnectionPartition workingPartition = activePartition;
		if (!workingPartition.getFreeConnections().offer(connectionHandle)){
			// we ran out of space on this partition, pick another free one
			boolean released = false;
			for (int i=0; i < this.partitionCount; i++){
				if (this.partitions[i].getFreeConnections().offer(connectionHandle)){
					released=true;
					break;
				}
			}

			if (!released)	{
				// we still didn't find an empty one, wait forever until our partition is free
				connectionHandle.getOriginatingPartition().getFreeConnections().put(connectionHandle);
			}
		}

	}

	/** 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; 
		try {
			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();
				rs = stmt.executeQuery(testStatement);
			}
              
 
			if (rs != null) { 
				rs.close();
			}

			result = true;
		} catch (SQLException e) {
			// connection must be broken!
			result = false;
		} finally {
			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].getFreeConnections().size();
		}
		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].getFreeConnections().size();
		}
		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
	 */
	public ExecutorService getReleaseHelper() {
		return this.releaseHelper;
	}

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

}
=======
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;

/**
 * Connection pool.
 * @author wwadge
 *
 */
public class 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 threads when we hit x% of our possible number of connections. */
	public static final int HIT_THRESHOLD = 20;
	/** 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;
	/** 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;
	/** almost full locking. **/
	final Lock connectionsObtainedLock = new ReentrantLock();
	/** If set to true, we have run out of connections at some point. */
	private volatile boolean connectionStarvationTriggered = false;
	/** 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;
	/** Logger class. */
	private static Logger logger = Logger.getLogger(BoneCP.class);


	/**
	 * Closes off this connection pool.
	 */
	public void shutdown(){
		this.keepAliveScheduler.shutdownNow(); // stop threads from firing.
		this.connectionsScheduler.shutdownNow(); // stop threads from firing.

		terminateAllConnections();
	}

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

	/** Closes off all connections in all partitions. */
	protected void terminateAllConnections(){
		// close off all connections.
		for (int i=0; i < this.partitionCount; i++) {
			ConnectionHandle conn;
			while ((conn = this.partitions[i].getFreeConnections().poll()) != null){
				postDestroyConnection(conn);
		
				try {
					conn.internalClose();
				} catch (SQLException e) {
					logger.error(e);
				}
			}
		}
	}

	/** Update counters and call hooks.
	 * @param handle connection handle.
	 */
	protected void postDestroyConnection(ConnectionHandle handle){
		ConnectionPartition partition = handle.getOriginatingPartition();
		partition.updateCreatedConnections(-1);
		partition.setUnableToCreateMoreTransactions(false); // we can create new ones now

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

	}
	/**
	 * Constructor.
	 * @param config Configuration for pool
	 * @throws SQLException on error
	 */
	public BoneCP(BoneCPConfig config) throws SQLException {
		
		config.sanitize();
		this.releaseHelperThreadsConfigured = config.getReleaseHelperThreads() > 0;
		this.config = config;
		this.partitions = new ConnectionPartition[config.getPartitionCount()];
		this.keepAliveScheduler =  Executors.newScheduledThreadPool(config.getPartitionCount(), new CustomThreadFactory("TinyCP-keep-alive-scheduler", true));
		this.connectionsScheduler =  Executors.newFixedThreadPool(config.getPartitionCount(), new CustomThreadFactory("TinyCP-pool-watch-thread", true));
		this.partitionCount = config.getPartitionCount();

		for (int p=0; p < config.getPartitionCount(); p++){
			ConnectionPartition connectionPartition = new ConnectionPartition(this);
			final Runnable connectionTester = new ConnectionTesterThread(connectionPartition, this.keepAliveScheduler, this);
			this.partitions[p]=connectionPartition;
			this.partitions[p].setFreeConnections(new ArrayBlockingQueue(config.getMaxConnectionsPerPartition()));

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

			if (config.getIdleConnectionTestPeriod() > 0){
				this.keepAliveScheduler.scheduleAtFixedRate(connectionTester, config.getIdleConnectionTestPeriod(), config.getIdleConnectionTestPeriod(), TimeUnit.MILLISECONDS);
			}

			// watch this partition for low no of threads
			this.connectionsScheduler.execute(new PoolWatchThread(connectionPartition, this));
		}
	}



	/**
	 * Returns a free connection.
	 * @return Connection handle.
	 * @throws SQLException 
	 */
	public Connection getConnection() throws SQLException {
		int partition = (int) (Thread.currentThread().getId() % this.partitionCount);

		ConnectionPartition connectionPartition = this.partitions[partition];
		if (!connectionPartition.isUnableToCreateMoreTransactions() ){
			try{
				this.connectionsObtainedLock.lock();
	 			maybeSignalForMoreConnections(connectionPartition);
			} finally {
				this.connectionsObtainedLock.unlock(); 
			}
		}

		ConnectionHandle result;
		if (this.connectionStarvationTriggered) {
			try{
				result = connectionPartition.getFreeConnections().take();
			}
			catch (InterruptedException e) {
				throw new SQLException(e);
			}
		} else { 
			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();
				connectionPartition = this.partitions[i];
				if (result != null) {
					break;
				}
			}
		}

		// we still didn't find an empty one, wait forever until our partition is free
		if (result == null) {
			try {
				this.connectionStarvationTriggered   = true; 
				result = connectionPartition.getFreeConnections().take();
			}
			catch (InterruptedException e) {
				throw new SQLException(e);
			}
		}
		result.setOriginatingPartition(connectionPartition);
		result.renewConnection();
		
		// Give an application the chance to do something with it.
		if (result.getConnectionHook() != null){
			result.getConnectionHook().onCheckOut(result);
		}

		return result;
	}

	/**
	 * 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() && connectionPartition.getFreeConnections().size()*100/connectionPartition.getMaxConnections() < HIT_THRESHOLD){
			try{
				connectionPartition.lockAlmostFullLock();
				connectionPartition.almostFullSignal();
			} finally {
				connectionPartition.unlockAlmostFullLock(); 
			}
		}


	}

	/**
	 * Move the incoming connection unto a different queue pending release.
	 *
	 * @param connection to release
	 * @throws SQLException
	 */
	public void releaseConnection(Connection connection) throws SQLException {
		
		try {
			ConnectionHandle handle = (ConnectionHandle)connection;
			
			// hook calls
			if (handle.getConnectionHook() != null){
				handle.getConnectionHook().onCheckIn(handle);
			}
			
			if (this.releaseHelperThreadsConfigured){
				handle.getOriginatingPartition().getConnectionsPendingRelease().put(handle);
			} else {
				internalReleaseConnection(handle);
			}
		}
		catch (InterruptedException e) {
			throw new SQLException(e);
		}
	}

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

		connectionHandle.clearStatementHandles(false);
		if (connectionHandle.isPossiblyBroken() && !isConnectionHandleAlive(connectionHandle)){

			ConnectionPartition connectionPartition = connectionHandle.getOriginatingPartition();
			maybeSignalForMoreConnections(connectionPartition);

			postDestroyConnection(connectionHandle);
			return; // don't place back in queue - connection is broken!
		}

		connectionHandle.setConnectionLastUsed(System.currentTimeMillis());
		releaseInAnyFreePartition(connectionHandle, connectionHandle.getOriginatingPartition());
	}

	/**
	 * Releases the given connection in any available partition, starting off
	 * with the active one.
	 *
	 * @param connectionHandle to release
	 * @param activePartition Preferred partition to release this connection to
	 * @throws InterruptedException on break
	 */
	protected void releaseInAnyFreePartition(ConnectionHandle connectionHandle, ConnectionPartition activePartition) throws InterruptedException  {

		ConnectionPartition workingPartition = activePartition;
		if (!workingPartition.getFreeConnections().offer(connectionHandle)){
			// we ran out of space on this partition, pick another free one
			boolean released = false;
			for (int i=0; i < this.partitionCount; i++){
				if (this.partitions[i].getFreeConnections().offer(connectionHandle)){
					released=true;
					break;
				}
			}

			if (!released)	{
				// we still didn't find an empty one, wait forever until our partition is free
				connectionHandle.getOriginatingPartition().getFreeConnections().put(connectionHandle);
			}
		}

	}

	/** 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; 
		try {
			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();
				rs = stmt.executeQuery(testStatement);
			}
              
 
			if (rs != null) { 
				rs.close();
			}

			result = true;
		} catch (SQLException e) {
			// connection must be broken!
			result = false;
		} finally {
			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].getFreeConnections().size();
		}
		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].getFreeConnections().size();
		}
		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
	 */
	public ExecutorService getReleaseHelper() {
		return this.releaseHelper;
	}

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

}
>>>>>>> d7a6bd4eb2ee52d1e4106dadf6c5f63704d5d659
Solution content
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;

/**
 * Connection pool.
 * @author wwadge
 *
 */
public class 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 threads when we hit x% of our possible number of connections. */
	public static final int HIT_THRESHOLD = 20;
	/** 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;
	/** 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;
	/** almost full locking. **/
	final Lock connectionsObtainedLock = new ReentrantLock();
	/** If set to true, we have run out of connections at some point. */
	private volatile boolean connectionStarvationTriggered = false;
	/** 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;
	/** Logger class. */
	private static Logger logger = Logger.getLogger(BoneCP.class);


	/**
	 * Closes off this connection pool.
	 */
	public void shutdown(){
		this.keepAliveScheduler.shutdownNow(); // stop threads from firing.
		this.connectionsScheduler.shutdownNow(); // stop threads from firing.

		terminateAllConnections();
	}
		}

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

	/** Closes off all connections in all partitions. */
	protected void terminateAllConnections(){
		// close off all connections.
		for (int i=0; i < this.partitionCount; i++) {
			ConnectionHandle conn;
			while ((conn = this.partitions[i].getFreeConnections().poll()) != null){
				postDestroyConnection(conn);
		
				try {
					conn.internalClose();
				} catch (SQLException e) {
					logger.error(e);
				}
			}
		}
	}

	/** Update counters and call hooks.
	 * @param handle connection handle.
	 */
	protected void postDestroyConnection(ConnectionHandle handle){
		ConnectionPartition partition = handle.getOriginatingPartition();
		partition.updateCreatedConnections(-1);
		partition.setUnableToCreateMoreTransactions(false); // we can create new ones now

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

	}
	/**
	 * Constructor.
	 * @param config Configuration for pool
	 * @throws SQLException on error
	 */
	public BoneCP(BoneCPConfig config) throws SQLException {
		
		config.sanitize();
		this.releaseHelperThreadsConfigured = config.getReleaseHelperThreads() > 0;
		this.config = config;
		this.partitions = new ConnectionPartition[config.getPartitionCount()];
		this.keepAliveScheduler =  Executors.newScheduledThreadPool(config.getPartitionCount(), new CustomThreadFactory("TinyCP-keep-alive-scheduler", true));
		this.connectionsScheduler =  Executors.newFixedThreadPool(config.getPartitionCount(), new CustomThreadFactory("TinyCP-pool-watch-thread", true));
		this.partitionCount = config.getPartitionCount();

		for (int p=0; p < config.getPartitionCount(); p++){
			ConnectionPartition connectionPartition = new ConnectionPartition(this);
			final Runnable connectionTester = new ConnectionTesterThread(connectionPartition, this.keepAliveScheduler, this);
			this.partitions[p]=connectionPartition;
			this.partitions[p].setFreeConnections(new ArrayBlockingQueue(config.getMaxConnectionsPerPartition()));

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

			if (config.getIdleConnectionTestPeriod() > 0){
				this.keepAliveScheduler.scheduleAtFixedRate(connectionTester, config.getIdleConnectionTestPeriod(), config.getIdleConnectionTestPeriod(), TimeUnit.MILLISECONDS);
			}

			// watch this partition for low no of threads
			this.connectionsScheduler.execute(new PoolWatchThread(connectionPartition, this));
		}
	}



	/**
	 * Returns a free connection.
	 * @return Connection handle.
	 * @throws SQLException 
	 */
	public Connection getConnection() throws SQLException {
		int partition = (int) (Thread.currentThread().getId() % this.partitionCount);

		ConnectionPartition connectionPartition = this.partitions[partition];
		if (!connectionPartition.isUnableToCreateMoreTransactions()){
 			maybeSignalForMoreConnections(connectionPartition);
		}

		ConnectionHandle result;
		if (this.connectionStarvationTriggered) {
			try{
				result = connectionPartition.getFreeConnections().take();
			}
			catch (InterruptedException e) {
				throw new SQLException(e);
			}
		} else { 
			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();
				connectionPartition = this.partitions[i];
				if (result != null) {
					break;
				}
			}
		}

		// we still didn't find an empty one, wait forever until our partition is free
		if (result == null) {
			try {
				this.connectionStarvationTriggered   = true; 
				result = connectionPartition.getFreeConnections().take();
			}
			catch (InterruptedException e) {
				throw new SQLException(e);
			}
		}
		result.setOriginatingPartition(connectionPartition);
		result.renewConnection();
		
		// Give an application the chance to do something with it.
		if (result.getConnectionHook() != null){
			result.getConnectionHook().onCheckOut(result);

		return result;
	}

	/**
	 * 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() && connectionPartition.getFreeConnections().size()*100/connectionPartition.getMaxConnections() < HIT_THRESHOLD){
			try{
				connectionPartition.lockAlmostFullLock();
				connectionPartition.almostFullSignal();
			} finally {
				connectionPartition.unlockAlmostFullLock(); 
			}
		}


	}

	/**
	 * Move the incoming connection unto a different queue pending release.
	 *
	 * @param connection to release
	 * @throws SQLException
	 */
	public void releaseConnection(Connection connection) throws SQLException {
		
		try {
			ConnectionHandle handle = (ConnectionHandle)connection;
			
			// hook calls
			if (handle.getConnectionHook() != null){
				handle.getConnectionHook().onCheckIn(handle);
			}
			
			if (this.releaseHelperThreadsConfigured){
				handle.getOriginatingPartition().getConnectionsPendingRelease().put(handle);
			} else {
				internalReleaseConnection(handle);
			}
		}
		catch (InterruptedException e) {
			throw new SQLException(e);
		}
	}

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

		connectionHandle.clearStatementHandles(false);
		if (connectionHandle.isPossiblyBroken() && !isConnectionHandleAlive(connectionHandle)){

			ConnectionPartition connectionPartition = connectionHandle.getOriginatingPartition();
			maybeSignalForMoreConnections(connectionPartition);

			postDestroyConnection(connectionHandle);
			return; // don't place back in queue - connection is broken!
		}

		connectionHandle.setConnectionLastUsed(System.currentTimeMillis());
		releaseInAnyFreePartition(connectionHandle, connectionHandle.getOriginatingPartition());
	}

	/**
	 * Releases the given connection in any available partition, starting off
	 * with the active one.
	 *
	 * @param connectionHandle to release
	 * @param activePartition Preferred partition to release this connection to
	 * @throws InterruptedException on break
	 */
	protected void releaseInAnyFreePartition(ConnectionHandle connectionHandle, ConnectionPartition activePartition) throws InterruptedException  {

		ConnectionPartition workingPartition = activePartition;
		if (!workingPartition.getFreeConnections().offer(connectionHandle)){
			// we ran out of space on this partition, pick another free one
			boolean released = false;
			for (int i=0; i < this.partitionCount; i++){
				if (this.partitions[i].getFreeConnections().offer(connectionHandle)){
					released=true;
					break;
				}
			}

			if (!released)	{
				// we still didn't find an empty one, wait forever until our partition is free
				connectionHandle.getOriginatingPartition().getFreeConnections().put(connectionHandle);
			}
		}

	}

	/** 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; 
		try {
			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();
				rs = stmt.executeQuery(testStatement);
			}
              
 
			if (rs != null) { 
				rs.close();
			}

			result = true;
		} catch (SQLException e) {
			// connection must be broken!
			result = false;
		} finally {
			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].getFreeConnections().size();
		}
		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].getFreeConnections().size();
		}
		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
	 */
	public ExecutorService getReleaseHelper() {
		return this.releaseHelper;
	}

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

}
File
BoneCP.java
Developer's decision
Combination
Kind of conflict
Class declaration
Comment
Import
Package declaration
Chunk
Conflicting content
<<<<<<< HEAD
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

	}
import java.sql.PreparedStatement;
BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.log4j.Logger;

import com.google.common.collect.ImmutableSet;

/**
 * Connection handle "wrapper". Proxies are nice and cool... but around twice as
 * slow as doing it the boring old way. No proxy stuff here, we're aiming for
 * the best speed possible at the expense of code readability...
 * 
 * @author wwadge
 * 
 */
public class ConnectionHandle implements Connection {

	/** Connection handle. */
	private Connection connection = null;
	/** Last time this connection was used by an application. */
	private long connectionLastUsed = System.currentTimeMillis();
	/** Last time we sent a reset to this connection. */
	private long connectionLastReset = System.currentTimeMillis();
	/** Pool handle. */
	private BoneCP pool;
	/**
	 * If true, this connection might have failed communicating with the
	 * database. We assume that exceptions should be rare here i.e. the normal
	 * case is assumed to succeed.
	 */
	private boolean possiblyBroken;
	/** If true, we've called internalClose() on this connection. */
	private boolean connectionClosed = false;
	/** Original partition. */
	private ConnectionPartition originatingPartition = null;
	/** Prepared Statement Cache. */
	private IStatementCache preparedStatementCache = null;
	/** Prepared Statement Cache. */
	private IStatementCache callableStatementCache = null;
	/** Logger handle. */
	private static Logger logger = Logger.getLogger(ConnectionHandle.class);
	/** An opaque handle for an application to use in any way it deems fit. */
	private Object debugHandle;

	/**
	 * List of statements that will be closed when this preparedStatement is
	 * logically closed.
	 */
	private ConcurrentLinkedQueue statementHandles = new ConcurrentLinkedQueue();
	/**
	 * From: http://publib.boulder.ibm.com/infocenter/db2luw/v8/index.jsp?topic=/com.ibm.db2.udb.doc/core/r0sttmsg.htm
	 * Table 7. Class Code 08: Connection Exception
		SQLSTATE Value	  
		Value	Meaning
		08001	The application requester is unable to establish the connection.
		08002	The connection already exists.
		08003	The connection does not exist.
		08004	The application server rejected establishment of the connection.
		08007	Transaction resolution unknown.
		08502	The CONNECT statement issued by an application process running with a SYNCPOINT of TWOPHASE has failed, because no transaction manager is available.
		08504	An error was encountered while processing the specified path rename configuration file.
	 SQL Failure codes indicating the database is broken/died (and thus kill off remaining connections). 
	  Anything else will be taken as the *connection* (not the db) being broken. 

	  08S01 is a mysql-specific code to indicate a connection failure (though in my tests it was triggered even when the entire
	  database was down so I treat that as a DB failure)
	 */
	private static final ImmutableSet sqlStateDBFailureCodes = ImmutableSet.of("08001", "08007", "08S01"); 
	/**
	 * Connection wrapper constructor
	 * 
	 * @param url
	 *            JDBC connection string
	 * @param username
	 *            user to use
	 * @param password
	 *            password for db
	 * @param pool
	 *            pool handle.
	 * @throws SQLException
	 *             on error
	 */
	public ConnectionHandle(String url, String username, String password,
			BoneCP pool) throws SQLException {
		try {
			this.connection = DriverManager.getConnection(url, username,
					password);

			int cacheSize = pool.getConfig().getPreparedStatementsCacheSize();
			if (cacheSize > 0) {
				this.preparedStatementCache = new StatementCache(cacheSize,  pool.getConfig().getStatementsCachedPerConnection());
				this.callableStatementCache = new StatementCache(cacheSize,  pool.getConfig().getStatementsCachedPerConnection());

			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		this.pool = pool;
	}

	/** Private constructor used solely for unit testing. 
	 * @param connection 
	 * @param preparedStatementCache 
	 * @param callableStatementCache 
	 * @param pool */
	public ConnectionHandle(Connection connection, IStatementCache preparedStatementCache, IStatementCache callableStatementCache, BoneCP pool){
		this.connection = connection;
		this.preparedStatementCache = preparedStatementCache;
		this.callableStatementCache = callableStatementCache;
		this.pool = pool;
	}


	/**
	 * Adds the given statement to a list.
	 * 
	 * @param statement
	 *            Statement to keep track of
	 * @return statement
	 */
	private Statement trackStatement(Statement statement) {
		if (statement != null){
			this.statementHandles.add(statement);
		}
		return statement;
	}

	/** 
	 * Given an exception, flag the connection (or database) as being potentially broken. If the exception is a data-specific exception,
	 * do nothing except throw it back to the application. 
	 * 
	 * @param t throwable exception to process
	 * @return SQLException for further processing
	 */
	protected SQLException markPossiblyBroken(Throwable t) {
		SQLException e;
		if (t instanceof SQLException){
			e=(SQLException) t;
		} else {
			e = new SQLException(t == null ? "Unknown error" : t.getMessage(), "08999");
			e.initCause( t );
		}

		String state = e.getSQLState();
		if (state == null){ // safety;
			state = "Z";
		}
		if (sqlStateDBFailureCodes.contains(state) && this.pool != null){
			logger.error("Database access problem. Killing off all remaining connections in the connection pool. SQL State = " + state);
			this.pool.terminateAllConnections();
		}

		// SQL-92 says:
		//		 Class values that begin with one of the s '5', '6', '7',
		//         '8', or '9' or one of the s 'I',
		//         'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
		//         'W', 'X', 'Y', or 'Z' are reserved for implementation-specified
		//         conditions.


		return result;
		char firstChar = state.charAt(0);
		// if it's a communication exception or an implementation-specific error code, flag this connection as being potentially broken.
		if (state.startsWith("08") ||  (firstChar >= '5' && firstChar <='9') || (firstChar >='I' && firstChar <= 'Z')){
			this.possiblyBroken = true;
		}

		return e;
	}


	public void clearWarnings() throws SQLException {
		checkClosed();
		try {
			this.connection.clearWarnings();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	/**
	 * Checks if the connection is (logically) closed and throws an exception if it is.
	 * 
	 * @throws SQLException
	 *             on error
	 * 
	 * 
	 */
	private void checkClosed() throws SQLException {
		if (this.connectionClosed) {
			throw new SQLException("Connection is closed.");
		}
	}

	/**
	 * Release the connection if called. never really thrown
	 * 
	 * @throws SQLException
	 */
	public void close() throws SQLException {
		try {
			if (!this.connectionClosed) {
				this.connectionClosed = true;
				this.pool.releaseConnection(this);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	/**
	 * Close off the connection.
	 * 
	 * @throws SQLException
	 */
	protected void internalClose() throws SQLException {
		try {
			clearStatementHandles(true);
			this.connection.close();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void commit() throws SQLException {
		checkClosed();
		try {
			this.connection.commit();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public Array createArrayOf(String typeName, Object[] elements)
	throws SQLException {
		Array result = null;
		checkClosed();
		try {
			result = this.connection.createArrayOf(typeName, elements);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

		return result;
	}

	public Blob createBlob() throws SQLException {
		Blob result = null;
		checkClosed();
		try {
			result = this.connection.createBlob();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Clob createClob() throws SQLException {
		Clob result = null;
		checkClosed();
		try {
			result = this.connection.createClob();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

		return result;

	}

	public NClob createNClob() throws SQLException {
		NClob result = null;
		checkClosed();
		try {
			result = this.connection.createNClob();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

	public SQLXML createSQLXML() throws SQLException {
		SQLXML result = null;
		checkClosed();
		try {
			result = this.connection.createSQLXML();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Statement createStatement() throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			result = trackStatement(new StatementHandle(this.connection.createStatement(), this));
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Statement createStatement(int resultSetType, int resultSetConcurrency)
	throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			result = trackStatement(new StatementHandle(this.connection.createStatement(resultSetType, resultSetConcurrency), this));
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Statement createStatement(int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
	throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			result = trackStatement(new StatementHandle(this.connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability), this));
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

		return result;
	}

	public Struct createStruct(String typeName, Object[] attributes)
	throws SQLException {
		Struct result = null;
		checkClosed();
		try {
			result = this.connection.createStruct(typeName, attributes);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean getAutoCommit() throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.getAutoCommit();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public String getCatalog() throws SQLException {
		String result = null;
		checkClosed();
		try {
			result = this.connection.getCatalog();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Properties getClientInfo() throws SQLException {
		Properties result = null;
		checkClosed();
		try {
			result = this.connection.getClientInfo();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public String getClientInfo(String name) throws SQLException {
		String result = null;
		checkClosed();
		try {
			result = this.connection.getClientInfo(name);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public int getHoldability() throws SQLException {
		int result = 0;
		checkClosed();
		try {
			result = this.connection.getHoldability();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
		return result;
	}

		return result;
	}

	public DatabaseMetaData getMetaData() throws SQLException {
		DatabaseMetaData result = null;
		checkClosed();
		try {
			result = this.connection.getMetaData();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public int getTransactionIsolation() throws SQLException {
		int result = 0;
		checkClosed();
		try {
			result = this.connection.getTransactionIsolation();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Map> getTypeMap() throws SQLException {
		Map> result = null;
		checkClosed();
		try {
			result = this.connection.getTypeMap();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public SQLWarning getWarnings() throws SQLException {
		SQLWarning result = null;
		checkClosed();
		try {
			result = this.connection.getWarnings();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean isClosed() throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.isClosed();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean isReadOnly() throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.isReadOnly();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean isValid(int timeout) throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.isValid(timeout);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public String nativeSQL(String sql) throws SQLException {
		String result = null;
		checkClosed();
		try {
			result = this.connection.nativeSQL(sql);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public CallableStatement prepareCall(String sql) throws SQLException {
		CallableStatement result = null;
		checkClosed();
		try {

			if (this.callableStatementCache != null) {
				result = (CallableStatement) this.callableStatementCache.get(sql);
				if (result == null) {
					String cacheKey = sql;
					result = (CallableStatement) trackStatement(new CallableStatementHandle(this.connection.prepareCall(sql),
							sql, this.callableStatementCache, this, cacheKey));

				} 

				((CallableStatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareCall(sql);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public CallableStatement prepareCall(String sql, int resultSetType,
			int resultSetConcurrency) throws SQLException {
		CallableStatement result = null;
		checkClosed();
		try {
			if (this.callableStatementCache != null) {
				result = (CallableStatement) this.callableStatementCache.get(sql, resultSetType, resultSetConcurrency);

				if (result == null) {
					String cacheKey = this.callableStatementCache.calculateCacheKey(sql, resultSetType, resultSetConcurrency);
					result = (CallableStatement) trackStatement(new CallableStatementHandle(
							this.connection.prepareCall(sql, resultSetType, resultSetConcurrency), sql, this.callableStatementCache, this, cacheKey));

				} 

				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareCall(sql, resultSetType, resultSetConcurrency);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

	public CallableStatement prepareCall(String sql, int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
	throws SQLException {
		CallableStatement result = null;
		checkClosed();
		try {
			if (this.callableStatementCache != null) {

				result = (CallableStatement) this.callableStatementCache.get(sql);

				if (result == null) {
					result = (CallableStatement) trackStatement(new CallableStatementHandle(
							this.connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability), sql,
							this.callableStatementCache, this, sql));

				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareCall(sql, resultSetType,
						resultSetConcurrency, resultSetHoldability);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql) throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = this.preparedStatementCache.get(sql);

				if (result == null) {

					String cacheKey = sql;

					result =  trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql), sql,
							this.preparedStatementCache, this, cacheKey));
				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return (PreparedStatement) result;
	}

	public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, autoGeneratedKeys);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, autoGeneratedKeys);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql,
									autoGeneratedKeys), sql,
									this.preparedStatementCache, this, cacheKey));
				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql,
						autoGeneratedKeys);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, columnIndexes);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, columnIndexes);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection
							.prepareStatement(sql, columnIndexes), sql,
							this.preparedStatementCache, this, cacheKey));
				}
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, columnIndexes);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, String[] columnNames)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, columnNames);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, columnNames);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql, columnNames),
							sql, this.preparedStatementCache, this, cacheKey));

				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, columnNames);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, int resultSetType,
			int resultSetConcurrency) throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, resultSetType, resultSetConcurrency);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, resultSetType, resultSetConcurrency);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql,
									resultSetType, resultSetConcurrency), sql,
									this.preparedStatementCache, this, cacheKey));
				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, resultSetType,
						resultSetConcurrency);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, resultSetType, resultSetConcurrency, resultSetHoldability);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql,
									resultSetType, resultSetConcurrency,
									resultSetHoldability), sql,
									this.preparedStatementCache, this, cacheKey));

				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, resultSetType,
						resultSetConcurrency, resultSetHoldability);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public void releaseSavepoint(Savepoint savepoint) throws SQLException {
		checkClosed();
		try {
			this.connection.releaseSavepoint(savepoint);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);

		}
	}

	public void rollback() throws SQLException {
		checkClosed();
		try {
			this.connection.rollback();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void rollback(Savepoint savepoint) throws SQLException {
		checkClosed();
		try {
			this.connection.rollback(savepoint);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setAutoCommit(boolean autoCommit) throws SQLException {
		checkClosed();
		try {
			this.connection.setAutoCommit(autoCommit);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setCatalog(String catalog) throws SQLException {
		checkClosed();
		try {
			this.connection.setCatalog(catalog);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setClientInfo(Properties properties)
	throws SQLClientInfoException {
		this.connection.setClientInfo(properties);
	}

	public void setClientInfo(String name, String value)
	throws SQLClientInfoException {
		this.connection.setClientInfo(name, value);
	}

	public void setHoldability(int holdability) throws SQLException {
		checkClosed();
		try {
			this.connection.setHoldability(holdability);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setReadOnly(boolean readOnly) throws SQLException {
		checkClosed();
		try {
			this.connection.setReadOnly(readOnly);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public Savepoint setSavepoint() throws SQLException {
		checkClosed();
		Savepoint result = null;
		try {
			result = this.connection.setSavepoint();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Savepoint setSavepoint(String name) throws SQLException {
		checkClosed();
		Savepoint result = null;
		try {
			result = this.connection.setSavepoint(name);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public void setTransactionIsolation(int level) throws SQLException {
		checkClosed();
		try {
			this.connection.setTransactionIsolation(level);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setTypeMap(Map> map) throws SQLException {
		checkClosed();
		try {
			this.connection.setTypeMap(map);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public boolean isWrapperFor(Class iface) throws SQLException {
		return this.connection.isWrapperFor(iface);
	}

	public  T unwrap(Class iface) throws SQLException {
		return this.connection.unwrap(iface);
	}

	/**
	 * @return the connectionLastUsed
	 */
	public long getConnectionLastUsed() {
		return this.connectionLastUsed;
	}

	/**
	 * @param connectionLastUsed
	 *            the connectionLastUsed to set
	 */
	public void setConnectionLastUsed(long connectionLastUsed) {
		this.connectionLastUsed = connectionLastUsed;
	}

	/**
	 * @return the connectionLastReset
	 */
	public long getConnectionLastReset() {
		return this.connectionLastReset;
	}

	/**
	 * @param connectionLastReset
	 *            the connectionLastReset to set
	 */
	public void setConnectionLastReset(long connectionLastReset) {
		this.connectionLastReset = connectionLastReset;
	}

	/**
	 * Gets true if connection has triggered an exception at some point.
	 * 
	 * @return true if the connection has triggered an error
	 */
	public boolean isPossiblyBroken() {
		return this.possiblyBroken;
	}


	/**
	 * Gets the partition this came from.
	 * 
	 * @return the partition this came from
	 */
	public ConnectionPartition getOriginatingPartition() {
		return this.originatingPartition;
	}

	/**
	 * Sets Originating partition
	 * 
	 * @param originatingPartition
	 *            to set
	 */
	public void setOriginatingPartition(ConnectionPartition originatingPartition) {
		this.originatingPartition = originatingPartition;
	}

	/**
	 * Renews this connection, i.e. Sets this connection to be logically open
	 * (although it was never really closed)
	 */
	public void renewConnection() {
		this.connectionClosed = false;
	}


	/** Clears out the statement handles.
	 * @param internalClose if true, close the inner statement handle too. 
	 * @throws SQLException
	 */
	protected void clearStatementHandles(boolean internalClose) throws SQLException {
		if (!internalClose){
			this.statementHandles.clear();
		} else{
			Statement statement = null;
			while ((statement = this.statementHandles.poll()) != null) {
				((StatementHandle) statement).internalClose();
			}
		}
	}

	/** Returns a debug handle as previously set by an application
	 * @return DebugHandle
	 */
	public Object getDebugHandle() {
		return this.debugHandle;
	}

	/** Sets a debugHandle, an object that is not used by the connection pool at all but may be set by an application to track
	 * this particular connection handle for any purpose it deems fit.
	 * @param debugHandle any object.
	 */
	public void setDebugHandle(Object debugHandle) {
		this.debugHandle = debugHandle;
	}
}
=======
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.log4j.Logger;

import com.google.common.collect.ImmutableSet;
import com.jolbox.bonecp.hooks.ConnectionHook;

/**
 * Connection handle "wrapper". Proxies are nice and cool... but around twice as
 * slow as doing it the boring old way. No proxy stuff here, we're aiming for
 * the best speed possible at the expense of code readability...
 * 
 * @author wwadge
 * 
 */
public class ConnectionHandle implements Connection {

	/** Connection handle. */
	private Connection connection = null;
	/** Last time this connection was used by an application. */
	private long connectionLastUsed = System.currentTimeMillis();
	/** Last time we sent a reset to this connection. */
	private long connectionLastReset = System.currentTimeMillis();
	/** Pool handle. */
	private BoneCP pool;
	/**
	 * If true, this connection might have failed communicating with the
	 * database. We assume that exceptions should be rare here i.e. the normal
	 * case is assumed to succeed.
	 */
	private boolean possiblyBroken;
	/** If true, we've called internalClose() on this connection. */
	private boolean connectionClosed = false;
	/** Original partition. */
	private ConnectionPartition originatingPartition = null;
	/** Prepared Statement Cache. */
	private IStatementCache preparedStatementCache = null;
	/** Prepared Statement Cache. */
	private IStatementCache callableStatementCache = null;
	/** Logger handle. */
	private static Logger logger = Logger.getLogger(ConnectionHandle.class);
	/** An opaque handle for an application to use in any way it deems fit. */
	private Object debugHandle;

	/**
	 * List of statements that will be closed when this preparedStatement is
	 * logically closed.
	 */
	private ConcurrentLinkedQueue statementHandles = new ConcurrentLinkedQueue();
	/** Handle to the connection hook as defined in the config. */
	private ConnectionHook connectionHook;
	
	/**
	 * From: http://publib.boulder.ibm.com/infocenter/db2luw/v8/index.jsp?topic=/com.ibm.db2.udb.doc/core/r0sttmsg.htm
	 * Table 7. Class Code 08: Connection Exception
		SQLSTATE Value	  
		Value	Meaning
		08001	The application requester is unable to establish the connection.
		08002	The connection already exists.
		08003	The connection does not exist.
		08004	The application server rejected establishment of the connection.
		08007	Transaction resolution unknown.
		08502	The CONNECT statement issued by an application process running with a SYNCPOINT of TWOPHASE has failed, because no transaction manager is available.
		08504	An error was encountered while processing the specified path rename configuration file.
		checkClosed();
	 SQL Failure codes indicating the database is broken/died (and thus kill off remaining connections). 
	  Anything else will be taken as the *connection* (not the db) being broken. 

	  08S01 is a mysql-specific code to indicate a connection failure (though in my tests it was triggered even when the entire
	  database was down so I treat that as a DB failure)
	 */
	private static final ImmutableSet sqlStateDBFailureCodes = ImmutableSet.of("08001", "08007", "08S01"); 
	/**
	 * Connection wrapper constructor
	 * 
	 * @param url
	 *            JDBC connection string
	 * @param username
	 *            user to use
	 * @param password
	 *            password for db
	 * @param pool
	 *            pool handle.
	 * @throws SQLException
	 *             on error
	 */
	public ConnectionHandle(String url, String username, String password,
			BoneCP pool) throws SQLException {
		
		this.pool = pool;

		try {
			this.connection = DriverManager.getConnection(url, username,
					password);

			int cacheSize = pool.getConfig().getPreparedStatementsCacheSize();
			if (cacheSize > 0) {
				this.preparedStatementCache = new StatementCache(cacheSize,  pool.getConfig().getStatementsCachedPerConnection());
				this.callableStatementCache = new StatementCache(cacheSize,  pool.getConfig().getStatementsCachedPerConnection());

			}
			// keep track of this hook.
			this.connectionHook = this.pool.getConfig().getConnectionHook();
			// call the hook, if available.
			if (this.connectionHook != null){
				this.connectionHook.onAcquire(this);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	/** Private constructor used solely for unit testing. 
	 * @param connection 
	 * @param preparedStatementCache 
	 * @param callableStatementCache 
	 * @param pool */
	public ConnectionHandle(Connection connection, IStatementCache preparedStatementCache, IStatementCache callableStatementCache, BoneCP pool){
		this.connection = connection;
		this.preparedStatementCache = preparedStatementCache;
		this.callableStatementCache = callableStatementCache;
		this.pool = pool;
	}


	/**
	 * Adds the given statement to a list.
	 * 
	 * @param statement
	 *            Statement to keep track of
	 * @return statement
	 */
	private Statement trackStatement(Statement statement) {
		if (statement != null){
			this.statementHandles.add(statement);
		}
		return statement;
	}

	/** 
	 * Given an exception, flag the connection (or database) as being potentially broken. If the exception is a data-specific exception,
	 * do nothing except throw it back to the application. 
	 * 
	 * @param t throwable exception to process
	 * @return SQLException for further processing
	 */
	protected SQLException markPossiblyBroken(Throwable t) {
		SQLException e;
		if (t instanceof SQLException){
			e=(SQLException) t;
		} else {
			e = new SQLException(t == null ? "Unknown error" : t.getMessage(), "08999");
			e.initCause( t );
		}

		String state = e.getSQLState();
		if (state == null){ // safety;
			state = "Z";
		}
		if (sqlStateDBFailureCodes.contains(state) && this.pool != null){
			logger.error("Database access problem. Killing off all remaining connections in the connection pool. SQL State = " + state);
			this.pool.terminateAllConnections();
		}

		// SQL-92 says:
		NClob result = null;
		//		 Class values that begin with one of the s '5', '6', '7',
		//         '8', or '9' or one of the s 'I',
		//         'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
		//         'W', 'X', 'Y', or 'Z' are reserved for implementation-specified
		//         conditions.


		char firstChar = state.charAt(0);
		// if it's a communication exception or an implementation-specific error code, flag this connection as being potentially broken.
		if (state.startsWith("08") ||  (firstChar >= '5' && firstChar <='9') || (firstChar >='I' && firstChar <= 'Z')){
			this.possiblyBroken = true;
		}

		return e;
	}


	public void clearWarnings() throws SQLException {
		checkClosed();
		try {
			this.connection.clearWarnings();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	/**
	 * Checks if the connection is (logically) closed and throws an exception if it is.
	 * 
	 * @throws SQLException
	 *             on error
	 * 
	 * 
	 */
	private void checkClosed() throws SQLException {
		if (this.connectionClosed) {
			throw new SQLException("Connection is closed.");
		}
	}

	/**
	 * Release the connection if called. 
	 * 
	 * @throws SQLException Never really thrown
	 */
	public void close() throws SQLException {
		try {
			if (!this.connectionClosed) {
				this.connectionClosed = true;
				this.pool.releaseConnection(this);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	/**
	 * Close off the connection.
	 * 
	 * @throws SQLException
	 */
	protected void internalClose() throws SQLException {
		try {
			clearStatementHandles(true);
			this.connection.close();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void commit() throws SQLException {
		checkClosed();
		try {
			this.connection.commit();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public Array createArrayOf(String typeName, Object[] elements)
	throws SQLException {
		Array result = null;
		checkClosed();
		try {
			result = this.connection.createArrayOf(typeName, elements);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

		return result;
	}

	public Blob createBlob() throws SQLException {
		Blob result = null;
		checkClosed();
		try {
			result = this.connection.createBlob();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Clob createClob() throws SQLException {
		Clob result = null;
		checkClosed();
		try {
			result = this.connection.createClob();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

		return result;

	}

	public NClob createNClob() throws SQLException {
		try {
			result = this.connection.createNClob();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public SQLXML createSQLXML() throws SQLException {
		SQLXML result = null;
		checkClosed();
		try {
			result = this.connection.createSQLXML();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Statement createStatement() throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			result = trackStatement(new StatementHandle(this.connection.createStatement(), this));
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Statement createStatement(int resultSetType, int resultSetConcurrency)
	throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			result = trackStatement(new StatementHandle(this.connection.createStatement(resultSetType, resultSetConcurrency), this));
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Statement createStatement(int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
	throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			result = trackStatement(new StatementHandle(this.connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability), this));
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

		return result;
	}

	public Struct createStruct(String typeName, Object[] attributes)
	throws SQLException {
		Struct result = null;
		checkClosed();
		try {
			result = this.connection.createStruct(typeName, attributes);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean getAutoCommit() throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.getAutoCommit();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public String getCatalog() throws SQLException {
		String result = null;
		checkClosed();
	}
		try {
			result = this.connection.getCatalog();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Properties getClientInfo() throws SQLException {
		Properties result = null;
		checkClosed();
		try {
			result = this.connection.getClientInfo();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public String getClientInfo(String name) throws SQLException {
		String result = null;
		checkClosed();
		try {
			result = this.connection.getClientInfo(name);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public int getHoldability() throws SQLException {
		int result = 0;
		checkClosed();
		try {
			result = this.connection.getHoldability();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

		return result;
	}

	public DatabaseMetaData getMetaData() throws SQLException {
		DatabaseMetaData result = null;
		checkClosed();
		try {
			result = this.connection.getMetaData();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public int getTransactionIsolation() throws SQLException {
		int result = 0;
		checkClosed();
		try {
			result = this.connection.getTransactionIsolation();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Map> getTypeMap() throws SQLException {
		Map> result = null;
		checkClosed();
		try {
			result = this.connection.getTypeMap();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public SQLWarning getWarnings() throws SQLException {
		SQLWarning result = null;
		checkClosed();
		try {
			result = this.connection.getWarnings();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean isClosed() throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.isClosed();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean isReadOnly() throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.isReadOnly();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean isValid(int timeout) throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.isValid(timeout);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;

	public String nativeSQL(String sql) throws SQLException {
		String result = null;
		checkClosed();
		try {
			result = this.connection.nativeSQL(sql);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public CallableStatement prepareCall(String sql) throws SQLException {
		CallableStatement result = null;
		checkClosed();
		try {

			if (this.callableStatementCache != null) {
				result = (CallableStatement) this.callableStatementCache.get(sql);
				if (result == null) {
					String cacheKey = sql;
					result = (CallableStatement) trackStatement(new CallableStatementHandle(this.connection.prepareCall(sql),
							sql, this.callableStatementCache, this, cacheKey));

				} 

				((CallableStatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareCall(sql);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public CallableStatement prepareCall(String sql, int resultSetType,
			int resultSetConcurrency) throws SQLException {
		CallableStatement result = null;
		checkClosed();
		try {
			if (this.callableStatementCache != null) {
				result = (CallableStatement) this.callableStatementCache.get(sql, resultSetType, resultSetConcurrency);

				if (result == null) {
					String cacheKey = this.callableStatementCache.calculateCacheKey(sql, resultSetType, resultSetConcurrency);
					result = (CallableStatement) trackStatement(new CallableStatementHandle(
							this.connection.prepareCall(sql, resultSetType, resultSetConcurrency), sql, this.callableStatementCache, this, cacheKey));

				} 

				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareCall(sql, resultSetType, resultSetConcurrency);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public CallableStatement prepareCall(String sql, int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
	throws SQLException {
		CallableStatement result = null;
		checkClosed();
		try {
			if (this.callableStatementCache != null) {

				result = (CallableStatement) this.callableStatementCache.get(sql);

				if (result == null) {
					result = (CallableStatement) trackStatement(new CallableStatementHandle(
							this.connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability), sql,
							this.callableStatementCache, this, sql));

				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareCall(sql, resultSetType,
						resultSetConcurrency, resultSetHoldability);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql) throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = this.preparedStatementCache.get(sql);

				if (result == null) {

					String cacheKey = sql;

					result =  trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql), sql,
							this.preparedStatementCache, this, cacheKey));
				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return (PreparedStatement) result;
	}

	public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, autoGeneratedKeys);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, autoGeneratedKeys);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql,
									autoGeneratedKeys), sql,
									this.preparedStatementCache, this, cacheKey));
				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql,
						autoGeneratedKeys);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, columnIndexes);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, columnIndexes);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection
							.prepareStatement(sql, columnIndexes), sql,
							this.preparedStatementCache, this, cacheKey));
				}
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, columnIndexes);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, String[] columnNames)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, columnNames);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, columnNames);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql, columnNames),
							sql, this.preparedStatementCache, this, cacheKey));

				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, columnNames);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, int resultSetType,
			int resultSetConcurrency) throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, resultSetType, resultSetConcurrency);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, resultSetType, resultSetConcurrency);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql,
									resultSetType, resultSetConcurrency), sql,
									this.preparedStatementCache, this, cacheKey));
				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, resultSetType,
						resultSetConcurrency);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, resultSetType, resultSetConcurrency, resultSetHoldability);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql,
									resultSetType, resultSetConcurrency,
									resultSetHoldability), sql,
									this.preparedStatementCache, this, cacheKey));

				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, resultSetType,
						resultSetConcurrency, resultSetHoldability);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public void releaseSavepoint(Savepoint savepoint) throws SQLException {
		checkClosed();
		try {
			this.connection.releaseSavepoint(savepoint);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);

		}
	}

	public void rollback() throws SQLException {
		checkClosed();
		try {
			this.connection.rollback();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void rollback(Savepoint savepoint) throws SQLException {
		checkClosed();
		try {
			this.connection.rollback(savepoint);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setAutoCommit(boolean autoCommit) throws SQLException {
		checkClosed();
		try {
			this.connection.setAutoCommit(autoCommit);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setCatalog(String catalog) throws SQLException {
		checkClosed();
		try {
			this.connection.setCatalog(catalog);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setClientInfo(Properties properties)
	throws SQLClientInfoException {
		this.connection.setClientInfo(properties);
	}

	public void setClientInfo(String name, String value)
		} catch (Throwable t) {
	throws SQLClientInfoException {
		this.connection.setClientInfo(name, value);
	}

	public void setHoldability(int holdability) throws SQLException {
		checkClosed();
		try {
			this.connection.setHoldability(holdability);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setReadOnly(boolean readOnly) throws SQLException {
		checkClosed();
		try {
			this.connection.setReadOnly(readOnly);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public Savepoint setSavepoint() throws SQLException {
		checkClosed();
		Savepoint result = null;
		try {
			result = this.connection.setSavepoint();
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Savepoint setSavepoint(String name) throws SQLException {
		checkClosed();
		Savepoint result = null;
		try {
			result = this.connection.setSavepoint(name);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public void setTransactionIsolation(int level) throws SQLException {
		checkClosed();
		try {
			this.connection.setTransactionIsolation(level);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setTypeMap(Map> map) throws SQLException {
		checkClosed();
		try {
			this.connection.setTypeMap(map);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public boolean isWrapperFor(Class iface) throws SQLException {
		return this.connection.isWrapperFor(iface);
	}

	public  T unwrap(Class iface) throws SQLException {
		return this.connection.unwrap(iface);
	}

	/**
	 * @return the connectionLastUsed
	 */
	public long getConnectionLastUsed() {
		return this.connectionLastUsed;
	}

	/**
	 * @param connectionLastUsed
	 *            the connectionLastUsed to set
	 */
	protected void setConnectionLastUsed(long connectionLastUsed) {
		this.connectionLastUsed = connectionLastUsed;
	}

	/**
	 * @return the connectionLastReset
	 */
	public long getConnectionLastReset() {
		return this.connectionLastReset;
	}

	/**
	 * @param connectionLastReset
	 *            the connectionLastReset to set
	 */
	protected void setConnectionLastReset(long connectionLastReset) {
		this.connectionLastReset = connectionLastReset;
	}

	/**
	 * Gets true if connection has triggered an exception at some point.
	 * 
	 * @return true if the connection has triggered an error
	 */
	public boolean isPossiblyBroken() {
		return this.possiblyBroken;
	}


	/**
	 * Gets the partition this came from.
	 * 
	 * @return the partition this came from
	 */
	public ConnectionPartition getOriginatingPartition() {
		return this.originatingPartition;
	}

	/**
	 * Sets Originating partition
	 * 
	 * @param originatingPartition
	 *            to set
	 */
	protected void setOriginatingPartition(ConnectionPartition originatingPartition) {
		this.originatingPartition = originatingPartition;
	}

	/**
	 * Renews this connection, i.e. Sets this connection to be logically open
	 * (although it was never really closed)
	 */
	protected void renewConnection() {
		this.connectionClosed = false;
	}


	/** Clears out the statement handles.
	 * @param internalClose if true, close the inner statement handle too. 
	 * @throws SQLException
	 */
	protected void clearStatementHandles(boolean internalClose) throws SQLException {
		if (!internalClose){
			this.statementHandles.clear();
		} else{
			Statement statement = null;
			while ((statement = this.statementHandles.poll()) != null) {
				((StatementHandle) statement).internalClose();
			}
		}
	}

	/** Returns a debug handle as previously set by an application
	 * @return DebugHandle
	 */
	public Object getDebugHandle() {
		return this.debugHandle;
	}

	/** Sets a debugHandle, an object that is not used by the connection pool at all but may be set by an application to track
	 * this particular connection handle for any purpose it deems fit.
	 * @param debugHandle any object.
	 */
	public void setDebugHandle(Object debugHandle) {
		this.debugHandle = debugHandle;
	}

	/** Returns the internal connection as obtained via the JDBC driver.
	 * @return the raw connection
	 */
	public Connection getRawConnection() {
		return this.connection;
	}

	/** Returns the configured connection hook object.
	 * @return the connectionHook that was set in the config
	 */
	public ConnectionHook getConnectionHook() {
		return this.connectionHook;
	}
}
>>>>>>> d7a6bd4eb2ee52d1e4106dadf6c5f63704d5d659
Solution content
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.log4j.Logger;

import com.google.common.collect.ImmutableSet;
import com.jolbox.bonecp.hooks.ConnectionHook;

/**
 * Connection handle "wrapper". Proxies are nice and cool... but around twice as
 * slow as doing it the boring old way. No proxy stuff here, we're aiming for
 * the best speed possible at the expense of code readability...
 * 
 * @author wwadge
 * 
 */
public class ConnectionHandle implements Connection {

	/** Connection handle. */
	private Connection connection = null;
	/** Last time this connection was used by an application. */
	private long connectionLastUsed = System.currentTimeMillis();
	/** Last time we sent a reset to this connection. */
	private long connectionLastReset = System.currentTimeMillis();
	/** Pool handle. */
	private BoneCP pool;
	/**
	 * If true, this connection might have failed communicating with the
	 * database. We assume that exceptions should be rare here i.e. the normal
	 * case is assumed to succeed.
	 */
	private boolean possiblyBroken;
	/** If true, we've called internalClose() on this connection. */
	private boolean connectionClosed = false;
	/** Original partition. */
	 * 
	private ConnectionPartition originatingPartition = null;
	/** Prepared Statement Cache. */
	private IStatementCache preparedStatementCache = null;
	/** Prepared Statement Cache. */
	private IStatementCache callableStatementCache = null;
	/** Logger handle. */
	private static Logger logger = Logger.getLogger(ConnectionHandle.class);
	/** An opaque handle for an application to use in any way it deems fit. */
	private Object debugHandle;

	/**
	 * List of statements that will be closed when this preparedStatement is
	 * logically closed.
	 */
	private ConcurrentLinkedQueue statementHandles = new ConcurrentLinkedQueue();
	/** Handle to the connection hook as defined in the config. */
	private ConnectionHook connectionHook;
	
	/**
	 * From: http://publib.boulder.ibm.com/infocenter/db2luw/v8/index.jsp?topic=/com.ibm.db2.udb.doc/core/r0sttmsg.htm
	 * Table 7. Class Code 08: Connection Exception
		SQLSTATE Value	  
		Value	Meaning
		08001	The application requester is unable to establish the connection.
		08002	The connection already exists.
		08003	The connection does not exist.
		08004	The application server rejected establishment of the connection.
		08007	Transaction resolution unknown.
		}
		08502	The CONNECT statement issued by an application process running with a SYNCPOINT of TWOPHASE has failed, because no transaction manager is available.
		08504	An error was encountered while processing the specified path rename configuration file.
	 SQL Failure codes indicating the database is broken/died (and thus kill off remaining connections). 
	  Anything else will be taken as the *connection* (not the db) being broken. 

	  08S01 is a mysql-specific code to indicate a connection failure (though in my tests it was triggered even when the entire
	  database was down so I treat that as a DB failure)
	 */
	private static final ImmutableSet sqlStateDBFailureCodes = ImmutableSet.of("08001", "08007", "08S01"); 
	/**
	 * Connection wrapper constructor
	 * 
	 * @param url
	 *            JDBC connection string
	 * @param username
	 *            user to use
	 * @param password
	 *            password for db
	 * @param pool
	 *            pool handle.
	 * @throws SQLException
	 *             on error
	 */
	public ConnectionHandle(String url, String username, String password,
			BoneCP pool) throws SQLException {
		
		this.pool = pool;

		try {
			this.connection = DriverManager.getConnection(url, username,
					password);

			int cacheSize = pool.getConfig().getPreparedStatementsCacheSize();
			if (cacheSize > 0) {
				this.preparedStatementCache = new StatementCache(cacheSize,  pool.getConfig().getStatementsCachedPerConnection());
				this.callableStatementCache = new StatementCache(cacheSize,  pool.getConfig().getStatementsCachedPerConnection());

			}
			// keep track of this hook.
			this.connectionHook = this.pool.getConfig().getConnectionHook();
			// call the hook, if available.
			if (this.connectionHook != null){
				this.connectionHook.onAcquire(this);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	/** Private constructor used solely for unit testing. 
	 * @param connection 
	 * @param preparedStatementCache 
	 * @param callableStatementCache 
	 * @param pool */
	public ConnectionHandle(Connection connection, IStatementCache preparedStatementCache, IStatementCache callableStatementCache, BoneCP pool){
		this.connection = connection;
		this.preparedStatementCache = preparedStatementCache;
		this.callableStatementCache = callableStatementCache;
		this.pool = pool;
	}


	/**
	 * Adds the given statement to a list.
	 * 
	 * @param statement
	 *            Statement to keep track of
	 * @return statement
	 */
	private Statement trackStatement(Statement statement) {
		if (statement != null){
			this.statementHandles.add(statement);
		}
		return statement;
	}

	/** 
	 * Given an exception, flag the connection (or database) as being potentially broken. If the exception is a data-specific exception,
	 * do nothing except throw it back to the application. 
	 * 
	 * @param t throwable exception to process
	 * @return SQLException for further processing
	 */
	protected SQLException markPossiblyBroken(Throwable t) {
		SQLException e;
		if (t instanceof SQLException){
			e=(SQLException) t;
		} else {
			e = new SQLException(t == null ? "Unknown error" : t.getMessage(), "08999");
			e.initCause( t );
		}

		String state = e.getSQLState();
		if (state == null){ // safety;
			state = "Z";
		}
		if (sqlStateDBFailureCodes.contains(state) && this.pool != null){
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
			logger.error("Database access problem. Killing off all remaining connections in the connection pool. SQL State = " + state);
			this.pool.terminateAllConnections();
		}

		// SQL-92 says:
		//		 Class values that begin with one of the s '5', '6', '7',
		//         '8', or '9' or one of the s 'I',
		//         'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
		//         'W', 'X', 'Y', or 'Z' are reserved for implementation-specified
		//         conditions.


		char firstChar = state.charAt(0);
		// if it's a communication exception or an implementation-specific error code, flag this connection as being potentially broken.
		if (state.startsWith("08") ||  (firstChar >= '5' && firstChar <='9') || (firstChar >='I' && firstChar <= 'Z')){
			this.possiblyBroken = true;
		}

		return e;
	}


	public void clearWarnings() throws SQLException {
		checkClosed();
		try {
			this.connection.clearWarnings();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	/**
	 * Checks if the connection is (logically) closed and throws an exception if it is.
	 * 
	 * @throws SQLException
	 *             on error
	 * 
	 * 
	 */
	private void checkClosed() throws SQLException {
		if (this.connectionClosed) {
			throw new SQLException("Connection is closed.");
		}
	}

	/**
	 * Release the connection if called. 
	 * 
	 * @throws SQLException Never really thrown
	 */
	public void close() throws SQLException {
		try {
			if (!this.connectionClosed) {
				this.connectionClosed = true;
				this.pool.releaseConnection(this);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	/**
	 * Close off the connection.
	 * 
	 * @throws SQLException
	 */
	protected void internalClose() throws SQLException {
		try {
			clearStatementHandles(true);
			this.connection.close();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void commit() throws SQLException {
		checkClosed();
		try {
			this.connection.commit();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public Array createArrayOf(String typeName, Object[] elements)
	throws SQLException {
		Array result = null;
		checkClosed();
		try {
			result = this.connection.createArrayOf(typeName, elements);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

		return result;
	}

	public Blob createBlob() throws SQLException {
		Blob result = null;
		checkClosed();
		try {
			result = this.connection.createBlob();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Clob createClob() throws SQLException {
		Clob result = null;
		checkClosed();
		try {
			result = this.connection.createClob();

		return result;

	}

	public NClob createNClob() throws SQLException {
		NClob result = null;
		checkClosed();
		try {
			result = this.connection.createNClob();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public SQLXML createSQLXML() throws SQLException {
		SQLXML result = null;
		checkClosed();
		try {
			result = this.connection.createSQLXML();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Statement createStatement() throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			result = trackStatement(new StatementHandle(this.connection.createStatement(), this));
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Statement createStatement(int resultSetType, int resultSetConcurrency)
	throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			result = trackStatement(new StatementHandle(this.connection.createStatement(resultSetType, resultSetConcurrency), this));
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Statement createStatement(int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
	throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			result = trackStatement(new StatementHandle(this.connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability), this));
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

		return result;
	}

	public Struct createStruct(String typeName, Object[] attributes)
	throws SQLException {
		Struct result = null;
		checkClosed();
		try {
			result = this.connection.createStruct(typeName, attributes);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean getAutoCommit() throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.getAutoCommit();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public String getCatalog() throws SQLException {
		String result = null;
		checkClosed();
		try {
			result = this.connection.getCatalog();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Properties getClientInfo() throws SQLException {
		Properties result = null;
		checkClosed();
		try {
			result = this.connection.getClientInfo();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public String getClientInfo(String name) throws SQLException {
		String result = null;
		checkClosed();
		try {
			result = this.connection.getClientInfo(name);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public int getHoldability() throws SQLException {
		int result = 0;
		checkClosed();
		try {
			result = this.connection.getHoldability();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}

		return result;
	}

	public DatabaseMetaData getMetaData() throws SQLException {
		DatabaseMetaData result = null;
		checkClosed();
		try {
			result = this.connection.getMetaData();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public int getTransactionIsolation() throws SQLException {
		int result = 0;
		checkClosed();
		try {
			result = this.connection.getTransactionIsolation();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Map> getTypeMap() throws SQLException {
		Map> result = null;
		checkClosed();
		try {
			result = this.connection.getTypeMap();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public SQLWarning getWarnings() throws SQLException {
		SQLWarning result = null;
		checkClosed();
		try {
			result = this.connection.getWarnings();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean isClosed() throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.isClosed();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean isReadOnly() throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.isReadOnly();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public boolean isValid(int timeout) throws SQLException {
		boolean result = false;
		checkClosed();
		try {
			result = this.connection.isValid(timeout);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public String nativeSQL(String sql) throws SQLException {
		String result = null;
		checkClosed();
		try {
			result = this.connection.nativeSQL(sql);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public CallableStatement prepareCall(String sql) throws SQLException {
		CallableStatement result = null;
		checkClosed();
		try {

			if (this.callableStatementCache != null) {
				result = (CallableStatement) this.callableStatementCache.get(sql);
				if (result == null) {
		}
					String cacheKey = sql;
					result = (CallableStatement) trackStatement(new CallableStatementHandle(this.connection.prepareCall(sql),
							sql, this.callableStatementCache, this, cacheKey));

				} 

				((CallableStatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareCall(sql);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public CallableStatement prepareCall(String sql, int resultSetType,
			int resultSetConcurrency) throws SQLException {
		CallableStatement result = null;
		checkClosed();
		try {
			if (this.callableStatementCache != null) {
				result = (CallableStatement) this.callableStatementCache.get(sql, resultSetType, resultSetConcurrency);

				if (result == null) {
					String cacheKey = this.callableStatementCache.calculateCacheKey(sql, resultSetType, resultSetConcurrency);
					result = (CallableStatement) trackStatement(new CallableStatementHandle(
							this.connection.prepareCall(sql, resultSetType, resultSetConcurrency), sql, this.callableStatementCache, this, cacheKey));

				} 

				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareCall(sql, resultSetType, resultSetConcurrency);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public CallableStatement prepareCall(String sql, int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
	throws SQLException {
		CallableStatement result = null;
		checkClosed();
		try {
			if (this.callableStatementCache != null) {

				result = (CallableStatement) this.callableStatementCache.get(sql);

				if (result == null) {
					result = (CallableStatement) trackStatement(new CallableStatementHandle(
							this.connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability), sql,
							this.callableStatementCache, this, sql));

				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareCall(sql, resultSetType,
						resultSetConcurrency, resultSetHoldability);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql) throws SQLException {
		Statement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = this.preparedStatementCache.get(sql);

				if (result == null) {

					String cacheKey = sql;

					result =  trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql), sql,
							this.preparedStatementCache, this, cacheKey));
				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return (PreparedStatement) result;
	}

	public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

			throw markPossiblyBroken(t);
				result = (PreparedStatement) this.preparedStatementCache.get(sql, autoGeneratedKeys);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, autoGeneratedKeys);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql,
									autoGeneratedKeys), sql,
									this.preparedStatementCache, this, cacheKey));
				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql,
						autoGeneratedKeys);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, columnIndexes);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, columnIndexes);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection
							.prepareStatement(sql, columnIndexes), sql,
							this.preparedStatementCache, this, cacheKey));
				}
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, columnIndexes);
			}
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, String[] columnNames)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, columnNames);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, columnNames);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql, columnNames),
							sql, this.preparedStatementCache, this, cacheKey));

				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, columnNames);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public PreparedStatement prepareStatement(String sql, int resultSetType,
			int resultSetConcurrency) throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, resultSetType, resultSetConcurrency);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, resultSetType, resultSetConcurrency);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql,
									resultSetType, resultSetConcurrency), sql,
									this.preparedStatementCache, this, cacheKey));
				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, resultSetType,
						resultSetConcurrency);
			}

		} catch (Throwable t) {
		return result;
	}

	public PreparedStatement prepareStatement(String sql, int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
	throws SQLException {
		PreparedStatement result = null;
		checkClosed();
		try {
			if (this.preparedStatementCache != null) {

				result = (PreparedStatement) this.preparedStatementCache.get(sql, resultSetType, resultSetConcurrency, resultSetHoldability);

				if (result == null) {
					String cacheKey = this.preparedStatementCache.calculateCacheKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability);

					result = (PreparedStatement) trackStatement(new PreparedStatementHandle(
							this.connection.prepareStatement(sql,
									resultSetType, resultSetConcurrency,
									resultSetHoldability), sql,
									this.preparedStatementCache, this, cacheKey));

				} 
				((StatementHandle)result).setLogicallyOpen();
			} else {
				result = this.connection.prepareStatement(sql, resultSetType,
						resultSetConcurrency, resultSetHoldability);
			}

		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public void releaseSavepoint(Savepoint savepoint) throws SQLException {
		checkClosed();
		try {
			this.connection.releaseSavepoint(savepoint);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);

		}
	}

	public void rollback() throws SQLException {
		checkClosed();
		try {
			this.connection.rollback();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void rollback(Savepoint savepoint) throws SQLException {
		checkClosed();
		try {
			this.connection.rollback(savepoint);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setAutoCommit(boolean autoCommit) throws SQLException {
		checkClosed();
		try {
			this.connection.setAutoCommit(autoCommit);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setCatalog(String catalog) throws SQLException {
		checkClosed();
		try {
			this.connection.setCatalog(catalog);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setClientInfo(Properties properties)
	throws SQLClientInfoException {
		this.connection.setClientInfo(properties);
	}

	public void setClientInfo(String name, String value)
	throws SQLClientInfoException {
		this.connection.setClientInfo(name, value);
	}

	public void setHoldability(int holdability) throws SQLException {
		checkClosed();
		try {
			this.connection.setHoldability(holdability);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setReadOnly(boolean readOnly) throws SQLException {
		checkClosed();
		try {
			this.connection.setReadOnly(readOnly);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public Savepoint setSavepoint() throws SQLException {
		checkClosed();
		Savepoint result = null;
		try {
			result = this.connection.setSavepoint();
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public Savepoint setSavepoint(String name) throws SQLException {
		checkClosed();
		Savepoint result = null;
		try {
			result = this.connection.setSavepoint(name);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
		return result;
	}

	public void setTransactionIsolation(int level) throws SQLException {
		checkClosed();
		try {
			this.connection.setTransactionIsolation(level);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public void setTypeMap(Map> map) throws SQLException {
		checkClosed();
		try {
			this.connection.setTypeMap(map);
		} catch (Throwable t) {
			throw markPossiblyBroken(t);
		}
	}

	public boolean isWrapperFor(Class iface) throws SQLException {
		return this.connection.isWrapperFor(iface);
	}

	public  T unwrap(Class iface) throws SQLException {
		return this.connection.unwrap(iface);
	}

	/**
	 * @return the connectionLastUsed
	 */
	public long getConnectionLastUsed() {
		return this.connectionLastUsed;
	}

	/**
	 * @param connectionLastUsed
	 *            the connectionLastUsed to set
	 */
	protected void setConnectionLastUsed(long connectionLastUsed) {
		this.connectionLastUsed = connectionLastUsed;
	}

	/**
	 * @return the connectionLastReset
	 */
	public long getConnectionLastReset() {
		return this.connectionLastReset;
	}

	/**
	 * @param connectionLastReset
	 *            the connectionLastReset to set
	 */
	protected void setConnectionLastReset(long connectionLastReset) {
		this.connectionLastReset = connectionLastReset;
	}

	/**
	 * Gets true if connection has triggered an exception at some point.
	 * @return true if the connection has triggered an error
	 */
	public boolean isPossiblyBroken() {
		return this.possiblyBroken;
	}


	/**
	 * Gets the partition this came from.
	 * 
	 * @return the partition this came from
	 */
	public ConnectionPartition getOriginatingPartition() {
		return this.originatingPartition;
	}

	/**
	 * Sets Originating partition
	 * 
	 * @param originatingPartition
	 *            to set
	 */
	protected void setOriginatingPartition(ConnectionPartition originatingPartition) {
		this.originatingPartition = originatingPartition;
	}

	/**
	 * Renews this connection, i.e. Sets this connection to be logically open
	 * (although it was never really closed)
	 */
	protected void renewConnection() {
		this.connectionClosed = false;
	}


	/** Clears out the statement handles.
	 * @param internalClose if true, close the inner statement handle too. 
	 * @throws SQLException
	 */
	protected void clearStatementHandles(boolean internalClose) throws SQLException {
		if (!internalClose){
			this.statementHandles.clear();
		} else{
			Statement statement = null;
			while ((statement = this.statementHandles.poll()) != null) {
				((StatementHandle) statement).internalClose();
			}
		}
	}

	/** Returns a debug handle as previously set by an application
	 * @return DebugHandle
	 */
	public Object getDebugHandle() {
		return this.debugHandle;
	}

	/** Sets a debugHandle, an object that is not used by the connection pool at all but may be set by an application to track
	 * this particular connection handle for any purpose it deems fit.
	 * @param debugHandle any object.
	 */
	public void setDebugHandle(Object debugHandle) {
		this.debugHandle = debugHandle;
	}

	/** Returns the internal connection as obtained via the JDBC driver.
	 * @return the raw connection
	 */
	public Connection getRawConnection() {
		return this.connection;
	}

	/** Returns the configured connection hook object.
	 * @return the connectionHook that was set in the config
	 */
	public ConnectionHook getConnectionHook() {
		return this.connectionHook;
	}
}
File
ConnectionHandle.java
Developer's decision
Version 2
Kind of conflict
Class declaration
Comment
Import
Package declaration
Chunk
Conflicting content
<<<<<<< HEAD
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Connection Partition structure
 * @author wwadge
 *
 */
public class ConnectionPartition {
    /**  Connections available to be taken  */
    private ArrayBlockingQueue freeConnections;
    /** When connections start running out, add these number of new connections. */
    private final int acquireIncrement;
    /** Minimum number of connections to start off with. */
    private final int minConnections;
    /** Maximum number of connections that will ever be created. */
    private final int maxConnections;
    /** almost full locking. **/
    private Lock almostFullLock = new ReentrantLock();
    /** Statistics lock. */
    private final ReentrantReadWriteLock statsLock = new ReentrantReadWriteLock();
    /** Signal mechanism. */
    private final Condition almostFull = this.almostFullLock.newCondition(); 
    /** Number of connections that have been created. */
    private int createdConnections=0;
    /** DB details. */
    private final String url;
    /** DB details. */
    private final String username;
    /** DB details. */
    private final String password;
    /** If set to true, don't bother calling method to attempt to create
        return result;
     * more connections because we've hit our limit. 
     */
    private volatile boolean unableToCreateMoreTransactions=false;
    /** Scratch queue of connections awaiting to be placed back in queue. */
    private ArrayBlockingQueue connectionsPendingRelease;
    /** Updates leased connections statistics
     * @param increment value to add/subtract
     */
    protected void updateCreatedConnections(int increment) {
        
    	try{
    		this.statsLock.writeLock().lock();
    		this.createdConnections+=increment;
    	} finally { 
    		this.statsLock.writeLock().unlock();
    	}
    }

    /**
     * Adds a free connection.
     *
     * @param connectionHandle
     */
    protected void addFreeConnection(ConnectionHandle connectionHandle){
        updateCreatedConnections(1);
        this.freeConnections.add(connectionHandle);
    }

    /**
     * @return the freeConnections
     */
    protected ArrayBlockingQueue getFreeConnections() {
        return this.freeConnections;
    }

    /**
     * @param freeConnections the freeConnections to set
     */
    protected void setFreeConnections(
            ArrayBlockingQueue freeConnections) {
        this.freeConnections = freeConnections;
    }


    /**
     * Partition constructor
     *
     * @param pool handle to connection pool
     */
    public ConnectionPartition(BoneCP pool) {
    	BoneCPConfig config = pool.getConfig();
        this.minConnections = config.getMinConnectionsPerPartition();
        this.maxConnections = config.getMaxConnectionsPerPartition();
        this.acquireIncrement = config.getAcquireIncrement();
        this.url = config.getJdbcUrl();
        this.username = config.getUsername();
        this.password = config.getPassword();
        this.connectionsPendingRelease = new ArrayBlockingQueue(this.maxConnections);

        /** Create a number of helper threads for connection release. */
        int helperThreads = config.getReleaseHelperThreads();
        if (helperThreads > 0) {
        	
            ExecutorService releaseHelper = Executors.newFixedThreadPool(helperThreads, new CustomThreadFactory("TinyCP-release-thread-helper-thread", true));
            pool.setReleaseHelper(releaseHelper); // keep a handle just in case
            
            for (int i = 0; i < helperThreads; i++) { 
            	// go through pool.getReleaseHelper() rather than releaseHelper directly to aid unit testing (i.e. mocking)
                pool.getReleaseHelper().execute(new ReleaseHelperThread(this.connectionsPendingRelease, pool));
            }
        }
    }

    /**
     * @return the acquireIncrement
     */
    protected int getAcquireIncrement() {
        return this.acquireIncrement;
    }

    /**
     * @return the minConnections
     */
    protected int getMinConnections() {
        return this.minConnections;
    }


    /**
     * @return the maxConnections
     */
    protected int getMaxConnections() {
        return this.maxConnections;
    }

    /**
     * @return the leasedConnections
     */
    protected int getCreatedConnections() {
        this.statsLock.readLock().lock();
        int result = this.createdConnections;
        this.statsLock.readLock().unlock();
    }

    /**
     * Returns the number of free slots in this partition. 
     *
     * @return number of free slots.
     */
    protected int getRemainingCapacity(){
        return this.freeConnections.remainingCapacity();
    }

    /**
     * @return the url
     */
    protected String getUrl() {
        return this.url;
    }


    /**
     * @return the username
     */
    protected String getUsername() {
        return this.username;
    }


    /**
     * @return the password
     */
    protected String getPassword() {
        return this.password;
    }


    /**
     * Returns true if we have created all the connections we can
     *
     * @return true if we have created all the connections we can
     */
    public boolean isUnableToCreateMoreTransactions() {
        return this.unableToCreateMoreTransactions;
    }


    /**
     * Sets connection creation possible status 
     *
     * @param unableToCreateMoreTransactions t/f
     */
    public void setUnableToCreateMoreTransactions(boolean unableToCreateMoreTransactions) {
        this.unableToCreateMoreTransactions = unableToCreateMoreTransactions;
    }


    /**
     * Gets handle to a release connection handle queue.
     *
     * @return release connection handle queue 
     */
    public ArrayBlockingQueue getConnectionsPendingRelease() {
        return this.connectionsPendingRelease;
    }

	/**
	 * Locks the almost full lock
	 */
	protected void lockAlmostFullLock() {
		this.almostFullLock.lock();
	}
	
	/**
	 * Unlocks the almost full lock
	 */
	protected void unlockAlmostFullLock() {
		this.almostFullLock.unlock();
	}

	/** Does _not_ loop for spurious interrupts etc
	 * @throws InterruptedException 
	 * 
	 */
	public void almostFullWait() throws InterruptedException {
		this.almostFull.await(); // callees must loop for spurious interrupts
	}

	/**
	 * signal handle
	 */
	public void almostFullSignal() {
		this.almostFull.signal();
	}

}
=======
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Connection Partition structure
 * @author wwadge
 *
 */
public class ConnectionPartition {
    /**  Connections available to be taken  */
    private ArrayBlockingQueue freeConnections;
    /** When connections start running out, add these number of new connections. */
    private final int acquireIncrement;
    /** Minimum number of connections to start off with. */
    private final int minConnections;
    /** Maximum number of connections that will ever be created. */
    private final int maxConnections;
    /** almost full locking. **/
    private Lock almostFullLock = new ReentrantLock();
    /** Statistics lock. */
    private final ReentrantReadWriteLock statsLock = new ReentrantReadWriteLock();
    /** Signal mechanism. */
    private final Condition almostFull = this.almostFullLock.newCondition(); 
    /** Number of connections that have been created. */
    private int createdConnections=0;
    /** DB details. */
    private final String url;
    /** DB details. */
    private final String username;
    /** DB details. */
    private final String password;
    /** If set to true, don't bother calling method to attempt to create
     * more connections because we've hit our limit. 
     */
    private volatile boolean unableToCreateMoreTransactions=false;
    /** Scratch queue of connections awaiting to be placed back in queue. */
    private ArrayBlockingQueue connectionsPendingRelease;
    /** Updates leased connections statistics
     * @param increment value to add/subtract
     */
    protected void updateCreatedConnections(int increment) {
        
    	try{
    		this.statsLock.writeLock().lock();
    		this.createdConnections+=increment;
    	} finally { 
    		this.statsLock.writeLock().unlock();
    	}
    }

    /**
     * Adds a free connection.
     *
     * @param connectionHandle
     */
    protected void addFreeConnection(ConnectionHandle connectionHandle){
        updateCreatedConnections(1);
        connectionHandle.setOriginatingPartition(this);
        this.freeConnections.add(connectionHandle);
    }

    /**
     * @return the freeConnections
     */
    protected ArrayBlockingQueue getFreeConnections() {
        return this.freeConnections;
    }

    /**
     * @param freeConnections the freeConnections to set
     */
    protected void setFreeConnections(
            ArrayBlockingQueue freeConnections) {
        this.freeConnections = freeConnections;
    }


    /**
     * Partition constructor
     *
     * @param pool handle to connection pool
     */
    public ConnectionPartition(BoneCP pool) {
    	BoneCPConfig config = pool.getConfig();
        this.minConnections = config.getMinConnectionsPerPartition();
        this.maxConnections = config.getMaxConnectionsPerPartition();
        this.acquireIncrement = config.getAcquireIncrement();
        this.url = config.getJdbcUrl();
        this.username = config.getUsername();
        this.password = config.getPassword();
        this.connectionsPendingRelease = new ArrayBlockingQueue(this.maxConnections);

        /** Create a number of helper threads for connection release. */
        int helperThreads = config.getReleaseHelperThreads();
        if (helperThreads > 0) {
        	
            ExecutorService releaseHelper = Executors.newFixedThreadPool(helperThreads, new CustomThreadFactory("TinyCP-release-thread-helper-thread", true));
            pool.setReleaseHelper(releaseHelper); // keep a handle just in case
            
            for (int i = 0; i < helperThreads; i++) { 
            	// go through pool.getReleaseHelper() rather than releaseHelper directly to aid unit testing (i.e. mocking)
                pool.getReleaseHelper().execute(new ReleaseHelperThread(this.connectionsPendingRelease, pool));
            }
        }
    }

    /**
     * @return the acquireIncrement
     */
    protected int getAcquireIncrement() {
        return this.acquireIncrement;
    }

    /**
     * @return the minConnections
     */
    protected int getMinConnections() {
        return this.minConnections;
    }


    /**
     * @return the maxConnections
     */
    protected int getMaxConnections() {
        return this.maxConnections;
    }

    /**
     * @return the leasedConnections
     */
    protected int getCreatedConnections() {
        this.statsLock.readLock().lock();
        int result = this.createdConnections;
        this.statsLock.readLock().unlock();
        return result;
    }

    /**
     * Returns the number of free slots in this partition. 
     *
     * @return number of free slots.
     */
    protected int getRemainingCapacity(){
        return this.freeConnections.remainingCapacity();
    }

    /**
     * @return the url
     */
    protected String getUrl() {
        return this.url;
    }


    /**
     * @return the username
     */
    protected String getUsername() {
        return this.username;
    }


    /**
     * @return the password
     */
    protected String getPassword() {
        return this.password;
    }


    /**
     * Returns true if we have created all the connections we can
     *

	/**
	 * Locks the almost full lock
     * @return true if we have created all the connections we can
     */
    public boolean isUnableToCreateMoreTransactions() {
        return this.unableToCreateMoreTransactions;
    }


    /**
     * Sets connection creation possible status 
     *
     * @param unableToCreateMoreTransactions t/f
     */
    public void setUnableToCreateMoreTransactions(boolean unableToCreateMoreTransactions) {
        this.unableToCreateMoreTransactions = unableToCreateMoreTransactions;
    }


    /**
     * Gets handle to a release connection handle queue.
     *
     * @return release connection handle queue 
     */
    public ArrayBlockingQueue getConnectionsPendingRelease() {
        return this.connectionsPendingRelease;
    }
	 */
	protected void lockAlmostFullLock() {
		this.almostFullLock.lock();
	}
	
	/**
	 * Unlocks the almost full lock
	 */
	protected void unlockAlmostFullLock() {
		this.almostFullLock.unlock();
	}

	/** Does _not_ loop for spurious interrupts etc
	 * @throws InterruptedException 
	 * 
	 */
	public void almostFullWait() throws InterruptedException {
		this.almostFull.await(); // callees must loop for spurious interrupts
	}

	/**
	 * signal handle
	 */
	public void almostFullSignal() {
		this.almostFull.signal();
	}

}
>>>>>>> d7a6bd4eb2ee52d1e4106dadf6c5f63704d5d659
Solution content
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;
    /** DB details. */
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Connection Partition structure
 * @author wwadge
 *
 */
public class ConnectionPartition {
    /**  Connections available to be taken  */
    private ArrayBlockingQueue freeConnections;
    /** When connections start running out, add these number of new connections. */
    private final int acquireIncrement;
    /** Minimum number of connections to start off with. */
    private final int minConnections;
    /** Maximum number of connections that will ever be created. */
    private final int maxConnections;
    /** almost full locking. **/
    private Lock almostFullLock = new ReentrantLock();
    /** Statistics lock. */
    private final ReentrantReadWriteLock statsLock = new ReentrantReadWriteLock();
    /** Signal mechanism. */
    private final Condition almostFull = this.almostFullLock.newCondition(); 
    /** Number of connections that have been created. */
    private int createdConnections=0;
    /** DB details. */
    private final String url;
    /** DB details. */
    private final String username;
    private final String password;
    /** If set to true, don't bother calling method to attempt to create
     * more connections because we've hit our limit. 
     */
    private volatile boolean unableToCreateMoreTransactions=false;
    /** Scratch queue of connections awaiting to be placed back in queue. */
    private ArrayBlockingQueue connectionsPendingRelease;
    /** Updates leased connections statistics
     * @param increment value to add/subtract
     */
    protected void updateCreatedConnections(int increment) {
        
    	try{
    		this.statsLock.writeLock().lock();
    		this.createdConnections+=increment;
    	} finally { 
    		this.statsLock.writeLock().unlock();
    	}
    }

    /**
     * Adds a free connection.
     *
     * @param connectionHandle
     */
    protected void addFreeConnection(ConnectionHandle connectionHandle){
        updateCreatedConnections(1);
        connectionHandle.setOriginatingPartition(this);
        this.freeConnections.add(connectionHandle);
    }

    /**
     * @return the freeConnections
     */
    protected ArrayBlockingQueue getFreeConnections() {
        return this.freeConnections;
    }

    /**
     * @param freeConnections the freeConnections to set
     */
    protected void setFreeConnections(
            ArrayBlockingQueue freeConnections) {
        this.freeConnections = freeConnections;
    }


    /**
     * Partition constructor
     *
     * @param pool handle to connection pool
     */
    public ConnectionPartition(BoneCP pool) {
    	BoneCPConfig config = pool.getConfig();
        this.minConnections = config.getMinConnectionsPerPartition();
        this.maxConnections = config.getMaxConnectionsPerPartition();
        this.acquireIncrement = config.getAcquireIncrement();
        this.url = config.getJdbcUrl();
        this.username = config.getUsername();
        this.password = config.getPassword();
        this.connectionsPendingRelease = new ArrayBlockingQueue(this.maxConnections);

        /** Create a number of helper threads for connection release. */
        int helperThreads = config.getReleaseHelperThreads();
        if (helperThreads > 0) {
        	
            ExecutorService releaseHelper = Executors.newFixedThreadPool(helperThreads, new CustomThreadFactory("TinyCP-release-thread-helper-thread", true));
	}
            pool.setReleaseHelper(releaseHelper); // keep a handle just in case
            
            for (int i = 0; i < helperThreads; i++) { 
            	// go through pool.getReleaseHelper() rather than releaseHelper directly to aid unit testing (i.e. mocking)
                pool.getReleaseHelper().execute(new ReleaseHelperThread(this.connectionsPendingRelease, pool));
            }
        }
    }

    /**
     * @return the acquireIncrement
     */
    protected int getAcquireIncrement() {
        return this.acquireIncrement;
    }

    /**
     * @return the minConnections
     */
    protected int getMinConnections() {
        return this.minConnections;
    }


    /**
     * @return the maxConnections
     */
    protected int getMaxConnections() {
        return this.maxConnections;
    }

    /**
     * @return the leasedConnections
     */
    protected int getCreatedConnections() {
        this.statsLock.readLock().lock();
        int result = this.createdConnections;
        this.statsLock.readLock().unlock();
        return result;
    }

    /**
     * Returns the number of free slots in this partition. 
     *
     * @return number of free slots.
     */
    protected int getRemainingCapacity(){
        return this.freeConnections.remainingCapacity();
    }

    /**
     * @return the url
     */
    protected String getUrl() {
        return this.url;
    }


    /**
     * @return the username
     */
    protected String getUsername() {
        return this.username;
    }


    /**
     * @return the password
     */
    protected String getPassword() {
        return this.password;
    }


    /**
     * Returns true if we have created all the connections we can
     *
     * @return true if we have created all the connections we can
     */
    public boolean isUnableToCreateMoreTransactions() {
        return this.unableToCreateMoreTransactions;
    }


    /**
     * Sets connection creation possible status 
     *
     * @param unableToCreateMoreTransactions t/f
     */
    public void setUnableToCreateMoreTransactions(boolean unableToCreateMoreTransactions) {
        this.unableToCreateMoreTransactions = unableToCreateMoreTransactions;
    }


    /**
     * Gets handle to a release connection handle queue.
     *
     * @return release connection handle queue 
     */
    public ArrayBlockingQueue getConnectionsPendingRelease() {
        return this.connectionsPendingRelease;
    }

	/**
	 * Locks the almost full lock
	 */
	protected void lockAlmostFullLock() {
		this.almostFullLock.lock();
	}
	
	/**
	 * Unlocks the almost full lock
	 */
	protected void unlockAlmostFullLock() {
		this.almostFullLock.unlock();
	/** Does _not_ loop for spurious interrupts etc
	 * @throws InterruptedException 
	 * 
	 */
	public void almostFullWait() throws InterruptedException {
		this.almostFull.await(); // callees must loop for spurious interrupts
	}

	/**
	 * signal handle
	 */
	public void almostFullSignal() {
		this.almostFull.signal();
	}

}
File
ConnectionPartition.java
Developer's decision
Version 2
Kind of conflict
Class declaration
Comment
Import
Package declaration
Chunk
Conflicting content
<<<<<<< HEAD
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.sql.SQLException;
import java.util.concurrent.ScheduledExecutorService;

import org.apache.log4j.Logger;

/**
 * Periodically sends a keep-alive statement to idle threads
 * and kills off any connections that have been unused for a long time (or broken).
 * @author wwadge
 *
 */
public class ConnectionTesterThread implements Runnable {

	/** Connections used less than this time ago are not keep-alive tested. */
	private long idleConnectionTestPeriod;
	/** Max no of ms to wait before a connection that isn't used is killed off. */
	private long idleMaxAge;
	/** Partition being handled. */
	private ConnectionPartition partition;
	/** Scheduler handle. **/
	private ScheduledExecutorService scheduler;
	/** Handle to connection pool. */
    private BoneCP pool;
    /** Logger handle. */
    private static Logger logger = Logger.getLogger(ConnectionTesterThread.class);
    
	/** Constructor
	 * @param connectionPartition partition to work on
	 * @param scheduler Scheduler handler.
	 * @param pool 
	 */
	public ConnectionTesterThread(ConnectionPartition connectionPartition, ScheduledExecutorService scheduler, BoneCP pool){
		this.partition = connectionPartition;
		this.scheduler = scheduler;
		this.idleMaxAge = pool.getConfig().getIdleMaxAge();
		this.idleConnectionTestPeriod = pool.getConfig().getIdleConnectionTestPeriod();
		this.pool = pool; 
	}


	/** Invoked periodically. */
	public void run() {
		ConnectionHandle connection = null;
	
		
		try {
			int partitionSize= this.partition.getFreeConnections().size();
			for (int i=0; i < partitionSize; i++){
			 
				connection = this.partition.getFreeConnections().poll();
				if (connection != null){
					if (connection.isPossiblyBroken() || 
	 * @param connectionPartition partition to work on
							((this.idleMaxAge > 0) && (this.partition.getFreeConnections().size() >= this.partition.getMinConnections() && System.currentTimeMillis()-connection.getConnectionLastUsed() > this.idleMaxAge))){
						// kill off this connection
						closeConnection(connection);
						continue;
					}

					if (this.idleConnectionTestPeriod > 0 && (System.currentTimeMillis()-connection.getConnectionLastUsed() > this.idleConnectionTestPeriod) &&
							(System.currentTimeMillis()-connection.getConnectionLastReset() > this.idleConnectionTestPeriod)) {
					   
					    // send a keep-alive, close off connection if we fail.
						if (!this.pool.isConnectionHandleAlive(connection)){
						    closeConnection(connection);
						    continue; 
						}
						connection.setConnectionLastReset(System.currentTimeMillis());
					}

				    this.pool.releaseInAnyFreePartition(connection, this.partition);
				    
					Thread.sleep(20L); // test slowly, this is not an operation that we're in a hurry to deal with...
				}

			} // throw it back on the queue
		} catch (InterruptedException e) {
			if (this.scheduler.isShutdown()){
			    logger.debug("Shutting down connection tester");
				closeConnection(connection);
			} else {
				logger.error(e);
			}
		}
	}


	/** Closes off this connection
	 * @param connection to close
	 */
	private void closeConnection(ConnectionHandle connection) {
		if (connection != null) {
			try {
				connection.internalClose();
				this.partition.setUnableToCreateMoreTransactions(false);
				this.partition.updateCreatedConnections(-1);
			} catch (SQLException e) {
				logger.error(e);
			}
		}
	}

	

}
=======
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.sql.SQLException;
import java.util.concurrent.ScheduledExecutorService;

import org.apache.log4j.Logger;

/**
 * Periodically sends a keep-alive statement to idle threads
 * and kills off any connections that have been unused for a long time (or broken).
 * @author wwadge
 *
 */
public class ConnectionTesterThread implements Runnable {

	/** Connections used less than this time ago are not keep-alive tested. */
	private long idleConnectionTestPeriod;
	/** Max no of ms to wait before a connection that isn't used is killed off. */
	private long idleMaxAge;
	/** Partition being handled. */
	private ConnectionPartition partition;
	/** Scheduler handle. **/
	private ScheduledExecutorService scheduler;
	/** Handle to connection pool. */
    private BoneCP pool;
    /** Logger handle. */
    private static Logger logger = Logger.getLogger(ConnectionTesterThread.class);
    
	/** Constructor
	 * @param scheduler Scheduler handler.
	 * @param pool 
	 */
	public ConnectionTesterThread(ConnectionPartition connectionPartition, ScheduledExecutorService scheduler, BoneCP pool){
		this.partition = connectionPartition;
		this.scheduler = scheduler;
		this.idleMaxAge = pool.getConfig().getIdleMaxAge();
		this.idleConnectionTestPeriod = pool.getConfig().getIdleConnectionTestPeriod();
		this.pool = pool; 
	}


	/** Invoked periodically. */
	public void run() {
		ConnectionHandle connection = null;
	
		
		try {
			int partitionSize= this.partition.getFreeConnections().size();
			for (int i=0; i < partitionSize; i++){
			 
				connection = this.partition.getFreeConnections().poll();
				if (connection != null){
					if (connection.isPossiblyBroken() || 
							((this.idleMaxAge > 0) && (this.partition.getFreeConnections().size() >= this.partition.getMinConnections() && System.currentTimeMillis()-connection.getConnectionLastUsed() > this.idleMaxAge))){
						// kill off this connection
						closeConnection(connection);
						continue;
					}

					if (this.idleConnectionTestPeriod > 0 && (System.currentTimeMillis()-connection.getConnectionLastUsed() > this.idleConnectionTestPeriod) &&
							(System.currentTimeMillis()-connection.getConnectionLastReset() > this.idleConnectionTestPeriod)) {
					   
					    // send a keep-alive, close off connection if we fail.
						if (!this.pool.isConnectionHandleAlive(connection)){
						    closeConnection(connection);
						    continue; 
						}
						connection.setConnectionLastReset(System.currentTimeMillis());
					}

				    this.pool.releaseInAnyFreePartition(connection, this.partition);
				    
					Thread.sleep(20L); // test slowly, this is not an operation that we're in a hurry to deal with...
				}

			} // throw it back on the queue
		} catch (InterruptedException e) {
			if (this.scheduler.isShutdown()){
			    logger.debug("Shutting down connection tester");
				closeConnection(connection);
			} else {
				logger.error(e);
			}
		}
	}


	/** Closes off this connection
	 * @param connection to close
	 */
	private void closeConnection(ConnectionHandle connection) {
		if (connection != null) {
			try {
				connection.internalClose();
				this.pool.postDestroyConnection(connection);
				
			} catch (SQLException e) {
				logger.error(e);
			}
		}
	}

	

}
>>>>>>> d7a6bd4eb2ee52d1e4106dadf6c5f63704d5d659
Solution content
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;

import java.sql.SQLException;
import java.util.concurrent.ScheduledExecutorService;

import org.apache.log4j.Logger;

/**
 * Periodically sends a keep-alive statement to idle threads
 * and kills off any connections that have been unused for a long time (or broken).
 * @author wwadge
 *
 */
public class ConnectionTesterThread implements Runnable {

	/** Connections used less than this time ago are not keep-alive tested. */
	private long idleConnectionTestPeriod;
	/** Max no of ms to wait before a connection that isn't used is killed off. */
	private long idleMaxAge;
	/** Partition being handled. */
	private ConnectionPartition partition;
	/** Scheduler handle. **/
	private ScheduledExecutorService scheduler;
	/** Handle to connection pool. */
    private BoneCP pool;
    /** Logger handle. */
    private static Logger logger = Logger.getLogger(ConnectionTesterThread.class);
    
	/** Constructor
	 * @param connectionPartition partition to work on
	 * @param scheduler Scheduler handler.
	 * @param pool 
	 */
	public ConnectionTesterThread(ConnectionPartition connectionPartition, ScheduledExecutorService scheduler, BoneCP pool){
		this.partition = connectionPartition;
		this.scheduler = scheduler;
		this.idleMaxAge = pool.getConfig().getIdleMaxAge();
		this.idleConnectionTestPeriod = pool.getConfig().getIdleConnectionTestPeriod();
		this.pool = pool; 
	}


	/** Invoked periodically. */
	public void run() {
		ConnectionHandle connection = null;
	
		
		try {
			int partitionSize= this.partition.getFreeConnections().size();
			for (int i=0; i < partitionSize; i++){
			 
				connection = this.partition.getFreeConnections().poll();
				if (connection != null){
					if (connection.isPossiblyBroken() || 
							((this.idleMaxAge > 0) && (this.partition.getFreeConnections().size() >= this.partition.getMinConnections() && System.currentTimeMillis()-connection.getConnectionLastUsed() > this.idleMaxAge))){
						// kill off this connection
						closeConnection(connection);
						continue;
					}

					if (this.idleConnectionTestPeriod > 0 && (System.currentTimeMillis()-connection.getConnectionLastUsed() > this.idleConnectionTestPeriod) &&
							(System.currentTimeMillis()-connection.getConnectionLastReset() > this.idleConnectionTestPeriod)) {
					   
					    // send a keep-alive, close off connection if we fail.
						if (!this.pool.isConnectionHandleAlive(connection)){
						    closeConnection(connection);
						    continue; 
						}
						connection.setConnectionLastReset(System.currentTimeMillis());
					}

				    this.pool.releaseInAnyFreePartition(connection, this.partition);
				    
					Thread.sleep(20L); // test slowly, this is not an operation that we're in a hurry to deal with...
				}

			} // throw it back on the queue
		} catch (InterruptedException e) {
			if (this.scheduler.isShutdown()){
			    logger.debug("Shutting down connection tester");
				closeConnection(connection);
			} else {
				logger.error(e);
			}
		}
	}


	/** Closes off this connection
	 * @param connection to close
	 */
	private void closeConnection(ConnectionHandle connection) {
		if (connection != null) {
			try {
				connection.internalClose();
				this.pool.postDestroyConnection(connection);
				
			} catch (SQLException e) {
				logger.error(e);
			}
		}
	}

	

}
File
ConnectionTesterThread.java
Developer's decision
Version 2
Kind of conflict
Class declaration
Comment
Import
Package declaration
Chunk
Conflicting content
<<<<<<< HEAD
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;


import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.aryEq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.makeThreadSafe;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.reset;
import static org.easymock.classextension.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import junit.framework.Assert;

import org.apache.log4j.Logger;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Mock unit testing for Connection Handle class.
 * @author wwadge
 *
 */
public class TestConnectionHandle {
	/** Test class handle. */
	private static ConnectionHandle testClass;
	/** Mock handle. */
	private static ConnectionHandle mockConnection;
	/** Mock handle. */
	private static IStatementCache mockPreparedStatementCache;
	/** Mock handle. */
	private static IStatementCache mockCallableStatementCache;
	/** Mock handle. */
	private static BoneCP mockPool;
	/** Mock handle. */
	private static Logger mockLogger;
	/** Mock handle. */
	private static StatementCache testStatementCache;


	/** Mock setup.
	 * @throws Exception
	 */
	@BeforeClass
	public static void setUp() throws Exception {
		mockConnection = createNiceMock(ConnectionHandle.class);
		mockPreparedStatementCache = createNiceMock(IStatementCache.class);
		mockCallableStatementCache = createNiceMock(IStatementCache.class);

		mockLogger = createNiceMock(Logger.class);
		makeThreadSafe(mockLogger, true);
		mockPool = createNiceMock(BoneCP.class);
		testClass = new ConnectionHandle(mockConnection, mockPreparedStatementCache, mockCallableStatementCache, mockPool);
		testStatementCache = new StatementCache(100, 100);
		Field field = testClass.getClass().getDeclaredField("logger");
		field.setAccessible(true);
		field.set(null, mockLogger);
	}


	/** Reset everything.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@Before
	public void before() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		reset(mockConnection, mockPreparedStatementCache, mockPool);
		Field field = testClass.getClass().getDeclaredField("connectionClosed");
		field.setAccessible(true);
		field.set(testClass, false);

	}

	/** Test bounce of inner connection.
	 * @throws IllegalArgumentException
	 * @throws SecurityException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	@Test
	public void testStandardMethods() throws IllegalArgumentException, SecurityException,  IllegalAccessException, InvocationTargetException{
		Set skipTests = new HashSet();
		skipTests.add("close");
		skipTests.add("getConnection");
		skipTests.add("markPossiblyBroken");
		skipTests.add("trackStatement");
		skipTests.add("checkClosed");
		skipTests.add("internalClose");
		skipTests.add("prepareCall");
		skipTests.add("prepareStatement");
		skipTests.add("setClientInfo");
		skipTests.add("getConnectionLastUsed");
		skipTests.add("setConnectionLastUsed");
		skipTests.add("getConnectionLastReset");	
		skipTests.add("setConnectionLastReset");
		skipTests.add("isPossiblyBroken");	
		skipTests.add("getOriginatingPartition");
		skipTests.add("setOriginatingPartition");
		skipTests.add("renewConnection");
		skipTests.add("clearStatementHandles");
		skipTests.add("$VRi"); // this only comes into play when code coverage is started. Eclemma bug?

		CommonTestUtils.testStatementBounceMethod(mockConnection, testClass, skipTests, mockConnection);
	}

	/** Test marking of possibly broken status.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@Test
	public void testMarkPossiblyBroken() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		Field field = testClass.getClass().getDeclaredField("possiblyBroken");
		field.setAccessible(true);
		field.set(testClass, false);
		testClass.markPossiblyBroken(null);
		Assert.assertTrue(field.getBoolean(testClass));

		// Test that a db fatal error will lead to the pool being instructed to terminate all connections (+ log)
		mockPool.terminateAllConnections();
		mockLogger.error(anyObject());
		replay(mockPool);
		testClass.markPossiblyBroken(new SQLException("test", "08001"));
		verify(mockPool);


	}

	/** Closing a connection handle should release that connection back in the pool and mark it as closed.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SQLException
	 */
	@Test 
	public void testClose() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SQLException{

		testClass.renewConnection();
		mockPool.releaseConnection((Connection)anyObject());
		expectLastCall().once().andThrow(new SQLException()).once();
		replay(mockPool);

		testClass.close();


		// logically mark the connection as closed
		Field field = testClass.getClass().getDeclaredField("connectionClosed");

		field.setAccessible(true);
		Assert.assertTrue(field.getBoolean(testClass));

		testClass.renewConnection();
		try{
			testClass.close(); // 2nd time should throw an exception
			fail("Should have thrown an exception");
		} catch (Throwable t){
			// do nothing.
		}
		verify(mockPool);
	}

	/** Closing a connection handle should release that connection back in the pool and mark it as closed.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SQLException
	 */
	@SuppressWarnings("unchecked")
	@Test 
	public void testInternalClose() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SQLException{
		ConcurrentLinkedQueue mockStatementHandles = createNiceMock(ConcurrentLinkedQueue.class);
		StatementHandle mockStatement = createNiceMock(StatementHandle.class);
		Field field = testClass.getClass().getDeclaredField("statementHandles");

		field.setAccessible(true);
		field.set(testClass, mockStatementHandles); 

		expect(mockStatementHandles.poll()).andReturn(mockStatement).once().andReturn(null).once();
		mockStatement.internalClose();
		expectLastCall().once();
		mockConnection.close();
		expectLastCall().once().andThrow(new SQLException()).once();
		replay(mockStatement, mockConnection, mockStatementHandles);
		testClass.internalClose();
		try{
			testClass.internalClose(); //2nd time should throw exception
			fail("Should have thrown an exception");
		} catch (Throwable t){
			// do nothing.
		}

		verify(mockStatement, mockConnection, mockStatementHandles);
	}


	/** Test for check closed routine.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 */
	@Test 
	public void testCheckClosed() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{

		testClass.renewConnection();

		// call the method (should not throw an exception)
		Method method = testClass.getClass().getDeclaredMethod("checkClosed");
		method.setAccessible(true);
		method.invoke(testClass);

		// logically mark the connection as closed
		Field field = testClass.getClass().getDeclaredField("connectionClosed");

		field.setAccessible(true);
		field.set(testClass, true);
		try{
			method.invoke(testClass);
			fail("Should have thrown an exception");
		} catch (Throwable t){
			// do nothing.
		}
	}

	/** Test renewal of connection.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@Test
	public void testRenewConnection() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		Field field = testClass.getClass().getDeclaredField("connectionClosed");
		field.setAccessible(true);
		field.set(testClass, true);

		testClass.renewConnection();
		assertFalse(field.getBoolean(testClass));


	}
	/** Tests various getter/setters.
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 */
	@Test
	public void testSettersGetters() throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException {
		ConnectionPartition mockPartition = createNiceMock(ConnectionPartition.class);
		testClass.setOriginatingPartition(mockPartition);
		assertEquals(mockPartition, testClass.getOriginatingPartition());

		testClass.setConnectionLastReset(123);
		assertEquals(testClass.getConnectionLastReset(), 123);

		testClass.setConnectionLastUsed(456);
		assertEquals(testClass.getConnectionLastUsed(), 456);

		Field field = testClass.getClass().getDeclaredField("possiblyBroken");
		field.setAccessible(true);
		field.setBoolean(testClass, true);
		assertTrue(testClass.isPossiblyBroken());
		
		Object debugHandle = new Object();
		testClass.setDebugHandle(debugHandle);
		assertEquals(debugHandle, testClass.getDebugHandle());
	}

	/** Prepare statement tests.
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@Test
	public void testPrepareStatement() throws SecurityException, IllegalArgumentException,  NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		prepareStatementTest(String.class);
		prepareStatementTest(String.class, int.class);
		prepareStatementTest(String.class, int[].class);
		prepareStatementTest(String.class, String[].class);
		prepareStatementTest(String.class, int.class, int.class);
		prepareStatementTest(String.class, int.class, int.class, int.class);
	}

	/** Callable statement tests.
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@Test
	public void testCallableStatement() throws SecurityException, IllegalArgumentException,  NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		callableStatementTest(String.class);
		callableStatementTest(String.class, int.class, int.class);
		callableStatementTest(String.class, int.class, int.class, int.class);
	}


	/** Test routine.
	 * @param args
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@SuppressWarnings("unchecked")
	private void prepareStatementTest(Class... args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		Object[] params = new Object[args.length];
		for (int i=0; i < args.length; i++){
			params[i] = CommonTestUtils.instanceMap.get(args[i]);
		}

		Method prepStatementMethod = testClass.getClass().getMethod("prepareStatement", args);

		PreparedStatementHandle mockStatement = createNiceMock(PreparedStatementHandle.class);
		ConcurrentLinkedQueue mockStatementHandles = createNiceMock(ConcurrentLinkedQueue.class);
		Field field = testClass.getClass().getDeclaredField("statementHandles");
		field.setAccessible(true);
		field.set(testClass, mockStatementHandles);

		testClass.renewConnection(); // logically open the connection

		// fetching a statement that is found in cache. Statement should be returned and marked as being (logically) open
		doStatementMock(mockPreparedStatementCache, mockStatement, params, args);

		mockStatement.setLogicallyOpen();
		expectLastCall();
		replay(mockStatement, mockPreparedStatementCache);
		prepStatementMethod.invoke(testClass, params);
		verify(mockStatement, mockPreparedStatementCache);

		reset(mockStatement, mockPreparedStatementCache);


		// test for a cache miss
		doStatementMock(mockPreparedStatementCache, null, params, args);
		// we should be creating the preparedStatement because it's not in the cache
		expect(prepStatementMethod.invoke(mockConnection, params)).andReturn(mockStatement);
		// we should be tracking this statement
		expect(mockStatementHandles.add(mockStatement)).andReturn(true);

		replay(mockStatement, mockPreparedStatementCache, mockConnection);
		prepStatementMethod.invoke(testClass, params);
		verify(mockStatement, mockPreparedStatementCache, mockConnection);


		// test for cache miss + sql exception
		reset(mockStatement, mockPreparedStatementCache, mockConnection);

		Method mockConnectionPrepareStatementMethod = mockConnection.getClass().getMethod("prepareStatement", args);

		//		expect(mockPreparedStatementCache.get((String)anyObject())).andReturn(null).once();
		doStatementMock(mockPreparedStatementCache, null, params, args);

		// we should be trying to create the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareStatementMethod.invoke(mockConnection, params)).andThrow(new SQLException("test", "Z"));

		replay(mockStatement, mockPreparedStatementCache, mockConnection);
		try{
			prepStatementMethod.invoke(testClass, params);
			fail("Should have thrown an exception");
		} catch (Throwable t) {
			// do nothing
		}

		verify(mockStatement, mockPreparedStatementCache, mockConnection);



		// test for no cache defined
		reset(mockStatement, mockPreparedStatementCache, mockConnection);
		field = testClass.getClass().getDeclaredField("preparedStatementCache");
		field.setAccessible(true);
		IStatementCache oldCache = (IStatementCache) field.get(testClass);
		field.set(testClass, null);


		// we should be creating the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareStatementMethod.invoke(mockConnection, params)).andReturn(mockStatement);

		replay(mockStatement, mockPreparedStatementCache, mockConnection);
		prepStatementMethod.invoke(testClass, params);
		verify(mockStatement, mockPreparedStatementCache, mockConnection);
		// restore sanity
		field.set(testClass, oldCache);

		reset(mockStatement, mockPreparedStatementCache, mockConnection);

	}

	/** Mock setup.
	 * @param cache 
	 * @param returnVal 
	 * @param params
	 * @param args
	 */
	private void doStatementMock(IStatementCache cache, Statement returnVal, Object[] params, Class... args) {
		expect(cache.get((String)anyObject())).andReturn(returnVal).anyTimes();
//		expect(cache.calculateCacheKey((String)anyObject())).andReturn(testStatementCache.calculateCacheKey((String)params[0])).anyTimes();

		if (args.length == 2) {
			if (params[1].getClass().equals(Integer.class)){

				expect(cache.calculateCacheKey((String)anyObject(), anyInt())).andReturn(testStatementCache.calculateCacheKey((String)params[0], (Integer)params[1])).anyTimes();
				expect(cache.get((String)anyObject(), anyInt())).andReturn(returnVal).anyTimes();
			}			
			expect(cache.calculateCacheKey((String)anyObject(), aryEq(new int[]{0,1}))).andReturn(testStatementCache.calculateCacheKey((String)params[0], new int[]{0,1})).anyTimes();
			expect(cache.get((String)anyObject(), aryEq(new int[]{0,1}))).andReturn(returnVal).anyTimes();

			expect(cache.calculateCacheKey((String)anyObject(), (String[])anyObject())).andReturn(testStatementCache.calculateCacheKey((String)params[0], new String[]{"test", "bar"})).anyTimes();
			expect(cache.get((String)anyObject(), (String[])anyObject())).andReturn(returnVal).anyTimes();
		}
		if (args.length == 3) {
			expect(cache.calculateCacheKey((String)anyObject(), anyInt(), anyInt())).andReturn(testStatementCache.calculateCacheKey((String)params[0], (Integer)params[1], (Integer)params[2])).anyTimes();
			expect(cache.get((String)anyObject(), anyInt(), anyInt())).andReturn(returnVal).anyTimes();
		}
		if (args.length == 4) {
			expect(cache.calculateCacheKey((String)anyObject(), anyInt(), anyInt(), anyInt())).andReturn(testStatementCache.calculateCacheKey((String)params[0], (Integer)params[1], (Integer)params[2], (Integer)params[3])).anyTimes();
			expect(cache.get((String)anyObject(), anyInt(), anyInt(), anyInt())).andReturn(returnVal).anyTimes();
		}

	}

	/** Test routine for callable statements.
	 * @param args
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@SuppressWarnings("unchecked")
	private void callableStatementTest(Class... args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		Object[] params = new Object[args.length];
		for (int i=0; i < args.length; i++){
			params[i] = CommonTestUtils.instanceMap.get(args[i]);
		}

		Method prepCallMethod = testClass.getClass().getMethod("prepareCall", args);

		CallableStatementHandle mockStatement = createNiceMock(CallableStatementHandle.class);
		ConcurrentLinkedQueue mockStatementHandles = createNiceMock(ConcurrentLinkedQueue.class);
		Field field = testClass.getClass().getDeclaredField("statementHandles");
		field.setAccessible(true);
		field.set(testClass, mockStatementHandles);

		testClass.renewConnection(); // logically open the connection

		// fetching a statement that is found in cache. Statement should be returned and marked as being (logically) open
		doStatementMock(mockCallableStatementCache, mockStatement, params, args);

//		expect(mockCallableStatementCache.get((String)anyObject())).andReturn(mockStatement).anyTimes();

		//	((StatementHandle)mockStatement).setLogicallyOpen();
		expectLastCall();
		replay(mockStatement, mockCallableStatementCache);
		prepCallMethod.invoke(testClass, params);
		verify(mockStatement, mockCallableStatementCache);

		reset(mockStatement, mockCallableStatementCache);


		// test for a cache miss
		doStatementMock(mockCallableStatementCache, null, params, args);
//		expect(mockCallableStatementCache.get((String)anyObject())).andReturn(null).once();

		// we should be creating the preparedStatement because it's not in the cache
		expect(prepCallMethod.invoke(mockConnection, params)).andReturn(mockStatement);
		// we should be tracking this statement
		expect(mockStatementHandles.add(mockStatement)).andReturn(true);

		replay(mockStatement, mockCallableStatementCache, mockConnection);
		prepCallMethod.invoke(testClass, params);
		verify(mockStatement, mockCallableStatementCache, mockConnection);


		// test for cache miss + sql exception
		reset(mockStatement, mockCallableStatementCache, mockConnection);

		Method mockConnectionPrepareCallMethod = mockConnection.getClass().getMethod("prepareCall", args);

//		expect(mockCallableStatementCache.get((String)anyObject())).andReturn(null).once();
		doStatementMock(mockCallableStatementCache, null, params, args);
	
		// we should be creating the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareCallMethod.invoke(mockConnection, params)).andThrow(new SQLException("test", "Z"));

		replay(mockStatement, mockCallableStatementCache, mockConnection);
		try{
			prepCallMethod.invoke(testClass, params);
			fail("Should have thrown an exception");
		} catch (Throwable t) {
			// do nothing
		}

		verify(mockStatement, mockCallableStatementCache, mockConnection);



		// test for no cache defined
		reset(mockStatement, mockCallableStatementCache, mockConnection);
		field = testClass.getClass().getDeclaredField("callableStatementCache");
		field.setAccessible(true);
		IStatementCache oldCache = (IStatementCache) field.get(testClass);
		field.set(testClass, null);


		// we should be creating the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareCallMethod.invoke(mockConnection, params)).andReturn(mockStatement);

		replay(mockStatement, mockCallableStatementCache, mockConnection);
		prepCallMethod.invoke(testClass, params);
		verify(mockStatement, mockCallableStatementCache, mockConnection);
		// restore sanity
		field.set(testClass, oldCache);

		reset(mockStatement, mockCallableStatementCache, mockConnection);

	}

	/** Set Client Info test.
	 * @throws SQLClientInfoException
	 */
	@Test
	public void testSetClientInfo() throws SQLClientInfoException {
		Properties prop = new Properties();
		mockConnection.setClientInfo(prop);
		replay(mockConnection);
		testClass.setClientInfo(prop);
		verify(mockConnection);

		reset(mockConnection);

		String name = "name";
		String value = "val";
		mockConnection.setClientInfo(name, value);
		replay(mockConnection);
		testClass.setClientInfo(name, value);
		verify(mockConnection);

	}


}
=======
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;


import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.aryEq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.makeThreadSafe;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.reset;
import static org.easymock.classextension.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import junit.framework.Assert;

import org.apache.log4j.Logger;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Mock unit testing for Connection Handle class.
 * @author wwadge
 *
 */
public class TestConnectionHandle {
	/** Test class handle. */
	private static ConnectionHandle testClass;
	/** Mock handle. */
	private static ConnectionHandle mockConnection;
	/** Mock handle. */
	private static IStatementCache mockPreparedStatementCache;
	/** Mock handle. */
	private static IStatementCache mockCallableStatementCache;
	/** Mock handle. */
	private static BoneCP mockPool;
	/** Mock handle. */
	private static Logger mockLogger;
	/** Mock handle. */
	private static StatementCache testStatementCache;


	/** Mock setup.
	 * @throws Exception
	 */
	@BeforeClass
	public static void setUp() throws Exception {
		mockConnection = createNiceMock(ConnectionHandle.class);
		mockPreparedStatementCache = createNiceMock(IStatementCache.class);
		mockCallableStatementCache = createNiceMock(IStatementCache.class);

		mockLogger = createNiceMock(Logger.class);
		makeThreadSafe(mockLogger, true);
		mockPool = createNiceMock(BoneCP.class);
		testClass = new ConnectionHandle(mockConnection, mockPreparedStatementCache, mockCallableStatementCache, mockPool);
		testStatementCache = new StatementCache(100, 100);
		Field field = testClass.getClass().getDeclaredField("logger");
		field.setAccessible(true);
		field.set(null, mockLogger);
	}

	/** Reset everything.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@Before
	public void before() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		reset(mockConnection, mockPreparedStatementCache, mockPool);
		Field field = testClass.getClass().getDeclaredField("connectionClosed");
		field.setAccessible(true);
		field.set(testClass, false);

	}

	/** Test bounce of inner connection.
	 * @throws IllegalArgumentException
	 * @throws SecurityException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	@Test
	public void testStandardMethods() throws IllegalArgumentException, SecurityException,  IllegalAccessException, InvocationTargetException{
		Set skipTests = new HashSet();
		skipTests.add("close");
		skipTests.add("getConnection");
		skipTests.add("markPossiblyBroken");
		skipTests.add("trackStatement");
		skipTests.add("checkClosed");
		skipTests.add("internalClose");
		skipTests.add("prepareCall");
		skipTests.add("prepareStatement");
		skipTests.add("setClientInfo");
		skipTests.add("getConnectionLastUsed");
		skipTests.add("setConnectionLastUsed");
		skipTests.add("getConnectionLastReset");	
		skipTests.add("setConnectionLastReset");
		skipTests.add("isPossiblyBroken");	
		skipTests.add("getOriginatingPartition");
		skipTests.add("setOriginatingPartition");
		skipTests.add("renewConnection");
		skipTests.add("clearStatementHandles");
		skipTests.add("$VRi"); // this only comes into play when code coverage is started. Eclemma bug?

		CommonTestUtils.testStatementBounceMethod(mockConnection, testClass, skipTests, mockConnection);
	}

	/** Test marking of possibly broken status.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@Test
	public void testMarkPossiblyBroken() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		Field field = testClass.getClass().getDeclaredField("possiblyBroken");
		field.setAccessible(true);
		field.set(testClass, false);
		testClass.markPossiblyBroken(null);
		Assert.assertTrue(field.getBoolean(testClass));

		// Test that a db fatal error will lead to the pool being instructed to terminate all connections (+ log)
		mockPool.terminateAllConnections();
		mockLogger.error(anyObject());
		replay(mockPool);
		testClass.markPossiblyBroken(new SQLException("test", "08001"));
		verify(mockPool);


	}

	/** Closing a connection handle should release that connection back in the pool and mark it as closed.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SQLException
	 */
	@Test 
	public void testClose() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SQLException{

		testClass.renewConnection();
		mockPool.releaseConnection((Connection)anyObject());
		expectLastCall().once().andThrow(new SQLException()).once();
		replay(mockPool);

		testClass.close();


		// logically mark the connection as closed
		Field field = testClass.getClass().getDeclaredField("connectionClosed");

		field.setAccessible(true);
		Assert.assertTrue(field.getBoolean(testClass));


		testClass.renewConnection();
		try{
			testClass.close(); // 2nd time should throw an exception
			fail("Should have thrown an exception");
		} catch (Throwable t){
			// do nothing.
		}
		verify(mockPool);
	}

	/** Closing a connection handle should release that connection back in the pool and mark it as closed.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SQLException
	 */
	@SuppressWarnings("unchecked")
	@Test 
	public void testInternalClose() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SQLException{
		ConcurrentLinkedQueue mockStatementHandles = createNiceMock(ConcurrentLinkedQueue.class);
		StatementHandle mockStatement = createNiceMock(StatementHandle.class);
		Field field = testClass.getClass().getDeclaredField("statementHandles");

		field.setAccessible(true);
		field.set(testClass, mockStatementHandles); 

		expect(mockStatementHandles.poll()).andReturn(mockStatement).once().andReturn(null).once();
		mockStatement.internalClose();
		expectLastCall().once();
		mockConnection.close();
		expectLastCall().once().andThrow(new SQLException()).once();
		replay(mockStatement, mockConnection, mockStatementHandles);
		testClass.internalClose();
		try{
			testClass.internalClose(); //2nd time should throw exception
			fail("Should have thrown an exception");
		} catch (Throwable t){
			// do nothing.
		}

		verify(mockStatement, mockConnection, mockStatementHandles);
	}


	/** Test for check closed routine.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 */
	@Test 
	public void testCheckClosed() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{

		testClass.renewConnection();

		// call the method (should not throw an exception)
		Method method = testClass.getClass().getDeclaredMethod("checkClosed");
		method.setAccessible(true);
		method.invoke(testClass);

		// logically mark the connection as closed
		Field field = testClass.getClass().getDeclaredField("connectionClosed");

		field.setAccessible(true);
		field.set(testClass, true);
		try{
			method.invoke(testClass);
			fail("Should have thrown an exception");
		} catch (Throwable t){
			// do nothing.
		}
	}

	/** Test renewal of connection.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@Test
	public void testRenewConnection() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		Field field = testClass.getClass().getDeclaredField("connectionClosed");
		field.setAccessible(true);
		field.set(testClass, true);

		testClass.renewConnection();
		assertFalse(field.getBoolean(testClass));


	}
	/** Tests various getter/setters.
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 */
	@Test
	public void testSettersGetters() throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException {
		ConnectionPartition mockPartition = createNiceMock(ConnectionPartition.class);
		testClass.setOriginatingPartition(mockPartition);
		assertEquals(mockPartition, testClass.getOriginatingPartition());

		testClass.setConnectionLastReset(123);
		assertEquals(testClass.getConnectionLastReset(), 123);

		testClass.setConnectionLastUsed(456);
		assertEquals(testClass.getConnectionLastUsed(), 456);

		Field field = testClass.getClass().getDeclaredField("possiblyBroken");
		field.setAccessible(true);
		field.setBoolean(testClass, true);
		assertTrue(testClass.isPossiblyBroken());
		
		Object debugHandle = new Object();
		testClass.setDebugHandle(debugHandle);
		assertEquals(debugHandle, testClass.getDebugHandle());
		
		field = testClass.getClass().getDeclaredField("connection");
		field.setAccessible(true);
		field.set(testClass, mockConnection);
		assertEquals(mockConnection, testClass.getRawConnection());
	}

	/** Prepare statement tests.
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@Test
	public void testPrepareStatement() throws SecurityException, IllegalArgumentException,  NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		prepareStatementTest(String.class);
		prepareStatementTest(String.class, int.class);
		prepareStatementTest(String.class, int[].class);
		prepareStatementTest(String.class, String[].class);
		prepareStatementTest(String.class, int.class, int.class);
		prepareStatementTest(String.class, int.class, int.class, int.class);
	}

	/** Callable statement tests.
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@Test
	public void testCallableStatement() throws SecurityException, IllegalArgumentException,  NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		callableStatementTest(String.class);
		prepStatementMethod.invoke(testClass, params);
		callableStatementTest(String.class, int.class, int.class);
		callableStatementTest(String.class, int.class, int.class, int.class);
	}


	/** Test routine.
	 * @param args
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@SuppressWarnings("unchecked")
	private void prepareStatementTest(Class... args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		Object[] params = new Object[args.length];
		for (int i=0; i < args.length; i++){
			params[i] = CommonTestUtils.instanceMap.get(args[i]);
		}

		Method prepStatementMethod = testClass.getClass().getMethod("prepareStatement", args);

		PreparedStatementHandle mockStatement = createNiceMock(PreparedStatementHandle.class);
		ConcurrentLinkedQueue mockStatementHandles = createNiceMock(ConcurrentLinkedQueue.class);
		Field field = testClass.getClass().getDeclaredField("statementHandles");
		field.setAccessible(true);
		field.set(testClass, mockStatementHandles);

		testClass.renewConnection(); // logically open the connection

		// fetching a statement that is found in cache. Statement should be returned and marked as being (logically) open
		doStatementMock(mockPreparedStatementCache, mockStatement, params, args);

		mockStatement.setLogicallyOpen();
		expectLastCall();
		replay(mockStatement, mockPreparedStatementCache);
		prepStatementMethod.invoke(testClass, params);
		verify(mockStatement, mockPreparedStatementCache);

		reset(mockStatement, mockPreparedStatementCache);


		// test for a cache miss
		doStatementMock(mockPreparedStatementCache, null, params, args);
		// we should be creating the preparedStatement because it's not in the cache
		expect(prepStatementMethod.invoke(mockConnection, params)).andReturn(mockStatement);
		// we should be tracking this statement
		expect(mockStatementHandles.add(mockStatement)).andReturn(true);

		replay(mockStatement, mockPreparedStatementCache, mockConnection);
		prepStatementMethod.invoke(testClass, params);
		verify(mockStatement, mockPreparedStatementCache, mockConnection);


		// test for cache miss + sql exception
		reset(mockStatement, mockPreparedStatementCache, mockConnection);

		Method mockConnectionPrepareStatementMethod = mockConnection.getClass().getMethod("prepareStatement", args);

		//		expect(mockPreparedStatementCache.get((String)anyObject())).andReturn(null).once();
		doStatementMock(mockPreparedStatementCache, null, params, args);

		// we should be trying to create the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareStatementMethod.invoke(mockConnection, params)).andThrow(new SQLException("test", "Z"));

		replay(mockStatement, mockPreparedStatementCache, mockConnection);
		try{
			prepStatementMethod.invoke(testClass, params);
			fail("Should have thrown an exception");
		} catch (Throwable t) {
			// do nothing
		}

		verify(mockStatement, mockPreparedStatementCache, mockConnection);



		// test for no cache defined
		reset(mockStatement, mockPreparedStatementCache, mockConnection);
		field = testClass.getClass().getDeclaredField("preparedStatementCache");
		field.setAccessible(true);
		IStatementCache oldCache = (IStatementCache) field.get(testClass);
		field.set(testClass, null);


		// we should be creating the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareStatementMethod.invoke(mockConnection, params)).andReturn(mockStatement);

		replay(mockStatement, mockPreparedStatementCache, mockConnection);
		verify(mockStatement, mockPreparedStatementCache, mockConnection);
		// restore sanity
		field.set(testClass, oldCache);

		reset(mockStatement, mockPreparedStatementCache, mockConnection);

	}

	/** Mock setup.
	 * @param cache 
	 * @param returnVal 
	 * @param params
	 * @param args
	 */
	private void doStatementMock(IStatementCache cache, Statement returnVal, Object[] params, Class... args) {
		expect(cache.get((String)anyObject())).andReturn(returnVal).anyTimes();
//		expect(cache.calculateCacheKey((String)anyObject())).andReturn(testStatementCache.calculateCacheKey((String)params[0])).anyTimes();

		if (args.length == 2) {
			if (params[1].getClass().equals(Integer.class)){

				expect(cache.calculateCacheKey((String)anyObject(), anyInt())).andReturn(testStatementCache.calculateCacheKey((String)params[0], (Integer)params[1])).anyTimes();
				expect(cache.get((String)anyObject(), anyInt())).andReturn(returnVal).anyTimes();
			}			
			expect(cache.calculateCacheKey((String)anyObject(), aryEq(new int[]{0,1}))).andReturn(testStatementCache.calculateCacheKey((String)params[0], new int[]{0,1})).anyTimes();
			expect(cache.get((String)anyObject(), aryEq(new int[]{0,1}))).andReturn(returnVal).anyTimes();

			expect(cache.calculateCacheKey((String)anyObject(), (String[])anyObject())).andReturn(testStatementCache.calculateCacheKey((String)params[0], new String[]{"test", "bar"})).anyTimes();
			expect(cache.get((String)anyObject(), (String[])anyObject())).andReturn(returnVal).anyTimes();
		}
		if (args.length == 3) {
			expect(cache.calculateCacheKey((String)anyObject(), anyInt(), anyInt())).andReturn(testStatementCache.calculateCacheKey((String)params[0], (Integer)params[1], (Integer)params[2])).anyTimes();
			expect(cache.get((String)anyObject(), anyInt(), anyInt())).andReturn(returnVal).anyTimes();
		}
		if (args.length == 4) {
			expect(cache.calculateCacheKey((String)anyObject(), anyInt(), anyInt(), anyInt())).andReturn(testStatementCache.calculateCacheKey((String)params[0], (Integer)params[1], (Integer)params[2], (Integer)params[3])).anyTimes();
			expect(cache.get((String)anyObject(), anyInt(), anyInt(), anyInt())).andReturn(returnVal).anyTimes();
		}

	}

	/** Test routine for callable statements.
	 * @param args
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@SuppressWarnings("unchecked")
	private void callableStatementTest(Class... args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		Object[] params = new Object[args.length];
		for (int i=0; i < args.length; i++){
			params[i] = CommonTestUtils.instanceMap.get(args[i]);
		}

		Method prepCallMethod = testClass.getClass().getMethod("prepareCall", args);

		CallableStatementHandle mockStatement = createNiceMock(CallableStatementHandle.class);
		ConcurrentLinkedQueue mockStatementHandles = createNiceMock(ConcurrentLinkedQueue.class);
		Field field = testClass.getClass().getDeclaredField("statementHandles");
		field.setAccessible(true);
		field.set(testClass, mockStatementHandles);

		testClass.renewConnection(); // logically open the connection

		// fetching a statement that is found in cache. Statement should be returned and marked as being (logically) open
		doStatementMock(mockCallableStatementCache, mockStatement, params, args);

//		expect(mockCallableStatementCache.get((String)anyObject())).andReturn(mockStatement).anyTimes();

		//	((StatementHandle)mockStatement).setLogicallyOpen();
		expectLastCall();
		replay(mockStatement, mockCallableStatementCache);
		prepCallMethod.invoke(testClass, params);
		verify(mockStatement, mockCallableStatementCache);

		reset(mockStatement, mockCallableStatementCache);


		// test for a cache miss
		doStatementMock(mockCallableStatementCache, null, params, args);
//		expect(mockCallableStatementCache.get((String)anyObject())).andReturn(null).once();

		// we should be creating the preparedStatement because it's not in the cache
		expect(prepCallMethod.invoke(mockConnection, params)).andReturn(mockStatement);
		// we should be tracking this statement
		expect(mockStatementHandles.add(mockStatement)).andReturn(true);

		replay(mockStatement, mockCallableStatementCache, mockConnection);
		prepCallMethod.invoke(testClass, params);
		verify(mockStatement, mockCallableStatementCache, mockConnection);


		// test for cache miss + sql exception
		reset(mockStatement, mockCallableStatementCache, mockConnection);

		Method mockConnectionPrepareCallMethod = mockConnection.getClass().getMethod("prepareCall", args);

//		expect(mockCallableStatementCache.get((String)anyObject())).andReturn(null).once();
		doStatementMock(mockCallableStatementCache, null, params, args);
	
		// we should be creating the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareCallMethod.invoke(mockConnection, params)).andThrow(new SQLException("test", "Z"));

		replay(mockStatement, mockCallableStatementCache, mockConnection);
		try{
			prepCallMethod.invoke(testClass, params);
			fail("Should have thrown an exception");
		} catch (Throwable t) {
			// do nothing
		}

		verify(mockStatement, mockCallableStatementCache, mockConnection);



		// test for no cache defined
		reset(mockStatement, mockCallableStatementCache, mockConnection);
		field = testClass.getClass().getDeclaredField("callableStatementCache");
		field.setAccessible(true);
		IStatementCache oldCache = (IStatementCache) field.get(testClass);
		field.set(testClass, null);


		// we should be creating the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareCallMethod.invoke(mockConnection, params)).andReturn(mockStatement);

		replay(mockStatement, mockCallableStatementCache, mockConnection);
		prepCallMethod.invoke(testClass, params);
		verify(mockStatement, mockCallableStatementCache, mockConnection);
		// restore sanity
		field.set(testClass, oldCache);

		reset(mockStatement, mockCallableStatementCache, mockConnection);

	}

	/** Set Client Info test.
	 * @throws SQLClientInfoException
	 */
	@Test
	public void testSetClientInfo() throws SQLClientInfoException {
		Properties prop = new Properties();
		mockConnection.setClientInfo(prop);
		replay(mockConnection);
		testClass.setClientInfo(prop);
		verify(mockConnection);

		reset(mockConnection);

		String name = "name";
		String value = "val";
		mockConnection.setClientInfo(name, value);
		replay(mockConnection);
		testClass.setClientInfo(name, value);
		verify(mockConnection);

	}


}
>>>>>>> d7a6bd4eb2ee52d1e4106dadf6c5f63704d5d659
Solution content
/*

Copyright 2009 Wallace Wadge

This file is part of BoneCP.

BoneCP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BoneCP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BoneCP.  If not, see .
*/

package com.jolbox.bonecp;


import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.aryEq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.makeThreadSafe;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.reset;
import static org.easymock.classextension.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import junit.framework.Assert;

import org.apache.log4j.Logger;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Mock unit testing for Connection Handle class.
 * @author wwadge
 *
 */
		mockLogger = createNiceMock(Logger.class);
		mockCallableStatementCache = createNiceMock(IStatementCache.class);


public class TestConnectionHandle {
	/** Test class handle. */
	private static ConnectionHandle testClass;
	/** Mock handle. */
	private static ConnectionHandle mockConnection;
	/** Mock handle. */
	private static IStatementCache mockPreparedStatementCache;
	/** Mock handle. */
	private static IStatementCache mockCallableStatementCache;
	/** Mock handle. */
	private static BoneCP mockPool;
	/** Mock handle. */
	private static Logger mockLogger;
	/** Mock handle. */
	private static StatementCache testStatementCache;


	/** Mock setup.
	 * @throws Exception
	 */
	@BeforeClass
	public static void setUp() throws Exception {
		mockConnection = createNiceMock(ConnectionHandle.class);
		mockPreparedStatementCache = createNiceMock(IStatementCache.class);
		makeThreadSafe(mockLogger, true);
		mockPool = createNiceMock(BoneCP.class);
		testClass = new ConnectionHandle(mockConnection, mockPreparedStatementCache, mockCallableStatementCache, mockPool);
		testStatementCache = new StatementCache(100, 100);
		Field field = testClass.getClass().getDeclaredField("logger");
		field.setAccessible(true);
		field.set(null, mockLogger);
	}

	/** Reset everything.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@Before
	public void before() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		reset(mockConnection, mockPreparedStatementCache, mockPool);
		Field field = testClass.getClass().getDeclaredField("connectionClosed");
		field.setAccessible(true);
		field.set(testClass, false);

	}

	/** Test bounce of inner connection.
	 * @throws IllegalArgumentException
	 * @throws SecurityException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	@Test
	public void testStandardMethods() throws IllegalArgumentException, SecurityException,  IllegalAccessException, InvocationTargetException{
		Set skipTests = new HashSet();
		skipTests.add("close");
		skipTests.add("getConnection");
		skipTests.add("markPossiblyBroken");
		skipTests.add("trackStatement");
		skipTests.add("checkClosed");
		skipTests.add("internalClose");
		skipTests.add("prepareCall");
		skipTests.add("prepareStatement");
		skipTests.add("setClientInfo");
		skipTests.add("getConnectionLastUsed");
		skipTests.add("setConnectionLastUsed");
		skipTests.add("getConnectionLastReset");	
		skipTests.add("setConnectionLastReset");
		skipTests.add("isPossiblyBroken");	
		skipTests.add("getOriginatingPartition");
		skipTests.add("setOriginatingPartition");
		skipTests.add("renewConnection");
		skipTests.add("clearStatementHandles");
		skipTests.add("$VRi"); // this only comes into play when code coverage is started. Eclemma bug?

		CommonTestUtils.testStatementBounceMethod(mockConnection, testClass, skipTests, mockConnection);
	}

	/** Test marking of possibly broken status.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@Test
	public void testMarkPossiblyBroken() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		Field field = testClass.getClass().getDeclaredField("possiblyBroken");
		field.setAccessible(true);
		field.set(testClass, false);
		testClass.markPossiblyBroken(null);
		Assert.assertTrue(field.getBoolean(testClass));
		prepStatementMethod.invoke(testClass, params);
		// Test that a db fatal error will lead to the pool being instructed to terminate all connections (+ log)
		mockPool.terminateAllConnections();
		mockLogger.error(anyObject());
		replay(mockPool);
		testClass.markPossiblyBroken(new SQLException("test", "08001"));
		verify(mockPool);


	}

	/** Closing a connection handle should release that connection back in the pool and mark it as closed.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SQLException
	 */
	@Test 
	public void testClose() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SQLException{

		testClass.renewConnection();
		mockPool.releaseConnection((Connection)anyObject());
		expectLastCall().once().andThrow(new SQLException()).once();
		replay(mockPool);

		testClass.close();


		// logically mark the connection as closed
		Field field = testClass.getClass().getDeclaredField("connectionClosed");

		field.setAccessible(true);
		Assert.assertTrue(field.getBoolean(testClass));


		testClass.renewConnection();
		try{
			testClass.close(); // 2nd time should throw an exception
			fail("Should have thrown an exception");
		} catch (Throwable t){
			// do nothing.
		}
		verify(mockPool);
	}

	/** Closing a connection handle should release that connection back in the pool and mark it as closed.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SQLException
	 */
	@SuppressWarnings("unchecked")
	@Test 
	public void testInternalClose() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SQLException{
		ConcurrentLinkedQueue mockStatementHandles = createNiceMock(ConcurrentLinkedQueue.class);
		StatementHandle mockStatement = createNiceMock(StatementHandle.class);
		Field field = testClass.getClass().getDeclaredField("statementHandles");

		field.setAccessible(true);
		field.set(testClass, mockStatementHandles); 

		expect(mockStatementHandles.poll()).andReturn(mockStatement).once().andReturn(null).once();
		mockStatement.internalClose();
		expectLastCall().once();
		mockConnection.close();
		expectLastCall().once().andThrow(new SQLException()).once();
		replay(mockStatement, mockConnection, mockStatementHandles);
		testClass.internalClose();
		try{
			testClass.internalClose(); //2nd time should throw exception
			fail("Should have thrown an exception");
		} catch (Throwable t){
			// do nothing.
		}

		verify(mockStatement, mockConnection, mockStatementHandles);
	}


	/** Test for check closed routine.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 */
	@Test 
	public void testCheckClosed() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{

		testClass.renewConnection();

		// call the method (should not throw an exception)
		Method method = testClass.getClass().getDeclaredMethod("checkClosed");
		method.setAccessible(true);
		method.invoke(testClass);

		// logically mark the connection as closed
		Field field = testClass.getClass().getDeclaredField("connectionClosed");

		field.setAccessible(true);
		field.set(testClass, true);
		try{
			method.invoke(testClass);
			fail("Should have thrown an exception");
		} catch (Throwable t){
			// do nothing.
		}
	}

	/** Test renewal of connection.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@Test
	public void testRenewConnection() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		Field field = testClass.getClass().getDeclaredField("connectionClosed");
		field.setAccessible(true);
		field.set(testClass, true);

		testClass.renewConnection();
		assertFalse(field.getBoolean(testClass));


	}
	/** Tests various getter/setters.
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 */
	@Test
	public void testSettersGetters() throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException {
		ConnectionPartition mockPartition = createNiceMock(ConnectionPartition.class);
		testClass.setOriginatingPartition(mockPartition);
		assertEquals(mockPartition, testClass.getOriginatingPartition());

		testClass.setConnectionLastReset(123);
		assertEquals(testClass.getConnectionLastReset(), 123);

		testClass.setConnectionLastUsed(456);
		assertEquals(testClass.getConnectionLastUsed(), 456);

		Field field = testClass.getClass().getDeclaredField("possiblyBroken");
		field.setAccessible(true);
		field.setBoolean(testClass, true);
		assertTrue(testClass.isPossiblyBroken());
		
		Object debugHandle = new Object();
		testClass.setDebugHandle(debugHandle);
		assertEquals(debugHandle, testClass.getDebugHandle());
		
		field = testClass.getClass().getDeclaredField("connection");
		field.setAccessible(true);
		field.set(testClass, mockConnection);
		assertEquals(mockConnection, testClass.getRawConnection());
	}

	/** Prepare statement tests.
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@Test
	public void testPrepareStatement() throws SecurityException, IllegalArgumentException,  NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		prepareStatementTest(String.class);
		prepareStatementTest(String.class, int.class);
		prepareStatementTest(String.class, int[].class);
		prepareStatementTest(String.class, String[].class);
		prepareStatementTest(String.class, int.class, int.class);
		prepareStatementTest(String.class, int.class, int.class, int.class);
	}

	/** Callable statement tests.
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@Test
	public void testCallableStatement() throws SecurityException, IllegalArgumentException,  NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		callableStatementTest(String.class);
		callableStatementTest(String.class, int.class, int.class);
		callableStatementTest(String.class, int.class, int.class, int.class);
	}


	/** Test routine.
	 * @param args
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@SuppressWarnings("unchecked")
	private void prepareStatementTest(Class... args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		Object[] params = new Object[args.length];
		for (int i=0; i < args.length; i++){
			params[i] = CommonTestUtils.instanceMap.get(args[i]);
		}

		Method prepStatementMethod = testClass.getClass().getMethod("prepareStatement", args);

		PreparedStatementHandle mockStatement = createNiceMock(PreparedStatementHandle.class);
		ConcurrentLinkedQueue mockStatementHandles = createNiceMock(ConcurrentLinkedQueue.class);
		Field field = testClass.getClass().getDeclaredField("statementHandles");
		field.setAccessible(true);
		field.set(testClass, mockStatementHandles);

		testClass.renewConnection(); // logically open the connection

		// fetching a statement that is found in cache. Statement should be returned and marked as being (logically) open
		doStatementMock(mockPreparedStatementCache, mockStatement, params, args);

		mockStatement.setLogicallyOpen();
		expectLastCall();
		replay(mockStatement, mockPreparedStatementCache);
		prepStatementMethod.invoke(testClass, params);
		verify(mockStatement, mockPreparedStatementCache);

		reset(mockStatement, mockPreparedStatementCache);


		// test for a cache miss
		doStatementMock(mockPreparedStatementCache, null, params, args);
		// we should be creating the preparedStatement because it's not in the cache
		expect(prepStatementMethod.invoke(mockConnection, params)).andReturn(mockStatement);
		// we should be tracking this statement
		expect(mockStatementHandles.add(mockStatement)).andReturn(true);

		replay(mockStatement, mockPreparedStatementCache, mockConnection);
		prepStatementMethod.invoke(testClass, params);
		verify(mockStatement, mockPreparedStatementCache, mockConnection);


		// test for cache miss + sql exception
		reset(mockStatement, mockPreparedStatementCache, mockConnection);

		Method mockConnectionPrepareStatementMethod = mockConnection.getClass().getMethod("prepareStatement", args);

		//		expect(mockPreparedStatementCache.get((String)anyObject())).andReturn(null).once();
		doStatementMock(mockPreparedStatementCache, null, params, args);

		// we should be trying to create the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareStatementMethod.invoke(mockConnection, params)).andThrow(new SQLException("test", "Z"));

		replay(mockStatement, mockPreparedStatementCache, mockConnection);
		try{
			prepStatementMethod.invoke(testClass, params);
			fail("Should have thrown an exception");
		} catch (Throwable t) {
			// do nothing
		}

		verify(mockStatement, mockPreparedStatementCache, mockConnection);



		// test for no cache defined
		reset(mockStatement, mockPreparedStatementCache, mockConnection);
		field = testClass.getClass().getDeclaredField("preparedStatementCache");
		field.setAccessible(true);
		IStatementCache oldCache = (IStatementCache) field.get(testClass);
		field.set(testClass, null);


		// we should be creating the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareStatementMethod.invoke(mockConnection, params)).andReturn(mockStatement);

		replay(mockStatement, mockPreparedStatementCache, mockConnection);
		verify(mockStatement, mockPreparedStatementCache, mockConnection);
		// restore sanity
		field.set(testClass, oldCache);

		reset(mockStatement, mockPreparedStatementCache, mockConnection);

	}

	/** Mock setup.
	 * @param cache 
	 * @param returnVal 
	 * @param params
	 * @param args
	 */
	private void doStatementMock(IStatementCache cache, Statement returnVal, Object[] params, Class... args) {
		expect(cache.get((String)anyObject())).andReturn(returnVal).anyTimes();
//		expect(cache.calculateCacheKey((String)anyObject())).andReturn(testStatementCache.calculateCacheKey((String)params[0])).anyTimes();

		if (args.length == 2) {
			if (params[1].getClass().equals(Integer.class)){

				expect(cache.calculateCacheKey((String)anyObject(), anyInt())).andReturn(testStatementCache.calculateCacheKey((String)params[0], (Integer)params[1])).anyTimes();
				expect(cache.get((String)anyObject(), anyInt())).andReturn(returnVal).anyTimes();
			}			
			expect(cache.calculateCacheKey((String)anyObject(), aryEq(new int[]{0,1}))).andReturn(testStatementCache.calculateCacheKey((String)params[0], new int[]{0,1})).anyTimes();
			expect(cache.get((String)anyObject(), aryEq(new int[]{0,1}))).andReturn(returnVal).anyTimes();

			expect(cache.calculateCacheKey((String)anyObject(), (String[])anyObject())).andReturn(testStatementCache.calculateCacheKey((String)params[0], new String[]{"test", "bar"})).anyTimes();
			expect(cache.get((String)anyObject(), (String[])anyObject())).andReturn(returnVal).anyTimes();
		}
		if (args.length == 3) {
			expect(cache.calculateCacheKey((String)anyObject(), anyInt(), anyInt())).andReturn(testStatementCache.calculateCacheKey((String)params[0], (Integer)params[1], (Integer)params[2])).anyTimes();
			expect(cache.get((String)anyObject(), anyInt(), anyInt())).andReturn(returnVal).anyTimes();
		}
		if (args.length == 4) {
			expect(cache.calculateCacheKey((String)anyObject(), anyInt(), anyInt(), anyInt())).andReturn(testStatementCache.calculateCacheKey((String)params[0], (Integer)params[1], (Integer)params[2], (Integer)params[3])).anyTimes();
			expect(cache.get((String)anyObject(), anyInt(), anyInt(), anyInt())).andReturn(returnVal).anyTimes();
		}

	}

	/** Test routine for callable statements.
	 * @param args
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	@SuppressWarnings("unchecked")
	private void callableStatementTest(Class... args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		Object[] params = new Object[args.length];
		for (int i=0; i < args.length; i++){
			params[i] = CommonTestUtils.instanceMap.get(args[i]);
		}

		Method prepCallMethod = testClass.getClass().getMethod("prepareCall", args);

		CallableStatementHandle mockStatement = createNiceMock(CallableStatementHandle.class);
		ConcurrentLinkedQueue mockStatementHandles = createNiceMock(ConcurrentLinkedQueue.class);
		Field field = testClass.getClass().getDeclaredField("statementHandles");
		field.setAccessible(true);
		field.set(testClass, mockStatementHandles);

		testClass.renewConnection(); // logically open the connection

		// fetching a statement that is found in cache. Statement should be returned and marked as being (logically) open
		doStatementMock(mockCallableStatementCache, mockStatement, params, args);

//		expect(mockCallableStatementCache.get((String)anyObject())).andReturn(mockStatement).anyTimes();

		//	((StatementHandle)mockStatement).setLogicallyOpen();
		expectLastCall();
		replay(mockStatement, mockCallableStatementCache);
		prepCallMethod.invoke(testClass, params);
		verify(mockStatement, mockCallableStatementCache);

		reset(mockStatement, mockCallableStatementCache);


		// test for a cache miss
		doStatementMock(mockCallableStatementCache, null, params, args);
//		expect(mockCallableStatementCache.get((String)anyObject())).andReturn(null).once();

		// we should be creating the preparedStatement because it's not in the cache
		expect(prepCallMethod.invoke(mockConnection, params)).andReturn(mockStatement);
		// we should be tracking this statement
		expect(mockStatementHandles.add(mockStatement)).andReturn(true);

		replay(mockStatement, mockCallableStatementCache, mockConnection);
		prepCallMethod.invoke(testClass, params);
		verify(mockStatement, mockCallableStatementCache, mockConnection);


		// test for cache miss + sql exception
		reset(mockStatement, mockCallableStatementCache, mockConnection);

		Method mockConnectionPrepareCallMethod = mockConnection.getClass().getMethod("prepareCall", args);

//		expect(mockCallableStatementCache.get((String)anyObject())).andReturn(null).once();
		doStatementMock(mockCallableStatementCache, null, params, args);
	
		// we should be creating the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareCallMethod.invoke(mockConnection, params)).andThrow(new SQLException("test", "Z"));

		replay(mockStatement, mockCallableStatementCache, mockConnection);
		try{
			prepCallMethod.invoke(testClass, params);
			fail("Should have thrown an exception");
		} catch (Throwable t) {
			// do nothing
		}

		verify(mockStatement, mockCallableStatementCache, mockConnection);



		// test for no cache defined
		reset(mockStatement, mockCallableStatementCache, mockConnection);
		field = testClass.getClass().getDeclaredField("callableStatementCache");
		field.setAccessible(true);
		IStatementCache oldCache = (IStatementCache) field.get(testClass);
		field.set(testClass, null);


		// we should be creating the preparedStatement because it's not in the cache
		expect(mockConnectionPrepareCallMethod.invoke(mockConnection, params)).andReturn(mockStatement);

		replay(mockStatement, mockCallableStatementCache, mockConnection);
		prepCallMethod.invoke(testClass, params);
		verify(mockStatement, mockCallableStatementCache, mockConnection);
		// restore sanity
		field.set(testClass, oldCache);

		reset(mockStatement, mockCallableStatementCache, mockConnection);

	}

	/** Set Client Info test.
	 * @throws SQLClientInfoException
	 */
	@Test
	public void testSetClientInfo() throws SQLClientInfoException {
		Properties prop = new Properties();
		mockConnection.setClientInfo(prop);
		replay(mockConnection);
		testClass.setClientInfo(prop);
		verify(mockConnection);

		reset(mockConnection);

		String name = "name";
		String value = "val";
		mockConnection.setClientInfo(name, value);
		replay(mockConnection);
		testClass.setClientInfo(name, value);
		verify(mockConnection);

	}


}
File
TestConnectionHandle.java
Developer's decision
Version 2
Kind of conflict
Class declaration
Comment
Import
Package declaration