/**********************************************************************************
 * $URL: https://source.sakaiproject.org/svn/db/tags/sakai_2-3-1/db-impl/ext/src/java/org/apache/commons/dbcp/SakaiBasicDataSource.java $
 * $Id: SakaiBasicDataSource.java 9718 2006-05-20 00:19:01Z ggolden@umich.edu $
 ***********************************************************************************
 *
 * Copyright (c) 2005, 2006 The Sakai Foundation.
 * 
 * Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
 * 
 * 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 org.apache.commons.dbcp;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
import org.apache.commons.pool.impl.GenericObjectPool;

/**
 * <p>
 * SakaiBasicDataSource extends apache common's BasicDataSource ...
 * </p>
 */
public class SakaiBasicDataSource extends BasicDataSource
{
	/** Our logger. */
	private static Log M_log = LogFactory.getLog(SakaiBasicDataSource.class);

	/** Configuration: to rollback each connection when returned to the pool. */
	protected boolean m_rollbackOnReturn = false;

	/**
	 * Set the default transaction isolation level from a string value, based on the settings and values in java.sql.Connection
	 * 
	 * @param defaultTransactionIsolation
	 */
	public void setDefaultTransactionIsolationString(String defaultTransactionIsolation)
	{
		if ((defaultTransactionIsolation == null) || (defaultTransactionIsolation.trim().length() == 0))
		{
			setDefaultTransactionIsolation(PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION);
		}
		else if (defaultTransactionIsolation.trim().equalsIgnoreCase("TRANSACTION_NONE"))
		{
			setDefaultTransactionIsolation(Connection.TRANSACTION_NONE);
		}
		else if (defaultTransactionIsolation.trim().equalsIgnoreCase("TRANSACTION_READ_UNCOMMITTED"))
		{
			setDefaultTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
		}
		else if (defaultTransactionIsolation.trim().equalsIgnoreCase("TRANSACTION_READ_COMMITTED"))
		{
			setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
		}
		else if (defaultTransactionIsolation.trim().equalsIgnoreCase("TRANSACTION_REPEATABLE_READ"))
		{
			setDefaultTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
		}
		else if (defaultTransactionIsolation.trim().equalsIgnoreCase("TRANSACTION_SERIALIZABLE"))
		{
			setDefaultTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
		}
		else
		{
			setDefaultTransactionIsolation(PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION);
			M_log.warn("invalid transaction isolation level: " + defaultTransactionIsolation);
		}
	}

	/**
	 * Set the rollback on borrow configuration.
	 * 
	 * @param value
	 *        if true, rollback each connection when borrowed from the pool, if false, do not.
	 */
	public void setRollbackOnBorrow(boolean value)
	{
		m_rollbackOnReturn = value;
	}

	/**
	 * <p>
	 * Sakai changes: use the SakaiPoolableConnectionFactory, removed some not-visible (damn the use of private!) code.
	 * </p>
	 * <p>
	 * Create (if necessary) and return the internal data source we are using to manage our connections.
	 * </p>
	 * <p>
	 * <strong>IMPLEMENTATION NOTE</strong> - It is tempting to use the "double checked locking" idiom in an attempt to avoid synchronizing on every single call to this method. However, this idiom fails to work correctly in the face of some optimizations
	 * that are legal for a JVM to perform.
	 * </p>
	 * 
	 * @exception SQLException
	 *            if the object pool cannot be created.
	 */
	protected synchronized DataSource createDataSource() throws SQLException
	{

		// Return the pool if we have already created it
		if (dataSource != null)
		{
			return (dataSource);
		}

		// Load the JDBC driver class
		if (driverClassName != null)
		{
			try
			{
				Class.forName(driverClassName);
			}
			catch (Throwable t)
			{
				String message = "Cannot load JDBC driver class '" + driverClassName + "'";
				logWriter.println(message);
				t.printStackTrace(logWriter);
				throw new SQLNestedException(message, t);
			}
		}

		// Create a JDBC driver instance
		Driver driver = null;
		try
		{
			driver = DriverManager.getDriver(url);
		}
		catch (Throwable t)
		{
			String message = "Cannot create JDBC driver of class '" + (driverClassName != null ? driverClassName : "")
					+ "' for connect URL '" + url + "'";
			logWriter.println(message);
			t.printStackTrace(logWriter);
			throw new SQLNestedException(message, t);
		}

		// Can't test without a validationQuery
		if (validationQuery == null)
		{
			setTestOnBorrow(false);
			setTestOnReturn(false);
			setTestWhileIdle(false);
		}

		// Create an object pool to contain our active connections
		// Sakai:
		// if ((abandonedConfig != null) && (abandonedConfig.getRemoveAbandoned() == true))
		// {
		// connectionPool = new AbandonedObjectPool(null, abandonedConfig);
		// }
		// else
		{
			connectionPool = new GenericObjectPool();
		}
		connectionPool.setMaxActive(maxActive);
		connectionPool.setMaxIdle(maxIdle);
		connectionPool.setMinIdle(minIdle);
		connectionPool.setMaxWait(maxWait);
		connectionPool.setTestOnBorrow(testOnBorrow);
		connectionPool.setTestOnReturn(testOnReturn);
		connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
		connectionPool.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
		connectionPool.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
		connectionPool.setTestWhileIdle(testWhileIdle);

		// Set up statement pool, if desired
		GenericKeyedObjectPoolFactory statementPoolFactory = null;
		if (isPoolPreparedStatements())
		{
			statementPoolFactory = new GenericKeyedObjectPoolFactory(null, -1, // unlimited maxActive (per key)
					GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL, 0, // maxWait
					1, // maxIdle (per key)
					maxOpenPreparedStatements);
		}

		// Set up the driver connection factory we will use
		if (username != null)
		{
			connectionProperties.put("user", username);
		}
		else
		{
			// Sakai: log("DBCP DataSource configured without a 'username'");
		}

		if (password != null)
		{
			connectionProperties.put("password", password);
		}
		else
		{
			// Sakai: log("DBCP DataSource configured without a 'password'");
		}

		DriverConnectionFactory driverConnectionFactory = new DriverConnectionFactory(driver, url, connectionProperties);

		// Set up the poolable connection factory we will use
		PoolableConnectionFactory connectionFactory = null;
		try
		{
			connectionFactory = new SakaiPoolableConnectionFactory(driverConnectionFactory, connectionPool, statementPoolFactory,
					validationQuery, defaultReadOnly, defaultAutoCommit, defaultTransactionIsolation, defaultCatalog,
					/* abandonedConfig Sakai: */null, m_rollbackOnReturn);
			if (connectionFactory == null)
			{
				throw new SQLException("Cannot create PoolableConnectionFactory");
			}
			// Sakai: validateConnectionFactory(connectionFactory);
		}
		catch (RuntimeException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new SQLNestedException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e);
		}

		// Create and return the pooling data source to manage the connections
		dataSource = new PoolingDataSource(connectionPool);
		((PoolingDataSource) dataSource).setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
		dataSource.setLogWriter(logWriter);

		try
		{
			for (int i = 0; i < initialSize; i++)
			{
				connectionPool.addObject();
			}
		}
		catch (Exception e)
		{
			throw new SQLNestedException("Error preloading the connection pool", e);
		}

		return dataSource;
	}
}
