/*-----------------------------------------------------------------------------
 *
 * 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.standard;

import java.io.Serializable;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Hashtable;
import java.util.Enumeration;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import javax.sql.DataSource;
import org.enhydra.jdbc.core.CoreDataSource;
import java.sql.Driver;
import java.util.Properties;

/**
 * Provides a Data Source which can be used to generate JDBC connections.
 * <P>
 * This class is generic in the sense that it does not rely upon anything other
 * than standard Java APIs. It uses java.sql.DriverManager and preconfigured
 * properties to construct a JDBC connection.
 * Important : networkProtocol, portNumber, serverName are not used. Please use
 * instead the url property.
 */
public class StandardDataSource extends CoreDataSource implements DataSource {

    // Standard Data Source properties
    transient Driver driver;
    String	driverName;		// name of the Standard JDBC driver
    String	url;			// an explicit JDBC URL used to access this data source
    private int         transIsolation;         // transaction isolation level
    private boolean loadedFromCCL = false;

    /**
     * Constructors
     */
    public StandardDataSource() {
	// This constructor is needed by the object factory
	super();
    driver = null;
	driverName      = "";
	url             = "";
	transIsolation = -1; //use default
    } // constructor
    
    protected StandardDataSource(Driver drv) throws SQLException {
        this();
        driver = drv;
    	driverName = drv.getClass().getName();
    }
    
    /**
     * Setter/Getter defined for standard properties
     */
    public String getDriverName()		{return driverName;}
    public String getUrl()			{return url;}

    public void setDriverName (String driverName) throws SQLException {
	if (!this.driverName.equals(driverName)) {
	    this.driverName = driverName;
	    driver = null;
	}
	/*
	try {
        driver= (Driver)Class.forName (driverName).newInstance();
	    log("StandardDataSource:setDriverName a new driver instance is created");
	} catch (Exception e) {
	    throw new SQLException ("Error trying to load driver: "+driverName+"\n"+e.getMessage());
	} // try-catch
	*/
    } // method setDriverName

    public void setUrl (String url) {this.url = url;}
         
    public void setTransactionIsolation(int level) {
	transIsolation = level;
    } // method setTransactionIsolation

    
    public int getTransactionIsolation() {
	return transIsolation;
    } // method getTransactionIsolation
 
    /**
     * Methods inherited from DataSource
     */
    synchronized public Connection getConnection() throws SQLException {
	return getConnection(user, password);
    } // method getConnection
    
    synchronized public Connection getConnection(String u, String p) throws SQLException {
	Connection ret=null;			// the connection that gets returned
    Properties prop = new Properties();
    if (u!=null) prop.put("user",u);
    if (p!=null) prop.put("password",p);
	if (url==null) {			// if no explicit url provided
	    // Build URL from serverName, NetworkProtocol etc.
	} else {				// explicit URL provided
        if (driver==null) {
	    /*
   		    log("No driver loaded");
            throw new SQLException("No driver loaded!");
	    */
	    try {
 		driver= (Driver)Class.forName(driverName).newInstance();
		loadedFromCCL = false;
 		log("StandardDataSource:setDriverName a new driver instance is created");
 	    } catch (Exception e) {
		try {
		    driver = (Driver)Class.forName(driverName, true, 
			    Thread.currentThread().getContextClassLoader())
			    .newInstance();
		    loadedFromCCL = true;
		} catch (Exception e2) {
		    
		    log("Error trying to load driver: " + driverName + " : " + e2.getMessage());
		    throw new SQLException ("Error trying to load driver: " + driverName + " : " + e2.getMessage());
		} // try-catch
	    }
	}
	// commenting out since at least one driver will complain if you 
	// instantiate the driver outside the Driver Manager 
	// (ie. Cloudscape RMI)
	/*
        if (!driver.acceptsURL(url)) {
   		    log("Driver does not accept url "+url);
            throw new SQLException("Driver does not accept url "+url);            
        }
	*/
	    try {
		if (loadedFromCCL) {
		    ret = driver.connect(url, prop);// Driver creates the connection
		} else {
		    ret = DriverManager.getConnection(url, prop);// DriverManager creates the connection
		}
		int transIsolation = getTransactionIsolation();
		if (transIsolation >= 0) {
		    ret.setTransactionIsolation(transIsolation);
		}
		log("StandardDataSource:getConnection Connection from DriverManager is returned");
	    } catch (SQLException e) {
		log("Cannot get connection for URL " + url + " : " + e.getMessage());
		throw new SQLException("Cannot get connection for URL " + url + " : " + e.getMessage());
	    }
	}
	return ret;
    } // method getConnection
    
    /**
     * Methods inherited from referenceable
     */
    public Reference getReference() throws NamingException {
	// Note that we use getClass().getName() to provide the factory
	// class name. It is assumed that this class, and all of its
	// descendants are their own factories.

	Reference ref = new Reference(getClass().getName(), getClass().getName(), null);
	ref.add(new StringRefAddr("driverName", getDriverName()));
	ref.add(new StringRefAddr("url", getUrl()));
	ref.add(new StringRefAddr("user", getUser()));
	ref.add(new StringRefAddr("password", getPassword()));
	ref.add(new StringRefAddr("description", getDescription()));
	ref.add(new StringRefAddr("loginTimeout", Integer.toString(getLoginTimeout())));
	ref.add(new StringRefAddr("transIsolation", Integer.toString(getTransactionIsolation())));
	log("StandardDataSource:getReference object returned");
	return ref;
    } // method getReference
    
    /**
     * Methods inherited from ObjectFactory
     */
    public Object getObjectInstance(Object refObj, Name name, Context nameCtx, Hashtable env) throws Exception {
	Reference ref = (Reference)refObj;
	this.setDriverName((String)ref.get("driverName").getContent());
	this.setUrl((String)ref.get("url").getContent());
	this.setUser((String)ref.get("user").getContent());
	this.setPassword((String)ref.get("password").getContent());
	this.setDescription((String)ref.get("description").getContent());
	this.setLoginTimeout(Integer.parseInt((String)ref.get("loginTimeout").getContent())); 
	this.setTransactionIsolation(Integer.parseInt((String)ref.get("transIsolation").getContent())); 
	log("StandardDataSource:getObjectInstance instance created");
	return this;
    } // method getObjectInstance
  
    public boolean equals(Object obj) {
 	if (this == obj) {
	    return true;
  	}
  	if (obj == null) {
  	    return false;
  	}
  	if (obj instanceof StandardDataSource) {
  	    StandardDataSource other = (StandardDataSource)obj;
  	    if (!(this.url == null || this.user == null)) {
  		return (this.url.equals(other.url) && this.user.equals(other.user));
  	    } else if (this.url != null) {
  		return (this.url.equals(other.url) && this.user == other.user);
  	    } else if (this.user != null) {
  		return (this.url == other.url && this.user.equals(other.user));
  	    } else {
  		return (this.url == other.url && this.user == other.user);
  	    }
  	} else {
  	    return false;
  	}
    }
 

} // class StandardDataSource
