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

import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.sql.Connection;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

import javax.sql.DataSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import static com.google.common.base.Preconditions.*;
import com.jolbox.bonecp.hooks.ConnectionHook;


/**
 * Configuration class.
 *
 * @author wallacew
 */
public class BoneCPConfig implements BoneCPConfigMBean, Cloneable, Serializable {
	/** Constant. */
	private static final String USER = "user";
	/** Constant. */
	private static final String PASSWORD = "password";
	/** Serialization UID. */
	private static final long serialVersionUID = 6090570773474131622L;
	/** For toString(). */
	private static final String CONFIG_TOSTRING = "JDBC URL = %s, Username = %s, partitions = %d, max (per partition) = %d, min (per partition) = %d, idle max age = %d min, idle test period = %d min, strategy = %s";
	/** For toString(). */
	private static final String CONFIG_DS_TOSTRING = "JDBC URL = (via datasource bean), Username = (via datasource bean), partitions = %d, max (per partition) = %d, min (per partition) = %d, idle max age = %d min, idle test period = %d min, strategy = %s";
	/** Logger class. */
	private static final Logger logger = LoggerFactory.getLogger(BoneCPConfig.class);
	/** Min number of connections per partition. */
	private int minConnectionsPerPartition = 1;
	/** Max number of connections per partition. */
	private int maxConnectionsPerPartition = 2;
	/** Number of new connections to create in 1 batch. */
	private int acquireIncrement = 2;
	/** Number of partitions. */
	private int partitionCount = 1;
	/** DB connection string. */
	private String jdbcUrl;
	/** User name to use. */
	private String username;
	/** Password to use. */
	private String password;
	/** Connections older than this are sent a keep-alive statement. */
	private long idleConnectionTestPeriodInSeconds = 240*60; 
	/** Maximum age of an unused connection before it is closed off. */ 
	private long idleMaxAgeInSeconds =  60*60; 
	/** SQL statement to use for keep-alive/test of connection. */
	private String connectionTestStatement;
	/** Min no of prepared statements to cache. */
	private int statementsCacheSize = 0;
	/** No of statements that can be cached per connection. Deprecated. */
	private int statementsCachedPerConnection = 0;
	/** Number of release-connection helper threads to create per partition. */
	private int releaseHelperThreads = 0;
	/** Number of statement release helper threads to create. */
	private int statementReleaseHelperThreads = 0;
	/** Hook class (external). */
	private ConnectionHook connectionHook;
	/** Query to send once per connection to the database. */
	private String initSQL;
	/** If set to true, create a new thread that monitors a connection and displays warnings if application failed to 
	 * close the connection. FOR DEBUG PURPOSES ONLY!
	 */
	private boolean closeConnectionWatch;
	/** If set to true, log SQL statements being executed. */ 
	private boolean logStatementsEnabled;
	/** After attempting to acquire a connection and failing, wait for this value before attempting to acquire a new connection again. */
	private long acquireRetryDelayInMs=7000;
	/** After attempting to acquire a connection and failing, try to connect these many times before giving up. */
	private int acquireRetryAttempts=5;
	/** If set to true, the connection pool will remain empty until the first connection is obtained. */
	private boolean lazyInit;
	/** If set to true, stores all activity on this connection to allow for replaying it again. */
	private boolean transactionRecoveryEnabled;
	/** Connection hook class name. */
	private String connectionHookClassName;
	/** Classloader to use when loading the JDBC driver. */
	private ClassLoader classLoader = this.getClassLoader();
	/** Name of the pool for JMX and thread names. */
	private String poolName;
	/** Set to true to disable JMX. */
	private boolean disableJMX;
	/** If set, use datasourceBean.getConnection() to obtain a new connection. */
	private DataSource datasourceBean;
	/** Queries taking longer than this limit to execute are logged. */ 
	private long queryExecuteTimeLimitInMs = 0;
	/** Create more connections when we hit x% of our possible number of connections. */
	private int poolAvailabilityThreshold = 0;
	/** Disable connection tracking. */
	private boolean disableConnectionTracking;
	/** Used when the alternate way of obtaining a connection is required */
	@VisibleForTesting protected Properties driverProperties;
	/** Time to wait before a call to getConnection() times out and returns an error. */ 
	private long connectionTimeoutInMs = 0;
	/** Time in ms to wait for close connection watch thread. */
	private long closeConnectionWatchTimeoutInMs = 0;
	/** A connection older than maxConnectionAge will be destroyed and purged from the pool. */
	private long maxConnectionAgeInSeconds = 0;
	/** Config property. */
	private String configFile;
	/** Queue mode. Values currently understood are FIFO and LIFO. */
	private String serviceOrder = "FIFO";
	/** If true, keep track of some statistics. */
	private boolean statisticsEnabled;
	/** The default auto-commit state of created connections. */
	private boolean defaultAutoCommit = true;
	/** The default read-only state of created connections. */
	private boolean defaultReadOnly;
	/** The default transaction isolation state of created connections. */
	private String defaultTransactionIsolation;
	/** The default catalog state of created connections. */
	private String defaultCatalog;
	/** The parsed transaction isolation value. Default = driver value*/
	private int defaultTransactionIsolationValue = -1;
	/** If true, stop caring about username/password when obtaining raw connections. */
	private boolean externalAuth;
	/** If true, try to unregister the JDBC driver when pool is shutdown. */
	private boolean deregisterDriverOnClose;
	/** If true, return null on connection timeout rather than throw an exception. */
	private boolean nullOnConnectionTimeout;
	/** If true, issue a reset (rollback) on connection close in case client forgot it. */
	private boolean resetConnectionOnClose;
	/** Detect uncommitted transactions. If true, and resetConnectionOnClose is also true, the pool will print out a stack 
	 * trace of the location where you had a connection that specified setAutoCommit(false)
	 * but then forgot to call commit/rollback before closing it off. This feature is intended 
	 * for debugging only.*/
	private boolean detectUnresolvedTransactions;
	/** Determines pool operation Recognised strategies are: DEFAULT, CACHED. */
	private String poolStrategy = "DEFAULT";
	/** If true, track statements and close them if application forgot to do so. See also: 
	 * detectUnclosedStatements. */
	private boolean closeOpenStatements;
	/** If true, print out a stack trace of where a statement was opened but not closed before
	 * the connection was closed. See also: closeOpenStatements. */
	private boolean detectUnclosedStatements;

	/** If set, pool will call this for every new connection that's created. */
	private Properties clientInfo;

	/** Returns the name of the pool for JMX and thread names.
	 * @return a pool name.
	 */
	public String getPoolName() {
		return this.poolName;
	}

	/** Sets the name of the pool for JMX and thread names.
	 * @param poolName to set.
	 */
	public void setPoolName(String poolName) {
		this.poolName = checkNotNull(poolName);
	}

	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getMinConnectionsPerPartition()
	 */
	public int getMinConnectionsPerPartition() {
		return this.minConnectionsPerPartition;
	}

	/**
	 * Sets the minimum number of connections that will be contained in every partition. Also refer 
	 * to {@link #setPoolAvailabilityThreshold(int)}.
	 *
	 * @param minConnectionsPerPartition number of connections
	 */
	public void setMinConnectionsPerPartition(int minConnectionsPerPartition) {
		this.minConnectionsPerPartition = minConnectionsPerPartition;
	}

	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getMaxConnectionsPerPartition()
	 */
	public int getMaxConnectionsPerPartition() {
		return this.maxConnectionsPerPartition;
	}

	/**
	 * Sets the maximum number of connections that will be contained in every partition. 
	 * Setting this to 5 with 3 partitions means you will have 15 unique connections to the database. 
	 * Note that the connection pool will not create all these connections in one go but rather start off 
	 * with minConnectionsPerPartition and gradually increase connections as required.
	 *
	 * @param maxConnectionsPerPartition number of connections.
	 */
	public void setMaxConnectionsPerPartition(int maxConnectionsPerPartition) {
		this.maxConnectionsPerPartition = maxConnectionsPerPartition;
	}

	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getAcquireIncrement()
	 */
	public int getAcquireIncrement() {
		return this.acquireIncrement;
	}

	/**
	 * Sets the acquireIncrement property. 
	 * 
	 * When the available connections are about to run out, BoneCP will dynamically create new ones in batches. 
	 * This property controls how many new connections to create in one go (up to a maximum of maxConnectionsPerPartition). 
	 * <p>Note: This is a per partition setting.
	 *
	 * @param acquireIncrement value to set. 
	 */
	public void setAcquireIncrement(int acquireIncrement) {
		this.acquireIncrement = acquireIncrement;
	}

	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getPartitionCount()
	 */
	public int getPartitionCount() {
		return this.partitionCount;
	}

	/**
	 * Sets number of partitions to use. 
	 * 
	 * In order to reduce lock contention and thus improve performance, 
	 * each incoming connection request picks off a connection from a pool that has thread-affinity, 
	 * i.e. pool[threadId % partition_count]. The higher this number, the better your performance will be for the case 
	 * when you have plenty of short-lived threads. Beyond a certain threshold (approx 4), maintenance of these pools will start 
	 * to have a negative effect on performance (and only for the case when connections on a partition start running out).
	 * Has no effect in a CACHED strategy.
	 *  
	 * <p>Default: 1, minimum: 1, recommended: 2-4 (but very app specific)
	 *
	 * @param partitionCount to set 
	 */
	public void setPartitionCount(int partitionCount) {
		this.partitionCount = partitionCount;
	}

	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getJdbcUrl()
	 */
	public String getJdbcUrl() {
		return this.jdbcUrl;
	}

	/**
	 * Sets the JDBC connection URL.
	 *
	 * @param jdbcUrl to set
	 */
	public void setJdbcUrl(String jdbcUrl) {
		this.jdbcUrl = jdbcUrl;
	}


	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getUsername()
	 */
	public String getUsername() {
		return this.username;
	}

	/**
	 * Sets username to use for connections.
	 *
	 * @param username to set
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * Sets username to use for connections. Just delegates to setUsername for clients hardcoded
	 * with "setUser" instead. 
	 *
	 * @param username to set
	 */
	public void setUser(String username) {
		setUsername(username);
	}

	/** Just delegates to getUsername for clients hardcoded to "getUser". 
	 * @return configured username
	 */
	public String getUser() {
		return getUsername();
	}
	/**
	 * Gets password to use for connections
	 *
	 * @return password
	 */
	public String getPassword() {
		return this.password;
	}

	/**
	 * Sets password to use for connections.
	 *
	 * @param password to set.
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * Deprecated. 
	 * 
	 * @deprecated Please use {@link #getIdleConnectionTestPeriodInMinutes()} instead.
	 * @return idleConnectionTest
	 */
	@Deprecated
	public long getIdleConnectionTestPeriod() {
		logger.warn("Please use getIdleConnectionTestPeriodInMinutes in place of getIdleConnectionTestPeriod. This method has been deprecated.");
		return getIdleConnectionTestPeriodInMinutes();
	}

	/**
	 * Sets the idleConnectionTestPeriod in minutes
	 * 
	 * @deprecated Please use {@link #setIdleConnectionTestPeriodInMinutes(long)} or {@link #setIdleConnectionTestPeriod(long, TimeUnit)} instead
	 * @param idleConnectionTestPeriod to set in minutes 
	 */
	@Deprecated
	public void setIdleConnectionTestPeriod(long idleConnectionTestPeriod) {
		logger.warn("Please use setIdleConnectionTestPeriodInMinutes in place of setIdleConnectionTestPeriod. This method has been deprecated.");
		setIdleConnectionTestPeriod(idleConnectionTestPeriod*60, TimeUnit.SECONDS);
	}

	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getIdleConnectionTestPeriodInMinutes()
	 */
	public long getIdleConnectionTestPeriodInMinutes() {
		return this.idleConnectionTestPeriodInSeconds / 60;
	}

	/**
	 * Returns the idleConnectionTestPeriod with the specified granularity.
	 * 
	 * @param timeUnit time granularity
	 * @return Idle Connection test period 
	 */
	public long getIdleConnectionTestPeriod(TimeUnit timeUnit) {
		return timeUnit.convert(this.idleConnectionTestPeriodInSeconds, TimeUnit.SECONDS);
	}

	/**
	 * Sets the idleConnectionTestPeriod.
	 * 
	 * This sets the time (in minutes), for a connection to remain idle before sending 
	 * a test query to the DB. This is useful to prevent a DB from timing out connections 
	 * on its end. Do not use aggressive values here! 
	 * 
	 * 
	 * <p>Default: 240 min, set to 0 to disable
	 *
	 * @param idleConnectionTestPeriod to set 
	 */
	public void setIdleConnectionTestPeriodInMinutes(long idleConnectionTestPeriod) {
		// we use TimeUnit.SECONDS instead of TimeUnit.MINUTES because it's not supported
		// by JDK5
		setIdleConnectionTestPeriod(idleConnectionTestPeriod*60, TimeUnit.SECONDS);
	}

	/**
	 * Sets the idleConnectionTestPeriod.
	 * 
	 * This sets the time (in seconds), for a connection to remain idle before sending 
	 * a test query to the DB. This is useful to prevent a DB from timing out connections 
	 * on its end. Do not use aggressive values here! 
	 * 
	 * 
	 * <p>Default: 240 min, set to 0 to disable
	 *
	 * @param idleConnectionTestPeriod to set 
	 */
	public void setIdleConnectionTestPeriodInSeconds(long idleConnectionTestPeriod) {
		setIdleConnectionTestPeriod(idleConnectionTestPeriod, TimeUnit.SECONDS);
	}

	/** Wrapper method for idleConnectionTestPeriod for easier programmatic access.
	 * @param idleConnectionTestPeriod time for a connection to remain idle before sending a test
	 * query to the DB.
	 * @param timeUnit Time granularity of given parameter. 
	 */
	public void setIdleConnectionTestPeriod(long idleConnectionTestPeriod, TimeUnit timeUnit) {
		this.idleConnectionTestPeriodInSeconds = TimeUnit.SECONDS.convert(idleConnectionTestPeriod, checkNotNull(timeUnit)); 
	}

	/** Deprecated. 
	 * @return idleMaxAge in minutes
	 * @deprecated Use {@link #getIdleMaxAgeInMinutes()} instead 
	 */
	@Deprecated
	public long getIdleMaxAge() {
		logger.warn("Please use getIdleMaxAgeInMinutes in place of getIdleMaxAge. This method has been deprecated.");
		return getIdleMaxAgeInMinutes();
	}

	/**
	 * Returns the idleMaxAge with the specified granularity.
	 * 
	 * @param timeUnit time granularity
	 * @return idleMaxAge value
	 */
	public long getIdleMaxAge(TimeUnit timeUnit) {
		return timeUnit.convert(this.idleMaxAgeInSeconds, TimeUnit.SECONDS);
	}

	/** Returns the idleMaxAge currently set. 
	 * @return idleMaxAge in minutes
	 */
	public long getIdleMaxAgeInMinutes() {
		return this.idleMaxAgeInSeconds / 60;
	}

	/**
	 * Deprecated. 
	 *
	 * @param idleMaxAge to set
	 * @deprecated Use {@link #setIdleMaxAgeInMinutes(long)} or {@link #setIdleMaxAge(long, TimeUnit)} instead.
	 */
	@Deprecated
	public void setIdleMaxAge(long idleMaxAge) {
		logger.warn("Please use setIdleMaxAgeInMinutes in place of setIdleMaxAge. This method has been deprecated.");
		setIdleMaxAgeInMinutes(idleMaxAge); 
	}

	/**
	 * Sets Idle max age (in min).
	 * 
	 * The time (in minutes), for a connection to remain unused before it is closed off. Do not use aggressive values here! 
	 * 
	 * <p>Default: 60 minutes, set to 0 to disable.
	 *
	 * @param idleMaxAge to set
	 */
	public void setIdleMaxAgeInMinutes(long idleMaxAge) {
		setIdleMaxAge(idleMaxAge*60, TimeUnit.SECONDS); 
	}

	/**
	 * Sets Idle max age (in seconds).
	 * 
	 * The time (in seconds), for a connection to remain unused before it is closed off. Do not use aggressive values here! 
	 * 
	 * <p>Default: 60 minutes, set to 0 to disable.
	 *
	 * @param idleMaxAge to set
	 */
	public void setIdleMaxAgeInSeconds(long idleMaxAge) {
		setIdleMaxAge(idleMaxAge, TimeUnit.SECONDS); 
	}
	/** Sets Idle max age.
	 * 
	 * The time, for a connection to remain unused before it is closed off. Do not use aggressive values here!
	 * @param idleMaxAge time after which a connection is closed off
	 * @param timeUnit idleMaxAge time granularity.
	 */
	public void setIdleMaxAge(long idleMaxAge, TimeUnit timeUnit) {
		this.idleMaxAgeInSeconds = TimeUnit.SECONDS.convert(idleMaxAge, checkNotNull(timeUnit)); 
	}



	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getConnectionTestStatement()
	 */
	public String getConnectionTestStatement() {
		return this.connectionTestStatement;
	}

	/**
	 *Sets the connection test statement.
	 *
	 *The query to send to the DB to maintain keep-alives and test for dead connections. 
	 *This is database specific and should be set to a query that consumes the minimal amount of load on the server. 
	 *Examples: MySQL: "/* ping *\/ SELECT 1", PostgreSQL: "SELECT NOW()". 
	 *If you do not set this, then BoneCP will issue a metadata request instead that should work on all databases but is probably slower.
	 *
	 * (Note: In MySQL, prefixing the statement by /* ping *\/ makes the driver issue 1 fast packet instead. See 
	 * http://blogs.sun.com/SDNChannel/entry/mysql_tips_for_java_developers )
	 *<p>Default: Use metadata request
	 *
	 * @param connectionTestStatement to set.
	 */
	public void setConnectionTestStatement(String connectionTestStatement) {
		this.connectionTestStatement = checkNotNull(connectionTestStatement);
	}

	/** Deprecated. Use getStatementsCacheSize() instead
	 * @return statementsCacheSize
	 */
	@Deprecated
	public int getPreparedStatementsCacheSize() {
		logger.warn("Please use getStatementsCacheSize in place of getPreparedStatementsCacheSize. This method has been deprecated.");
		return this.statementsCacheSize;
	}

	/** Deprecated. Use getStatementsCacheSize() instead
	 * @return statementsCacheSize
	 */
	@Deprecated
	public int getPreparedStatementCacheSize() {
		logger.warn("Please use getStatementsCacheSize in place of getPreparedStatementCacheSize. This method has been deprecated.");
		return this.statementsCacheSize;
	}

	/**
	 * Deprecated. Use setStatementsCacheSize() instead. 
	 *
	 * @param preparedStatementsCacheSize to set.
	 */
	@Deprecated
	public void setPreparedStatementsCacheSize(int preparedStatementsCacheSize) {
		logger.warn("Please use setStatementsCacheSize in place of setPreparedStatementsCacheSize. This method has been deprecated.");
		this.statementsCacheSize = preparedStatementsCacheSize;
	}

	/**
	 * Sets statementsCacheSize setting.
	 * 
	 * The number of statements to cache. 
	 *
	 * @param statementsCacheSize to set.
	 */
	public void setStatementsCacheSize(int statementsCacheSize) {
		this.statementsCacheSize = statementsCacheSize;
	}

	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getStatementsCacheSize()
	 */
	public int getStatementsCacheSize() {
		return this.statementsCacheSize;
	}

	/**
	 * Deprecated. Use set statementCacheSize instead. 
	 * 
	 * The number of statements to cache. 
	 *
	 * @param statementsCacheSize to set.
	 */
	@Deprecated
	public void setStatementCacheSize(int statementsCacheSize) {
		logger.warn("Please use setStatementsCacheSize in place of setStatementCacheSize. This method has been deprecated.");
		this.statementsCacheSize = statementsCacheSize;
	}

	/** Deprecated. Use getStatementsCacheSize instead
	 * @return no of cache size. 
	 */
	@Deprecated
	public int getStatementCacheSize() {
		logger.warn("Please use getStatementsCacheSize in place of getStatementCacheSize. This method has been deprecated.");
		return this.statementsCacheSize;
	}


	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getReleaseHelperThreads()
	 */
	@Deprecated
	public int getReleaseHelperThreads() {
		return this.releaseHelperThreads;
	}


	/**
	 * Sets number of helper threads to create that will handle releasing a connection.
	 *
	 * When this value is set to zero, the application thread is blocked until the pool is able to perform all the necessary cleanup to
	 * recycle the connection and make it available for another thread. 
	 * 
	 * When a non-zero value is set, the pool will create threads that will take care of recycling a connection when it is closed (the 
	 * application dumps the connection into a temporary queue to be processed asychronously to the application via the release helper 
	 * threads).
	 * 
	 * Useful when your application is doing lots of work on each connection 
	 * (i.e. perform an SQL query, do lots of non-DB stuff and perform another query), 
	 * otherwise will probably slow things down.
	 * 
	 * @param releaseHelperThreads no to release 
	 */
	@Deprecated
	public void setReleaseHelperThreads(int releaseHelperThreads) {
		logger.warn("releaseHelperThreads has been deprecated -- it tends to slow down your application more.");
		this.releaseHelperThreads = releaseHelperThreads;
	}

	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getStatementsCachedPerConnection()
	 */
	@Deprecated
	public int getStatementsCachedPerConnection() {
		return this.statementsCachedPerConnection;
	}

	/**
	 * DEPRECATED. No longer necessary to call at all. 
	 * 
	 * Sets no of statements cached per connection. 
	 *
	 * The number of prepared statements to cache per connection. This is usually only useful if you attempt to 
	 * prepare the same prepared statement string in the same connection (usually due to a wrong design condition).
	 *
	 * @param statementsCachedPerConnection to set
	 */
	@Deprecated
	public void setStatementsCachedPerConnection(int statementsCachedPerConnection) {
		this.statementsCachedPerConnection = statementsCachedPerConnection;
	}



	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getConnectionHook()
	 */
	public ConnectionHook getConnectionHook() {
		return this.connectionHook;
	}

	/** Sets the connection hook.
	 * 
	 * Fully qualified class name that implements the ConnectionHook interface (or extends AbstractConnectionHook). 
	 * BoneCP will callback the specified class according to the connection state (onAcquire, onCheckIn, onCheckout, onDestroy).
	 * 
	 * @param connectionHook the connectionHook to set
	 */
	public void setConnectionHook(ConnectionHook connectionHook) {
		this.connectionHook = connectionHook;
	}

	/** {@inheritDoc}
	 * @see com.jolbox.bonecp.BoneCPConfigMBean#getInitSQL()
	 */
	public String getInitSQL() {
		return this.initSQL;
	}

	/** Specifies an initial SQL statement that is run only when a connection is first created. 
	 * @param initSQL the initSQL to set
	 */
	public void setInitSQL(String initSQL) {
		this.initSQL = checkNotNull(initSQL);
	}

	/** Returns if BoneCP is configured to create a helper thread to watch over connection acquires that are never released (or released
	 * twice). 
	 * FOR DEBUG PURPOSES ONLY. 
	 * @return the current closeConnectionWatch setting.
	 */
	public boolean isCloseConnectionWatch() {
		return this.closeConnectionWatch;
	}

	/** Instruct the pool to create a helper thread to watch over connection acquires that are never released (or released twice). 
	 * This is for debugging purposes only and will create a new thread for each call to getConnection(). 
	 * Enabling this option will have a big negative impact on pool performance.
	 * @param closeConnectionWatch set to true to enable thread monitoring.
	 */
	public void setCloseConnectionWatch(boolean closeConnectionWatch) {
		this.closeConnectionWatch = closeConnectionWatch;
	}

	/** Returns true if SQL logging is currently enabled, false otherwise.
	 * @return the logStatementsEnabled status
	 */
	public boolean isLogStatementsEnabled() {
		return this.logStatementsEnabled;
	}

	/** If enabled, log SQL statements being executed. This will also "fill in" the parameters 
	 * for prepared statements so that you can see exactly what values where bound at the time 
	 * you executed the statement.
	 * You will also need to set your log4j settings ("com.jolbox.bonecp") to DEBUG 
	 * @param logStatementsEnabled the logStatementsEnabled to set
	 */
	public void setLogStatementsEnabled(boolean logStatementsEnabled) {
		this.logStatementsEnabled = logStatementsEnabled;
	}

	/** Deprecated. 
	 * 
	 * @deprecated Use {@link #getAcquireRetryDelayInMs()} instead.
	 * @return the acquireRetryDelay
	 */
	@Deprecated
	public long getAcquireRetryDelay() {
		logger.warn("Please use getAcquireRetryDelayInMs in place of getAcquireRetryDelay. This method has been deprecated.");
		return this.acquireRetryDelayInMs;
	}


	/** Deprecated.  
	 * @param acquireRetryDelayInMs the acquireRetryDelay to set
	 * @deprecated Use {@link #setAcquireRetryDelayInMs(long)}.
	 */
	@Deprecated
	public void setAcquireRetryDelay(int acquireRetryDelayInMs) {
		logger.warn("Please use setAcquireRetryDelayInMs in place of setAcquireRetryDelay. This method has been deprecated.");
		this.acquireRetryDelayInMs = acquireRetryDelayInMs;
	}

	/** Returns the number of ms to wait before attempting to obtain a connection again after a failure. Default: 7000.
	 * @return the acquireRetryDelay
	 */
	public long getAcquireRetryDelayInMs() {
		return this.acquireRetryDelayInMs;
	}

	/**
	 * Returns the acquireRetryDelay setting with the specified granularity.
	 * 
	 * @param timeUnit time granularity
	 * @return acquireRetryDelay 
	 */
	public long getAcquireRetryDelay(TimeUnit timeUnit) {
		return timeUnit.convert(this.acquireRetryDelayInMs, TimeUnit.MILLISECONDS);
	}


	/** Sets the number of ms to wait before attempting to obtain a connection again after a failure. 
	 * @param acquireRetryDelay the acquireRetryDelay to set
	 */
	public void setAcquireRetryDelayInMs(long acquireRetryDelay) {
		setAcquireRetryDelay(acquireRetryDelay, TimeUnit.MILLISECONDS);
	}



	/** Sets the number of ms to wait before attempting to obtain a connection again after a failure.
	 * @param acquireRetryDelay  the acquireRetryDelay to set
	 * @param timeUnit time granularity
	 */
	public void setAcquireRetryDelay(long acquireRetryDelay, TimeUnit timeUnit) {
		this.acquireRetryDelayInMs = TimeUnit.MILLISECONDS.convert(acquireRetryDelay, timeUnit);
	}

	/** Returns true if connection pool is to be initialized lazily.
	 * @return lazyInit setting 
	 */
	public boolean isLazyInit() {
		return this.lazyInit;
	}

	/** Set to true to force the connection pool to obtain the initial connections lazily.
	 * @param lazyInit the lazyInit setting to set
	 */
	public void setLazyInit(boolean lazyInit) {
		this.lazyInit = lazyInit;
	}




	/** Returns true if the pool is configured to record all transaction activity and replay the transaction automatically in case
	 * of connection failures.
	 * @return the transactionRecoveryEnabled status
	 */
	public boolean isTransactionRecoveryEnabled() {
		return this.transactionRecoveryEnabled;
	}

	/** Set to true to enable recording of all transaction activity and replay the transaction automatically in case
	 * of a connection failure.
	 * @param transactionRecoveryEnabled the transactionRecoveryEnabled status to set
	 */
	public void setTransactionRecoveryEnabled(boolean transactionRecoveryEnabled) {
		this.transactionRecoveryEnabled = transactionRecoveryEnabled;
	}

	/** After attempting to acquire a connection and failing, try to connect these many times before giving up. Default 5. 
	 * @return the acquireRetryAttempts value
	 */
	public int getAcquireRetryAttempts() {
		return this.acquireRetryAttempts;
	}

	/** After attempting to acquire a connection and failing, try to connect these many times before giving up. Default 5. 
	 * @param acquireRetryAttempts the acquireRetryAttempts to set
	 */
	public void setAcquireRetryAttempts(int acquireRetryAttempts) {
		this.acquireRetryAttempts = acquireRetryAttempts;
	}

	/** Sets the connection hook class name. Consider using setConnectionHook() instead.
	 * @param connectionHookClassName the connectionHook class name to set
	 */
	public void setConnectionHookClassName(String connectionHookClassName) {
		this.connectionHookClassName = checkNotNull(connectionHookClassName);
			Object hookClass;
			try {
				hookClass = loadClass(connectionHookClassName).newInstance();
				this.connectionHook = (ConnectionHook) hookClass;
			} catch (Exception e) {
				logger.error("Unable to create an instance of the connection hook class ("+connectionHookClassName+")");
				this.connectionHook = null;
			} 
	}

	/** Returns the connection hook class name as passed via the setter
	 * @return the connectionHookClassName.
	 */
	public String getConnectionHookClassName() {
		return this.connectionHookClassName;
	}


	/** Return true if JMX is disabled.
	 * @return the disableJMX.
	 */
	public boolean isDisableJMX() {
		return this.disableJMX;
	}

	/** Set to true to disable JMX.
	 * @param disableJMX the disableJMX to set
	 */
	public void setDisableJMX(boolean disableJMX) {
		this.disableJMX = disableJMX;
	}

	/** Returns the bean being used to return a connection.
	 * @return the datasourceBean that was set.
	 */
	public DataSource getDatasourceBean() {
		return this.datasourceBean;
	}

	/** If set, use datasourceBean.getConnection() to obtain a new connection instead of Driver.getConnection().
	 * @param datasourceBean the datasourceBean to set
	 */
	public void setDatasourceBean(DataSource datasourceBean) {
		this.datasourceBean = datasourceBean;
	}


	/** Deprecated.
	 * @deprecated Use {@link #getQueryExecuteTimeLimitInMs()} instead.
	 * @return the queryTimeLimit
	 */
	@Deprecated
	public long getQueryExecuteTimeLimit() {
		logger.warn("Please use getQueryExecuteTimeLimitInMs in place of getQueryExecuteTimeLimit. This method has been deprecated.");
		return this.queryExecuteTimeLimitInMs;
	}

	/** Queries taking longer than this limit to execute are logged.  
	 * @param queryExecuteTimeLimit the limit to set in milliseconds.
	 * @deprecated Use {@link #setQueryExecuteTimeLimitInMs(long)} instead.
	 */
	@Deprecated
	public void setQueryExecuteTimeLimit(int queryExecuteTimeLimit) {
		logger.warn("Please use setQueryExecuteTimeLimitInMs in place of setQueryExecuteTimeLimit. This method has been deprecated.");
		setQueryExecuteTimeLimit(queryExecuteTimeLimit, TimeUnit.MILLISECONDS);
	}

	/** Return the query execute time limit.
	 * @return the queryTimeLimit
	 */
	public long getQueryExecuteTimeLimitInMs() {
		return this.queryExecuteTimeLimitInMs;
	}

	/**
	 * Returns the queryExecuteTimeLimit setting with the specified granularity.
	 * 
	 * @param timeUnit time granularity
	 * @return queryExecuteTimeLimit period 
	 */
	public long getQueryExecuteTimeLimit(TimeUnit timeUnit) {
		return timeUnit.convert(this.queryExecuteTimeLimitInMs, TimeUnit.MILLISECONDS);
	}


	/** Queries taking longer than this limit to execute are logged.  
	 * @param queryExecuteTimeLimit the limit to set in milliseconds.
	 */
	public void setQueryExecuteTimeLimitInMs(long queryExecuteTimeLimit) {
		setQueryExecuteTimeLimit(queryExecuteTimeLimit, TimeUnit.MILLISECONDS);
	}

	/** Queries taking longer than this limit to execute are logged.  
	 * @param queryExecuteTimeLimit the limit to set in milliseconds.
	 * @param timeUnit 
	 */
	public void setQueryExecuteTimeLimit(long queryExecuteTimeLimit, TimeUnit timeUnit) {
		this.queryExecuteTimeLimitInMs = TimeUnit.MILLISECONDS.convert(queryExecuteTimeLimit, timeUnit);
	}



	/** Returns the pool watch connection threshold value.
	 * @return the poolAvailabilityThreshold currently set.
	 */
	public int getPoolAvailabilityThreshold() {
		return this.poolAvailabilityThreshold;
	}

	/** Sets the Pool Watch thread threshold.
	 * 
	 * The pool watch thread attempts to maintain a number of connections always available (between minConnections and maxConnections). This
	 * value sets the percentage value to maintain. For example, setting it to 20 means that if the following condition holds:
	 * Free Connections / MaxConnections < poolAvailabilityThreshold
	 * 
	 * new connections will be created. In other words, it tries to keep at least 20% of the pool full of connections. Setting the value
	 * to zero will make the pool create new connections when it needs them but it also means your application may have to wait for new
	 * connections to be obtained at times. 
	 * 
	 * Default: 0.
	 *  
	 * @param poolAvailabilityThreshold the poolAvailabilityThreshold to set
	 */
	public void setPoolAvailabilityThreshold(int poolAvailabilityThreshold) {
		this.poolAvailabilityThreshold = poolAvailabilityThreshold;
	}

	/** Returns true if connection tracking has been disabled.
	 * @return the disableConnectionTracking
	 */
	public boolean isDisableConnectionTracking() {
		return this.disableConnectionTracking;
	}

	/** If set to true, the pool will not monitor connections for proper closure. Enable this option if you only ever obtain
	 * your connections via a mechanism that is guaranteed to release the connection back to the pool (eg Spring's jdbcTemplate, 
	 * some kind of transaction manager, etc).
	 * 
	 * @param disableConnectionTracking set to true to disable. Default: false.
	 */
	public void setDisableConnectionTracking(boolean disableConnectionTracking) {
		this.disableConnectionTracking = disableConnectionTracking;
	}

	/** Deprecated.
	 * 
	 * @deprecated Use {@link #getConnectionTimeoutInMs()} instead.
	 * @return the connectionTimeout
	 */
	@Deprecated
	public long getConnectionTimeout() {
		logger.warn("Please use getConnectionTimeoutInMs in place of getConnectionTimeout. This method has been deprecated.");
		return this.connectionTimeoutInMs;
	}

	/** Deprecated.
	 * 
	 * @deprecated Use {@link #setConnectionTimeoutInMs(long)} instead.
	 * @param connectionTimeout the connectionTimeout to set
	 */
	@Deprecated
	public void setConnectionTimeout(long connectionTimeout) {
		logger.warn("Please use setConnectionTimeoutInMs in place of setConnectionTimeout. This method has been deprecated.");
		this.connectionTimeoutInMs = connectionTimeout;
	}

	/** Returns the maximum time (in milliseconds) to wait before a call to getConnection is timed out.
	 * @return the connectionTimeout
	 */
	public long getConnectionTimeoutInMs() {
		return this.connectionTimeoutInMs;
	}

	/**
	 * Returns the connectionTimeout with the specified granularity.
	 * 
	 * @param timeUnit time granularity
	 * @return connectionTimeout period 
	 */
	public long getConnectionTimeout(TimeUnit timeUnit) {
		return timeUnit.convert(this.connectionTimeoutInMs, TimeUnit.MILLISECONDS);
	}


	/** Sets the maximum time (in milliseconds) to wait before a call to getConnection is timed out.
	 * 
	 *  Setting this to zero is similar to setting it to Long.MAX_VALUE
	 * 
	 * Default: 0 ( = wait forever )
	 * 
	 * @param connectionTimeoutinMs the connectionTimeout to set
	 */
	public void setConnectionTimeoutInMs(long connectionTimeoutinMs) {
		setConnectionTimeout(connectionTimeoutinMs, TimeUnit.MILLISECONDS);
	}


	/** Sets the maximum time to wait before a call to getConnection is timed out.
	 * 
	 * Setting this to zero is similar to setting it to Long.MAX_VALUE
	 * 
	 * @param connectionTimeout
	 * @param timeUnit the unit of the connectionTimeout argument
	 */
	public void setConnectionTimeout(long connectionTimeout, TimeUnit timeUnit) {
		this.connectionTimeoutInMs = TimeUnit.MILLISECONDS.convert(connectionTimeout, timeUnit);
	}

	/** Returns the currently configured driver properties.
	 * @return the driverProperties handle
	 */
	public Properties getDriverProperties() {
		return this.driverProperties;
	}

	/** Sets properties that will be passed on to the driver. 
	 * 
	 * The properties handle should contain a list of arbitrary string tag/value pairs 
	 * as connection arguments; normally at least a "user" and "password" property 
	 * should be included. Failure to include the user or password properties will make the 
	 * pool copy the values given in config.setUsername(..) and config.setPassword(..).  
	 * 
	 * Note that the pool will make a copy of these properties so as not to risk attempting to
	 * create a connection later on with different settings.
	 * 
	 * @param driverProperties the driverProperties to set
	 */
	public void setDriverProperties(Properties driverProperties) {
			// make a copy of the properties so that we don't attempt to create more connections
			// later on and are possibly surprised by having different urls/usernames/etc
			this.driverProperties = new Properties();
			this.driverProperties.putAll(checkNotNull(driverProperties));
	}

	/** Deprecated.
	 * @deprecated Use {@link #getCloseConnectionWatchTimeoutInMs()} instead
	 * @return the watchTimeout currently set.
	 */
	@Deprecated
	public long getCloseConnectionWatchTimeout() {
		logger.warn("Please use getCloseConnectionWatchTimeoutInMs in place of getCloseConnectionWatchTimeout. This method has been deprecated.");
		return this.closeConnectionWatchTimeoutInMs;
	}

	/** Deprecated.
	 * @param closeConnectionWatchTimeout the watchTimeout to set
	 * @deprecated Use {@link #setCloseConnectionWatchTimeoutInMs(long)} instead
	 */
	@Deprecated
	public void setCloseConnectionWatchTimeout(long closeConnectionWatchTimeout) {
		logger.warn("Please use setCloseConnectionWatchTimeoutInMs in place of setCloseConnectionWatchTimeout. This method has been deprecated.");
		setCloseConnectionWatchTimeoutInMs(closeConnectionWatchTimeout);
	}

	/** Returns the no of ms to wait when close connection watch threads are enabled. 0 = wait forever.
	 * @return the watchTimeout currently set.
	 */
	public long getCloseConnectionWatchTimeoutInMs() {
		return this.closeConnectionWatchTimeoutInMs;
	}

	/**
	 * Returns the closeConnectionWatchTimeout with the specified granularity.
	 * 
	 * @param timeUnit time granularity
	 * @return closeConnectionWatchTimeout period 
	 */
	public long getCloseConnectionWatchTimeout(TimeUnit timeUnit) {
		return timeUnit.convert(this.closeConnectionWatchTimeoutInMs, TimeUnit.MILLISECONDS);
	}


	/** Sets the no of ms to wait when close connection watch threads are enabled. 0 = wait forever.
	 * @param closeConnectionWatchTimeout the watchTimeout to set
	 */
	public void setCloseConnectionWatchTimeoutInMs(long closeConnectionWatchTimeout) {
		setCloseConnectionWatchTimeout(closeConnectionWatchTimeout, TimeUnit.MILLISECONDS);
	}

	/** Sets the time to wait when close connection watch threads are enabled. 0 = wait forever.
	 * @param closeConnectionWatchTimeout the watchTimeout to set
	 * @param timeUnit Time granularity
	 */
	public void setCloseConnectionWatchTimeout(long closeConnectionWatchTimeout, TimeUnit timeUnit) {
		this.closeConnectionWatchTimeoutInMs = TimeUnit.MILLISECONDS.convert(closeConnectionWatchTimeout, timeUnit);
	}

	/**
	 * Returns the statementHelperThreads field. Deprecated.
	 * @return statementHelperThreads
	 */
	@Deprecated
	public int getStatementReleaseHelperThreads() {
		return this.statementReleaseHelperThreads;
	}

	/**
	 * Depracated method. Sets number of statement helper threads to create that will handle releasing a statement.
	 *
	 * When this value is set to zero, the application thread is blocked until the pool and JDBC driver are able to close off the statement. 
	 * 
	 * When a non-zero value is set, the pool will create threads that will take care of closing off the statement asychronously to the application via the release helper 
	 * threads).
	 * 
	 * Useful when your application is opening up lots of statements otherwise will probably slow things down.
	 * 
	 * @param statementReleaseHelperThreads no to release 
	 */	
	@Deprecated
	public void setStatementReleaseHelperThreads(int statementReleaseHelperThreads) {
		logger.warn("statementReleaseHelperThreads has been deprecated -- it tends to slow down your application more.");
		this.statementReleaseHelperThreads = statementReleaseHelperThreads;
	}


	/**
	 * Deprecated. Please use {@link #getMaxConnectionAgeInSeconds()} instead.

	 * @return maxConnectionAge
	 * @deprecated Please use {@link #getMaxConnectionAgeInSeconds()} instead.
	 */
	@Deprecated
	public long getMaxConnectionAge() {
		logger.warn("Please use getMaxConnectionAgeInSeconds in place of getMaxConnectionAge. This method has been deprecated.");
		return this.maxConnectionAgeInSeconds;
	}

	/**
	 * Returns the maxConnectionAge field in seconds
	 * @return maxConnectionAge
	 */
	public long getMaxConnectionAgeInSeconds() {
		return this.maxConnectionAgeInSeconds;
	}

	/**
	 * Returns the maxConnectionAge with the specified granularity.
	 * 
	 * @param timeUnit time granularity
	 * @return maxConnectionAge period 
	 */
	public long getMaxConnectionAge(TimeUnit timeUnit) {
		return timeUnit.convert(this.maxConnectionAgeInSeconds, TimeUnit.SECONDS);
	}


	/**
	 * Deprecated. Use {{@link #setMaxConnectionAgeInSeconds(long)} instead.
	 * 
	 * @param maxConnectionAgeInSeconds the maxConnectionAge to set
	 * @deprecated Please use {{@link #setMaxConnectionAgeInSeconds(long)} instead.
	 */
	@Deprecated
	public void setMaxConnectionAge(long maxConnectionAgeInSeconds) {
		logger.warn("Please use setmaxConnectionAgeInSecondsInSeconds in place of setMaxConnectionAge. This method has been deprecated.");
		this.maxConnectionAgeInSeconds = maxConnectionAgeInSeconds;
	}

	/**
	 * Sets the maxConnectionAge in seconds. Any connections older than this setting will be closed
	 * off whether it is idle or not. Connections currently in use will not be affected until they
	 * are returned to the pool.
	 * 
	 * @param maxConnectionAgeInSeconds the maxConnectionAge to set
	 */
	public void setMaxConnectionAgeInSeconds(long maxConnectionAgeInSeconds) {
		setMaxConnectionAge(maxConnectionAgeInSeconds, TimeUnit.SECONDS);
	}

	/**
	 * Sets the maxConnectionAge. Any connections older than this setting will be closed
	 * off whether it is idle or not. Connections currently in use will not be affected until they
	 * are returned to the pool.
	 * 
	 * @param maxConnectionAge the maxConnectionAge to set.
	 * @param timeUnit the unit of the maxConnectionAge argument.
	 */
	public void setMaxConnectionAge(long maxConnectionAge, TimeUnit timeUnit) {
		this.maxConnectionAgeInSeconds = TimeUnit.SECONDS.convert(maxConnectionAge, timeUnit);
	}
	/**
	 * Returns the configFile field.
	 * @return configFile
	 */
	public String getConfigFile() {
		return this.configFile;
	}

	/**
	 * Sets the configFile. If configured, this will cause the pool to initialise using the
	 * config file in the same way as if calling new BoneCPConfig(filename).
	 * @param configFile the configFile to set
	 */
	public void setConfigFile(String configFile) {
		this.configFile = checkNotNull(configFile);
	}

	/**
	 * Returns the serviceOrder field.
	 * @return serviceOrder
	 */
	public String getServiceOrder() {
		return this.serviceOrder;
	}

	/**
	 * Sets the queue serviceOrder. Values currently understood are FIFO and LIFO. 
	 * @param serviceOrder the serviceOrder to set
	 */
	public void setServiceOrder(String serviceOrder) {
		this.serviceOrder = checkNotNull(serviceOrder);
	}

	/**
	 * Returns the statisticsEnabled field.
	 * @return statisticsEnabled
	 */
	public boolean isStatisticsEnabled() {
		return this.statisticsEnabled;
	}

	/**
	 * If set to true, keep track of some more statistics for exposure via JMX. Will slow down the pool
	 * operation. 
	 * @param statisticsEnabled set to true to enable
	 */
	public void setStatisticsEnabled(boolean statisticsEnabled) {
		this.statisticsEnabled = statisticsEnabled;
	}

	/**
	 * Returns the defaultAutoCommit field.
	 * @return defaultAutoCommit
	 */
	public boolean getDefaultAutoCommit() {
		return this.defaultAutoCommit;
	}

	/**
	 * Sets the defaultAutoCommit setting for newly created connections. If not set, use driver default.
	 * @param defaultAutoCommit the defaultAutoCommit to set
	 */
	public void setDefaultAutoCommit(boolean defaultAutoCommit) {
		this.defaultAutoCommit = defaultAutoCommit;
	}

	/**
	 * Returns the defaultReadOnly field.
	 * @return defaultReadOnly
	 */
	public Boolean getDefaultReadOnly() {
		return this.defaultReadOnly;
	}

	/**
	 * Sets the defaultReadOnly setting for newly created connections. If not set, use driver default.
	 * @param defaultReadOnly the defaultReadOnly to set
	 */
	public void setDefaultReadOnly(Boolean defaultReadOnly) {
		this.defaultReadOnly = checkNotNull(defaultReadOnly);
	}


	/**
	 * Returns the defaultCatalog field.
	 * @return defaultCatalog
	 */
	public String getDefaultCatalog() {
		return this.defaultCatalog;
	}

	/**
	 * Sets the defaultCatalog setting for newly created connections. If not set, use driver default.
	 * @param defaultCatalog the defaultCatalog to set
	 */
	public void setDefaultCatalog(String defaultCatalog) {
		this.defaultCatalog = checkNotNull(defaultCatalog);
	}

	/**
	 * Returns the defaultTransactionIsolation field.
	 * @return defaultTransactionIsolation
	 */
	public String getDefaultTransactionIsolation() {
		return this.defaultTransactionIsolation;
	}

	/**
	 * Sets the defaultTransactionIsolation. Should be set to one of: NONE, READ_COMMITTED, READ_UNCOMMITTED,  
	 * REPEATABLE_READ or SERIALIZABLE. If not set, will use driver default. 
	 * @param defaultTransactionIsolation the defaultTransactionIsolation to set
	 */
	public void setDefaultTransactionIsolation(String defaultTransactionIsolation) {
		this.defaultTransactionIsolation = checkNotNull(defaultTransactionIsolation);
	}

	/**
	 * Returns the defaultTransactionIsolationValue field.
	 * @return defaultTransactionIsolationValue
	 */
	protected int getDefaultTransactionIsolationValue() {
		return this.defaultTransactionIsolationValue;
	}

	/**
	 * Sets the defaultTransactionIsolationValue.
	 * @param defaultTransactionIsolationValue the defaultTransactionIsolationValue to set
	 */
	protected void setDefaultTransactionIsolationValue(int defaultTransactionIsolationValue) {
		this.defaultTransactionIsolationValue = defaultTransactionIsolationValue;
	}

	/**
	 * Default constructor. Attempts to fill settings in this order:
	 * 1. bonecp-default-config.xml file, usually found in the pool jar
	 * 2. bonecp-config.xml file, usually found in your application's classpath
	 * 3. Other hardcoded defaults in BoneCPConfig class.
	 */
	public BoneCPConfig(){
		// try to load the default config file, if available from somewhere in the classpath
		loadProperties("bonecp-default-config.xml");
		// try to override with app specific config, if available
		loadProperties("bonecp-config.xml");
	}

	/** Creates a new config using the given properties.
	 * @param props properties to set.
	 * @throws Exception on error
	 */
	public BoneCPConfig(Properties props) throws Exception {
		this();
		this.setProperties(checkNotNull(props));
	}


	/** Initialize the configuration by loading bonecp-config.xml containing the settings. 
	 * @param sectionName section to load
	 * @throws Exception on parse errors
	 */
	public BoneCPConfig(String sectionName) throws Exception{
		this(BoneCPConfig.class.getResourceAsStream("/bonecp-config.xml"), checkNotNull(sectionName));
	}

	/** Initialise the configuration by loading an XML file containing the settings. 
	 * @param xmlConfigFile file to load
	 * @param sectionName section to load
	 * @throws Exception 
	 */
	public BoneCPConfig(InputStream xmlConfigFile, String sectionName) throws Exception{
		this();
		setXMLProperties(xmlConfigFile, checkNotNull(sectionName));
	}

	/**
	 * @param xmlConfigFile
	 * @param sectionName
	 * @throws Exception
	 */
	private void setXMLProperties(InputStream xmlConfigFile, String sectionName)
			throws Exception {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db;
		// ugly XML parsing, but this is built-in the JDK.
		try {
			db = dbf.newDocumentBuilder();
			Document doc = db.parse(xmlConfigFile);
			doc.getDocumentElement().normalize();

			// get the default settings
			Properties settings = parseXML(doc, null);
			if (sectionName != null){
				// override with custom settings
				settings.putAll(parseXML(doc, sectionName));
			}
			// set the properties
			setProperties(settings);

		} catch (Exception e) {
			throw e;
		} finally {
			if (xmlConfigFile != null){ // safety
				xmlConfigFile.close();
			}
		}
	}


	/** Lowercases the first character.
	 * @param name
	 * @return the same string with the first letter in lowercase
	 */
	private String lowerFirst(String name) {
		return name.substring(0, 1).toLowerCase()+name.substring(1);
	}


	/**
	 * Sets the properties by reading off entries in the given parameter (where each key is equivalent to the field name) 
	 * @param props Parameter list to set
	 * @throws Exception on error
	 */
	public void setProperties(Properties props) throws Exception {
		// Use reflection to read in all possible properties of int, String or boolean.
		for (Method method: BoneCPConfig.class.getDeclaredMethods()){
			String tmp = null;
			if (method.getName().startsWith("is")){
				tmp = lowerFirst(method.getName().substring(2));
			} else if (method.getName().startsWith("set")){
				tmp = lowerFirst(method.getName().substring(3));
			} else {
				continue;
			}

			if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(int.class)){
				String val = props.getProperty(tmp);
				if (val == null){
					val = props.getProperty("bonecp."+tmp); // hibernate provider style
				}
				if (val != null) {
					try{
						method.invoke(this, Integer.parseInt(val));
					} catch (NumberFormatException e){
						// do nothing, use the default value
					}
				}
			} else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(long.class)){
				String val = props.getProperty(tmp);
				if (val == null){
					val = props.getProperty("bonecp."+tmp); // hibernate provider style
				}
				if (val != null) {
					try{
						method.invoke(this, Long.parseLong(val));
					} catch (NumberFormatException e){
						// do nothing, use the default value
					}
				}
			} else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(String.class)){
				String val = props.getProperty(tmp);
				if (val == null){
					val = props.getProperty("bonecp."+tmp); // hibernate provider style
				}
				if (val != null) {
					method.invoke(this, val);
				}
			} if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(boolean.class)){
				String val = props.getProperty(tmp);
				if (val == null){
					val = props.getProperty("bonecp."+tmp); // hibernate provider style
				}
				if (val != null) {
					method.invoke(this, Boolean.parseBoolean(val));
				}
			}
		}
	}

	/** Parses the given XML doc to extract the properties and return them into a java.util.Properties.
	 * @param doc to parse
	 * @param sectionName which section to extract
	 * @return Properties map
	 */
	private Properties parseXML(Document doc, String sectionName) {
		int found = -1;
		Properties results = new Properties();
		NodeList config = null;
		if (sectionName == null){
			config = doc.getElementsByTagName("default-config");
			found = 0;
		} else {
			config = doc.getElementsByTagName("named-config");
			if(config != null && config.getLength() > 0) {
				for (int i = 0; i < config.getLength(); i++) {
					Node node = config.item(i);
					if(node.getNodeType() == Node.ELEMENT_NODE ){
						NamedNodeMap attributes = node.getAttributes();
						if (attributes != null && attributes.getLength() > 0){
							Node name = attributes.getNamedItem("name");
							if (name.getNodeValue().equalsIgnoreCase(sectionName)){
								found = i;
								break;
							}
						}
					}
				}
			}

			if (found == -1){
				config = null;
				logger.warn("Did not find "+sectionName+" section in config file. Reverting to defaults.");
			}
		}

		if(config != null && config.getLength() > 0) {
			Node node = config.item(found);
			if(node.getNodeType() == Node.ELEMENT_NODE){
				Element elementEntry = (Element)node;
				NodeList childNodeList = elementEntry.getChildNodes();
				for (int j = 0; j < childNodeList.getLength(); j++) {
					Node node_j = childNodeList.item(j);
					if (node_j.getNodeType() == Node.ELEMENT_NODE) {
						Element piece = (Element) node_j;
						NamedNodeMap attributes = piece.getAttributes();
						if (attributes != null && attributes.getLength() > 0){
							results.put(attributes.item(0).getNodeValue(), piece.getTextContent());
						}
					}
				}
			}
		}

		return results;
	}

	/**
	 * Returns the current externalAuth setting.
	 * @return externalAuth setting
	 */
	public boolean isExternalAuth() {
		return this.externalAuth;
	}


	/**
	 * If set to true, no attempts at passing in a username/password will be attempted
	 * when trying to obtain a raw (driver) connection. Useful for cases when you already have
	 * another mechanism on authentication eg NTLM.
	 * 
	 * @param externalAuth True to enable external auth.
	 */
	public void setExternalAuth(boolean externalAuth) {
		this.externalAuth = externalAuth;
	}

	/**
	 * Performs validation on the config object.
	 *
	 */
	public void sanitize(){
		if (this.configFile != null){
			loadProperties(this.configFile);
		}

		if (this.poolStrategy == null || !(this.poolStrategy.equalsIgnoreCase("DEFAULT") || this.poolStrategy.equalsIgnoreCase("CACHED"))){
			logger.warn("Unrecognised pool strategy. Allowed values are DEFAULT and CACHED. Setting to DEFAULT.");
			this.poolStrategy = "DEFAULT";
		} 

		this.poolStrategy = this.poolStrategy.toUpperCase();

		if ((this.poolAvailabilityThreshold < 0) || (this.poolAvailabilityThreshold > 100)){
			this.poolAvailabilityThreshold = 20;
		}

		if (this.defaultTransactionIsolation != null){
			this.defaultTransactionIsolation = this.defaultTransactionIsolation.trim().toUpperCase();

			if (this.defaultTransactionIsolation.equals("NONE")){
				this.defaultTransactionIsolationValue = Connection.TRANSACTION_NONE;
			} else if (this.defaultTransactionIsolation.equals("READ_COMMITTED") || this.defaultTransactionIsolation.equals("READ COMMITTED")){
				this.defaultTransactionIsolationValue = Connection.TRANSACTION_READ_COMMITTED;
			} else if (this.defaultTransactionIsolation.equals("REPEATABLE_READ") || this.defaultTransactionIsolation.equals("REPEATABLE READ")){
				this.defaultTransactionIsolationValue = Connection.TRANSACTION_REPEATABLE_READ;
			} else if (this.defaultTransactionIsolation.equals("READ_UNCOMMITTED") || this.defaultTransactionIsolation.equals("READ UNCOMMITTED")){
				this.defaultTransactionIsolationValue = Connection.TRANSACTION_READ_UNCOMMITTED;
			} else if (this.defaultTransactionIsolation.equals("SERIALIZABLE")){
				this.defaultTransactionIsolationValue = Connection.TRANSACTION_SERIALIZABLE;
			} else {
				logger.warn("Unrecognized defaultTransactionIsolation value. Using driver default.");
				this.defaultTransactionIsolationValue = -1;
			}
		}
		if (this.maxConnectionsPerPartition < 1) {
			logger.warn("Max Connections < 1. Setting to 20");
			this.maxConnectionsPerPartition = 20;
		}
		if (this.minConnectionsPerPartition < 0) {
			logger.warn("Min Connections < 0. Setting to 1");
			this.minConnectionsPerPartition = 1;
		}

		if (this.minConnectionsPerPartition > this.maxConnectionsPerPartition) {
			logger.warn("Min Connections > max connections");
			this.minConnectionsPerPartition = this.maxConnectionsPerPartition;
		}
		if (this.acquireIncrement <= 0) {
			logger.warn("acquireIncrement <= 0. Setting to 1.");
			this.acquireIncrement = 1;
		}
		if (this.partitionCount < 1) {
			logger.warn("partitions < 1! Setting to 1");
			this.partitionCount = 1;
		}

		if (this.releaseHelperThreads < 0){
			logger.warn("releaseHelperThreads < 0! Setting to 0");
			this.releaseHelperThreads = 0;
		}

		if (this.statementReleaseHelperThreads < 0){
			logger.warn("statementReleaseHelperThreads < 0! Setting to 0");
			this.statementReleaseHelperThreads = 0;
		}

		if (this.statementsCacheSize < 0) {
			logger.warn("preparedStatementsCacheSize < 0! Setting to 0");
			this.statementsCacheSize = 0;
		}

		if (this.acquireRetryDelayInMs <= 0) {
			this.acquireRetryDelayInMs = 1000;
		}

		if (!this.externalAuth && 
				(this.datasourceBean == null) && 
				this.driverProperties == null 
				&& (this.jdbcUrl == null || this.jdbcUrl.trim().equals(""))){
			logger.warn("JDBC url was not set in config!");
		}

		if (!this.externalAuth && this.datasourceBean == null && this.driverProperties == null && 
				(this.username == null || this.username.trim().equals(""))){
			logger.warn("JDBC username was not set in config!");
		}

		if (!this.externalAuth && (this.datasourceBean == null) && (this.driverProperties == null) && (this.password == null)){ 
			logger.warn("JDBC password was not set in config!");
		}


		// if not external auth and no datasource and we have driver properties set...
		if (!this.externalAuth && this.datasourceBean == null && this.driverProperties != null){
			if ((this.driverProperties.get(USER) == null) && this.username == null){
				logger.warn("JDBC username not set in driver properties and not set in pool config either");
			} else if ((this.driverProperties.get(USER) == null) && this.username != null){
				logger.warn("JDBC username not set in driver properties, copying it from pool config");
				this.driverProperties.setProperty(USER, this.username);
			} else if (this.username != null && !this.driverProperties.get(USER).equals(this.username)){
				logger.warn("JDBC username set in driver properties does not match the one set in the pool config.  Overriding it with pool config.");
				this.driverProperties.setProperty(USER, this.username);
			}  
		}

		// if no datasource and we have driver properties set...
		if (!this.externalAuth && this.datasourceBean == null && this.driverProperties != null){
			if ((this.driverProperties.get(PASSWORD) == null) && this.password == null){
				logger.warn("JDBC password not set in driver properties and not set in pool config either");
			} else if ((this.driverProperties.get(PASSWORD) == null) && this.password != null){
				logger.warn("JDBC password not set in driver properties, copying it from pool config");
				this.driverProperties.setProperty(PASSWORD, this.password);
			} else if (this.password != null && !this.driverProperties.get(PASSWORD).equals(this.password)){
				logger.warn("JDBC password set in driver properties does not match the one set in the pool config. Overriding it with pool config.");
				this.driverProperties.setProperty(PASSWORD, this.password);
			}



			// maintain sanity between the two states 
			this.username = this.driverProperties.getProperty(USER);
			this.password = this.driverProperties.getProperty(PASSWORD);
		}

		if (this.username != null){
			this.username = this.username.trim();
		}
		if (this.jdbcUrl != null){
			this.jdbcUrl = this.jdbcUrl.trim();
		}
		if (this.password != null){
			this.password = this.password.trim();
		}

		if (this.connectionTestStatement != null) { 
			this.connectionTestStatement = this.connectionTestStatement.trim();
		}

		this.serviceOrder = this.serviceOrder.toUpperCase();

		if (!(this.serviceOrder.equals("FIFO") || this.serviceOrder.equals("LIFO"))){
			logger.warn("Queue service order is not set to FIFO or LIFO. Defaulting to FIFO.");
			this.serviceOrder = "FIFO";
		}
		
		if (this.logStatementsEnabled && !logger.isDebugEnabled()){
			logger.warn("LogStatementsEnabled is set to true, but log4j level is not set at DEBUG. Disabling statement logging.");
			logStatementsEnabled = false;
			
		}

	}

	/**
	 * Loads the given properties file using the classloader.
	 * @param filename Config filename to load
	 * 
	 */
	protected void loadProperties(String filename) {
		ClassLoader thclassLoader = Thread.currentThread().getContextClassLoader();
		if (thclassLoader != null){
			URL url = thclassLoader.getResource(filename);
			if (url != null){
				try {
					this.setXMLProperties(url.openStream(), null);
				} catch (Exception e) {
					// do nothing
				} 
			}
		}
	}

	@Override
	public String toString() {
		String result = null;
		if (this.datasourceBean != null){
			result = String.format(CONFIG_DS_TOSTRING, this.partitionCount, this.maxConnectionsPerPartition, this.minConnectionsPerPartition, 
					getIdleMaxAgeInMinutes(), 
					getIdleConnectionTestPeriodInMinutes(), this.poolStrategy);
		} else {
			result = String.format(CONFIG_TOSTRING, this.jdbcUrl,
					this.username, this.partitionCount, this.maxConnectionsPerPartition, this.minConnectionsPerPartition, 
					getIdleMaxAgeInMinutes(), 
					getIdleConnectionTestPeriodInMinutes(), this.poolStrategy);
		}

		return result;
	}

	/** Loads the given class, respecting the given classloader.
	 * @param clazz class to load
	 * @return Loaded class
	 * @throws ClassNotFoundException
	 */
	protected Class<?> loadClass(String clazz) throws ClassNotFoundException {
		if (this.classLoader == null){
			return Class.forName(clazz);
		}

		return Class.forName(clazz, true, this.classLoader);

	}
	/** Returns the currently active classloader. 
	 * @return the classLoader
	 */
	public ClassLoader getClassLoader() {
		return this.classLoader;
	}

	/** Sets the classloader to use to load JDBC driver and hooks (set to null to use default).
	 * @param classLoader the classLoader to set
	 */ 
	public void setClassLoader(ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	@Override
	public BoneCPConfig clone() throws CloneNotSupportedException {

		BoneCPConfig clone = (BoneCPConfig)super.clone();
		Field[] fields = this.getClass().getDeclaredFields();
		for (Field field: fields){
			try {
				field.set(clone, field.get(this));
			} catch (Exception e) {
				// should never happen
			}
		}
		return clone;
	}


	/** Returns true if this instance has the same config as a given config.
	 * @param that
	 * @return true if the instance has the same config, false otherwise.
	 */
	public boolean hasSameConfiguration(BoneCPConfig that){
		if ( that != null && Objects.equal(this.acquireIncrement, that.getAcquireIncrement())
				&& Objects.equal(this.acquireRetryDelayInMs, that.getAcquireRetryDelayInMs())
				&& Objects.equal(this.closeConnectionWatch, that.isCloseConnectionWatch())
				&& Objects.equal(this.logStatementsEnabled, that.isLogStatementsEnabled())
				&& Objects.equal(this.connectionHook, that.getConnectionHook())
				&& Objects.equal(this.connectionTestStatement, that.getConnectionTestStatement())
				&& Objects.equal(this.idleConnectionTestPeriodInSeconds, that.getIdleConnectionTestPeriod(TimeUnit.SECONDS))
				&& Objects.equal(this.idleMaxAgeInSeconds, that.getIdleMaxAge(TimeUnit.SECONDS))
				&& Objects.equal(this.initSQL, that.getInitSQL())
				&& Objects.equal(this.jdbcUrl, that.getJdbcUrl())
				&& Objects.equal(this.maxConnectionsPerPartition, that.getMaxConnectionsPerPartition())
				&& Objects.equal(this.minConnectionsPerPartition, that.getMinConnectionsPerPartition())
				&& Objects.equal(this.partitionCount, that.getPartitionCount())
				&& Objects.equal(this.releaseHelperThreads, that.getReleaseHelperThreads())
				&& Objects.equal(this.statementsCacheSize, that.getStatementsCacheSize())
				&& Objects.equal(this.username, that.getUsername())
				&& Objects.equal(this.password, that.getPassword())
				&& Objects.equal(this.lazyInit, that.isLazyInit())
				&& Objects.equal(this.transactionRecoveryEnabled, that.isTransactionRecoveryEnabled())
				&& Objects.equal(this.acquireRetryAttempts, that.getAcquireRetryAttempts())
				&& Objects.equal(this.statementReleaseHelperThreads, that.getStatementReleaseHelperThreads())
				&& Objects.equal(this.closeConnectionWatchTimeoutInMs, that.getCloseConnectionWatchTimeout())
				&& Objects.equal(this.connectionTimeoutInMs, that.getConnectionTimeoutInMs())
				&& Objects.equal(this.datasourceBean, that.getDatasourceBean())
				&& Objects.equal(this.getQueryExecuteTimeLimitInMs(), that.getQueryExecuteTimeLimitInMs())
				&& Objects.equal(this.poolAvailabilityThreshold, that.getPoolAvailabilityThreshold())
				&& Objects.equal(this.poolName, that.getPoolName())
				&& Objects.equal(this.disableConnectionTracking, that.isDisableConnectionTracking())

				){
			return true;
		} 

		return false;
	}

	/**
	 * Returns the deregisterDriverOnClose setting.
	 * @return deregisterDriverOnClose
	 */
	public boolean isDeregisterDriverOnClose() {
		return this.deregisterDriverOnClose;
	}


	/**
	 * If set to true, try to unregister the JDBC driver when pool is shutdown. 
	 * @param deregisterDriverOnClose the deregisterDriverOnClose setting.
	 */
	public void setDeregisterDriverOnClose(boolean deregisterDriverOnClose) {
		this.deregisterDriverOnClose = deregisterDriverOnClose;
	}

	/**
	 * Returns the nullOnConnectionTimeout field.
	 * @return nullOnConnectionTimeout
	 */
	public boolean isNullOnConnectionTimeout() {
		return this.nullOnConnectionTimeout;
	}


	/**
	 * Sets the nullOnConnectionTimeout.  
	 * 
	 * If true, return null on connection timeout rather than throw an exception. This performs
	 * better but must be handled differently in your application. This only
	 * makes sense when using the connectionTimeout config option. 
	 * @param nullOnConnectionTimeout the nullOnConnectionTimeout to set
	 */
	public void setNullOnConnectionTimeout(boolean nullOnConnectionTimeout) {
		this.nullOnConnectionTimeout = nullOnConnectionTimeout;
	}

	/**
	 * Returns the resetConnectionOnClose setting.
	 * @return resetConnectionOnClose
	 */
	public boolean isResetConnectionOnClose() {
		return this.resetConnectionOnClose;
	}


	/**
	 * If true, issue a reset (rollback) on connection close in case client forgot it.
	 * @param resetConnectionOnClose the resetConnectionOnClose to set
	 */
	public void setResetConnectionOnClose(boolean resetConnectionOnClose) {
		this.resetConnectionOnClose = resetConnectionOnClose;
	}

	/**
	 * Returns the detectUnresolvedTransactions field.
	 * @return detectUnresolvedTransactions
	 */
	public boolean isDetectUnresolvedTransactions() {
		return this.detectUnresolvedTransactions;
	}


	/**
	 * If true, and resetConnectionOnClose is also true, the pool will print out a stack 
	 * trace of the location where you had a connection that specified setAutoCommit(false)
	 * but then forgot to call commit/rollback before closing it off. This feature is intended 
	 * for debugging only. 
	 * @param detectUnresolvedTransactions the detectUnresolvedTransactions to set
	 */
	public void setDetectUnresolvedTransactions(boolean detectUnresolvedTransactions) {
		this.detectUnresolvedTransactions = detectUnresolvedTransactions;
	}

	/**
	 * Returns the poolStrategy field.
	 * @return poolStrategy
	 */
	public String getPoolStrategy() {
		return this.poolStrategy;
	}


	/**
	 * Sets the poolStrategy. Currently supported strategies are DEFAULT and CACHED. This is an experimental feature!
	 *
	 * DEFAULT strategy operates in a manner that has been used in the pool since the very first
	 * version: it tries to obtain a connection from a queue.
	 *  
	 * CACHED stores each connection in a thread-local variable so that next time the same thread
	 * asks for a connection, it gets the same one assigned to it (if it asks for more than one, it
	 * will be allocated a new one). This is very fast but you must ensure that the number of threads 
	 * asking for a connection is less than or equal to the number  of connections you have made 
	 * available. Should you exceed this limit, the pool will switch back (permanently) to the DEFAULT 
	 * strategy which will cause a one-time performance hit. Use this strategy if your threads are 
	 * managed eg in a Tomcat environment where you can limit the number of threads that it can 
	 * handle. A typical use case would be a web service that always requires some form of database access, 
	 * therefore a service would have little point in accepting a new incoming socket connection if it 
	 * still has to wait in order to obtain a connection.
	 *  
	 * Essentially this means that you are pushing back the lock down to the socket or thread layer. 
	 * While the first few thread hits will be slower than in the DEFAULT strategy, significant performance 
	 * gains are to be expected as the thread gets increasingly re-used (i.e. initially you should expect
	 * the first few rounds to be measurably slower than the DEFAULT strategy but once the caches
	 * get more hits you should get >2x better performance).
	 * 
	 * Threads that are killed off are detected during the next garbage collection and result in 
	 * their allocated connections from being taken back though since GC timing is not guaranteed
	 * you should ideally set your minimum pool size to be equal to the maximum pool size.
	 * 
	 * Therefore for best results, make sure that the configured minConnectionPerPartition = maxConnectionPerPartition = min Threads = max Threads.
	 *   
	 *   
	 * @param poolStrategy the poolStrategy to set
	 */
	public void setPoolStrategy(String poolStrategy) {
		this.poolStrategy = poolStrategy;
	}

	/**
	 * Returns the closeOpenStatements field.
	 * @return closeOpenStatements
	 */
	public boolean isCloseOpenStatements() {
		return this.closeOpenStatements;
	}


	/**
	 * If true, track statements and close them if application forgot to do so. See also: 
	 * {@link BoneCPConfig#detectUnclosedStatements}. Do not set if your connections are managed
	 * eg via Spring jdbcTemplate or hibernate since those frameworks will always automatically
	 * close off your statements. This option has a negative performance hit.
	 * 
	 * @param closeOpenStatements the closeOpenStatements to set
	 */
	public void setCloseOpenStatements(boolean closeOpenStatements) {
		this.closeOpenStatements = closeOpenStatements;
	}

	/**
	 * Returns the detectUnclosedStatements field.
	 * @return detectUnclosedStatements
	 */
	public boolean isDetectUnclosedStatements() {
		return this.detectUnclosedStatements;
	}


	/**
	 * Sets the detectUnclosedStatements. If true, print out a stack trace of where a statement was opened but not closed before
	 * the connection was closed. See also: {@link BoneCPConfig#closeOpenStatements}. 
	 * @param detectUnclosedStatements the detectUnclosedStatements to set
	 */
	public void setDetectUnclosedStatements(boolean detectUnclosedStatements) {
		this.detectUnclosedStatements = detectUnclosedStatements;
	}

	/** If set, pool will call this for every new connection that's created. JDK 1.6 and higher only.
	 * @param properties Properties to set. 
	 *  
	 */
	public void setClientInfo(Properties properties) {
		this.clientInfo = properties;
	}

	/**
	 * Returns the clientInfo field.
	 * @return clientInfo
	 */
	public Properties getClientInfo() {
		return this.clientInfo;
	}



}