/*-----------------------------------------------------------------------------
 *
 * Enhydra JdbcS
 * The Initial Developer of the Original Code is Lutris Technologies Inc.
 * Portions created by Lutris are Copyright 1997-2000 Lutris Technologies Inc.
 * All Rights Reserved.
 *
 * The contents of this file are subject to the Enhydra Public License
 * Version 1.1 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * www.enhydra.org/license/epl.html
 *
 * Software distributed under the License is distributed on an "ASIS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * -----------------------------------------------------------------------------
 * $Id$
 * -----------------------------------------------------------------------------
 */
package org.enhydra.jdbc.pool;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Hashtable;
import java.util.Enumeration;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;
import javax.sql.PooledConnection;
import org.enhydra.jdbc.core.CoreDataSource;
import org.enhydra.jdbc.core.JdbcThreadFactory;

/**
 * StandardPoolDataSource class allows to make some operations on
 * PooledConnection. It implements PoolHelper for the 3 methods :<p>
 * create   : create a PooledConnection<p>
 * create(user,password)   : create a PooledConnection with an other user/password<p>
 * testThisObject : check if the object is still valid<p>
 * checkThisObject : check if the object is closed<p>
 * expire   : kill the object<p>
 */
public class StandardPoolDataSource extends CoreDataSource implements DataSource, PoolHelper, ConnectionEventListener {

    public ConnectionPoolDataSource 	cpds;		// object to build PooledConnection
    public GenericPool			pool;		// pool to store StandardDataSource object
    public String 			dataSourceName;	// jndi name for DataSource Factory
    private String			jdbcTestStmt;	// JDBC test statement for checkLevelObject=1 or 2
    public boolean			onOff;		// If the pool is started or not
    public Context			ictx;		// the initial context

    /**
     * Constructor
     */
    public StandardPoolDataSource() {
	pool = new GenericPool(this);	// create the pool with StandardPoolDataSource object
	onOff = false;
	dataSourceName = null;
    } // constructor

    /**
     * Constructor
     */
    public StandardPoolDataSource(int initSize) {	// with an init Max size
	pool = new GenericPool(this, initSize);	// create the pool with StandardPoolDataSource object
	onOff = false;
	dataSourceName = null;
    } // constructor

    /**
     * Set up the data source name, get the initial context,
     * and lookup in JNDI to obtain a reference of the DataSourceName
     * this method must be called before a getConnection (in this case
     * an exception is returned
     */
    public void setDataSourceName(String dataSourceName) {
	log("StandardPoolDataSource:setDataSourceName");
	this.dataSourceName = dataSourceName;		// set up the data source name
	synchronized(this) {
	    if (onOff) {
		pool.stop();
		onOff = false;
	    }
	}
    } // method setDataSourceName

    public void initContext() throws SQLException {
	try {
	    ictx =  new InitialContext ();		// get the initial context
	    cpds = (ConnectionPoolDataSource)ictx.lookup(this.dataSourceName);
	} catch (javax.naming.NameNotFoundException e) {
	    log("The ConnectionPoolDataSource is not bound at " + this.dataSourceName);
	    throw new SQLException("The ConnectionPoolDataSource is not bound at " + this.dataSourceName);
	} catch (javax.naming.NamingException e) {
	    log("Error javax.naming.NamingException in StandardPoolDataSource:StandardPoolDataSource");
	}
    /* removed by christophe. There are no reason for the pool to configure the logWriter of
     the ConnectionPoolDataSource
	try {
	    cpds.setLogWriter(pool.getLogWriter());
	} catch(java.sql.SQLException e) {
	    log("setLogWriter error "+e);
	}
     */
    } // method initContext

    public String getDataSourceName() {
	return dataSourceName;				// return the dataSourceName (jndi mechanism)
    } // method getDataSourceName

    /**
     * getConnection allows to get an object from the pool and returns it
     * to the user. In this case, we return an PooledConnection
     */
    public Connection getConnection() throws SQLException {
	return getConnection(getUser(),getPassword());
    } // method getConnection

    /**
     * getConnection allows to get an object from the pool and returns it
     * to the user. In this case, we return an PooledConnection
     */
    public Connection getConnection(String _user, String _password) throws SQLException {
	log("StandardPoolDataSource:getConnection");
	Connection ret = null;
	PooledConnection con = null;

	synchronized(this) {
	    if (! onOff) {
		initContext();
		pool.start();	// the pool starts now
		onOff = true;	// and is initialized
		log("pool config : "+pool.toString());
	    }
	}

	try {
	    try {
		con = (PooledConnection)pool.checkOut(_user, _password);	// get a connection from the pool
	    } catch (Exception e) {
		log("SQLException in StandardPoolDataSource:getConnection" + e);
		throw new SQLException("SQLException in StandardPoolDataSource:getConnection no connection available " + e);
	    }

	    ret = con.getConnection();
	} catch(Exception e) {
	    e.printStackTrace();
	    log("StandardPoolDataSource:getConnection exception"+e);
	    SQLException sqle  = new SQLException("SQLException in StandardPoolDataSource:getConnection exception: "+e);
	    if (e instanceof SQLException) sqle.setNextException((SQLException)e);
	    if (con != null) {
		pool.checkIn(con);
	    }
	    throw sqle;
	}
	log("StandardPoolDataSource:getConnection return a connection");
	return ret;

    } // method getConnection

    /**
     * connectionErrorOccurred and connectionClosed are methods
     * from ConnectionEventListener interface
     *
     * Invoked when a fatal connection error occurs,
     * just before an SQLException is thrown to the application
     */
    public void connectionErrorOccurred(ConnectionEvent event) {
	Object obj = event.getSource();
	PooledConnection pc = (PooledConnection)obj;
	pool.nextGeneration(pc);
	pool.removeLockedObject(pc);			// remove the object from the locked pool
	expire(pc);				// kill the connection (from super)
	log("StandardXAPoolDataSource:connectionErrorOccurred remove the object from the pool");
    } // method connectionErrorOccurred

    /**
     * Invoked when the application calls close()
     * on its representation of the connection
     */
    public void connectionClosed(ConnectionEvent event) {
	log("StandardPoolDataSource:connectionClosed close the connection");
	Object obj = event.getSource();
	pool.checkIn((PooledConnection)obj);
    } // method connectionClosed

    /**
     * object specific work to kill the object
     */
    public void expire(Object o) {
	log("StandardPoolDataSource:expire expire a connection, remove from the pool");
	if (o == null) return;
	try {
	    PooledConnection pooledCon = (PooledConnection)o;
	    pooledCon.close();					// call close() of PooledConnection
	    pooledCon.removeConnectionEventListener(this);
	    log("StandardPoolDataSource:expire close the connection");
	} catch (java.sql.SQLException e) {
	    log("Error java.sql.SQLException in StandardPoolDataSource:expire");
	}
    } // method expire


    /**
     * This method tests if a connection is closed or not
     */
    public boolean checkThisObject(Object o) {

	PooledConnection con;
	Connection ret;
	log("StandardPoolDataSource:checkThisObject verify the current object");
	try {
	    con = (PooledConnection)o;
	    ret = con.getConnection();		// get the connection from the pool
	    if (ret.isClosed()) {
		return false;
	    }
	    try {
		ret.close();
	    } catch (Exception e) {
		log("StandardPoolDataSource:checkThisObject can't closed the connection: "+e);
	    }

	    return true;
	} catch (java.sql.SQLException e) {
	    log("Error java.sql.SQLException in StandardPoolDataSource:checkThisObject");
	    return false;
	}
    } // method checkThisObject

    /**
     * This method tests if a connection is valid or not
     */
    public boolean testThisObject(Object o) {
	Connection ret = null;
	log("StandardPoolDataSource:testThisObject verify the current object");
	try {
	    PooledConnection con = (PooledConnection)o;
	    ret = con.getConnection();
	    Statement s = ret.createStatement();
	    s.execute(jdbcTestStmt);
	    s.close();
	    try {
		ret.close();
	    } catch (Exception e) {
		log("StandardPoolDataSource:checkThisObject can't closed the connection: "+e);
	    }
	    return true;
	} catch (java.sql.SQLException e) {
	    log("Error java.sql.SQLException in StandardPoolDataSource:testThisObject");
	    return false;
	}
    } // method testThisObject

    public GenerationObject create() throws SQLException{
	return create(getUser(), getPassword());
    } // method create

    public GenerationObject create(String _user, String _password) throws SQLException{
	log("StandardPoolDataSource:create create a connection for the pool");
	GenerationObject genObject;
	PooledConnection pooledCon =
	    cpds.getPooledConnection(_user,_password);	// get the pooled connection

	pooledCon.addConnectionEventListener (this);		// add it to the event listener
        log("StandardPoolDataSource:create create a object for the pool");
	genObject = new GenerationObject(pooledCon, pool.getGeneration(), _user, _password);
	return genObject; 			// return a connection
    } // method create

    /**
     * stop method to switch off the pool
     */
    public void stopPool() {
	pool.stop();
	log("StandardPoolDataSource:stopPool stop now the pool");
    } // method stopPool

    public void shutdown(boolean force) {
        stopPool();
    }

    /**
     * Set/Get methods
     */
    public void setLogWriter(PrintWriter logWriter) {
	pool.setLogWriter(logWriter);
	super.setLogWriter(logWriter);
    } // method setLogWriter

    public void setDebug(boolean debug) {
	super.setDebug(debug);
	//	cpds.setDebug(debug);
	pool.setDebug(debug);
    } //method setDebug

    public void setMinSize(int minSize) throws Exception {
	pool.setMinSize(minSize);
    } // method setMinSize

    public void setMaxSize(int maxSize) throws Exception {
	pool.setMaxSize(maxSize);
    } // method setMaxSize

    public void setLifeTime(long lifeTime) {
	pool.setLifeTime(lifeTime);
    } // method public

    public void setSleepTime(long sleepTime) {
	pool.setSleepTime(sleepTime);
    } // method setSleepTime

    public void setGC(boolean gc) {
	pool.setGC(gc);
    } // method setGC

    public void setCheckLevelObject(int checkLevelObject) {
	pool.setCheckLevelObject(checkLevelObject);
    } // method setCheckLevelObject

    public void setJdbcTestStmt(String jdbcTestStmt) {
	this.jdbcTestStmt = jdbcTestStmt;
    } // method setJdbcTestStmt

    public void setGeneration(int generation) {
	pool.setGeneration(generation);
    } // method setGeneration

    public void setDeadLockMaxWait(long deadLock) {
	pool.setDeadLockMaxWait(deadLock);
    } // method setDeadLockMaxWait

    public void setDeadLockRetryWait(long loopWait) {
	pool.setDeadLockRetryWait(loopWait);
    } // method setDeadLockRetryWait

    public PrintWriter getLogWriter() {
	return pool.getLogWriter();
    } // method getLogWriter

    public int getMinSize() {
	return pool.getMinSize();
    } // method getMinSize

    public int getMaxSize() {
	return pool.getMaxSize();
    } // method getMaxSize

    public long getLifeTime() {
	return pool.getLifeTime();
    } // method getLifeTime

    public long getSleepTime() {
	return pool.getSleepTime();
    } // method getSleepTime

    public int getGeneration() {
	return pool.generation;
    } // method getGeneration

    public boolean isGC() {
	return pool.isGC();
    } // method isGC

    public int getLockedObjectCount() {
	return pool.getLockedObjectCount();
    } // method getLockedObjectCount

    public int getUnlockedObjectCount() {
	return pool.getUnlockedObjectCount();
    } // method getUnlockedObjectCount

    public int getCheckLevelObject() {
	return pool.getCheckLevelObject();
    } // method getCheckLevelObject

    public String getJdbcTestStmt() {
	return jdbcTestStmt;
    } // method getJdbcTestStmt

    public long getDeadLockMaxWait() {
	return pool.getDeadLockMaxWait();
    } // method getDeadLockMaxWait

    public long getDeadLockRetryWait() {
	return pool.getDeadLockRetryWait();
    } // method getDeadLockRetryWait

    public String toString() {
	return pool.toString();
    } // method toString

    /**
     * Retrieves the Reference of this object. Used at binding time by JNDI
     * to build a reference on this object.
     *
     * @return  The non-null Reference of this object.
     * @exception  NamingException  If a naming exception was encountered while
     * retrieving the reference.
     */
    public Reference getReference() throws NamingException {
	log("StandardPoolDataSource:getReference return a reference of the object");
	Reference ref = new Reference (getClass().getName(), getClass().getName(), null);
	ref.add(new StringRefAddr("checkLevelObject", Integer.toString(getCheckLevelObject())));
	ref.add(new StringRefAddr("lifeTime", Long.toString(getLifeTime())));
	ref.add(new StringRefAddr("jdbcTestStmt", getJdbcTestStmt()));
	ref.add(new StringRefAddr("maxSize", Integer.toString(getMaxSize())));
	ref.add(new StringRefAddr("minSize", Integer.toString(getMinSize())));
	ref.add(new StringRefAddr("dataSourceName", getDataSourceName()));
        return ref;
    } // method getReference

    /**
     * Override this so that the pool's tf gets set as well
     */
    public void setThreadFactory(JdbcThreadFactory tf) {
	super.setThreadFactory(tf);
	pool.setThreadFactory(tf);
    }

} // class StandardPoolDataSource
