/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * ConnectionFactoryImpl.java
 *
 * Created on March 10, 2000, 5:09 PM
 */
 
package com.sun.jdo.spi.persistence.support.sqlstore.impl;

import java.lang.String;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.ResourceBundle;


import com.sun.jdo.api.persistence.support.*;
import com.sun.jdo.spi.persistence.support.sqlstore.connection.ConnectionManager;
import com.sun.jdo.spi.persistence.utility.I18NHelper;

/** 
 *
 * @author  Craig Russell
 * @version 0.1
 */
public class ConnectionFactoryImpl implements ConnectionFactory, java.io.Serializable {

	// Delegate all connection variables to ConnectionManager: 

        private String URL = null;
        private String userName = null;
        private String password = null;
        private String driverName = null;
        private int maxPool = 0;
        private int minPool = 0;
        private int msInterval = 0;
        private int loginTimeout = 0;
        private int msWait = 0;
        private int _txIsolation = -1;
        private transient PrintWriter logWriter = null;

	private transient boolean _configured = false;
	private transient ConnectionManager connectionManager = null;

        /**
         * I18N message handler
         */
        private transient final static ResourceBundle messages = I18NHelper.loadBundle(
                                ConnectionFactoryImpl.class);

	

  /**
   * Creates new default <code>ConnectionFactoryImpl</code> object
   */
  public ConnectionFactoryImpl()
  {
	//connectionManager = new ConnectionManager();
  }

  /**
   * Creates new <code>ConnectionFactoryImpl</code> object with user info
   * @param URL           connection URL
   * @param userName      database user
   * @param password      database user password
   * @param driverName    driver name
   */
  public ConnectionFactoryImpl(
		String URL, 
		String userName, 
		String password, 
		String driverName
		)
  {
		this.driverName = driverName;
		this.URL = URL;
		this.userName = userName;
		this.password = password;

  }

  /**
   * Creates new <code>ConnectionFactoryImpl</code> object with user  and connection info
   * @param URL           connection URL
   * @param userName      database user
   * @param password      database user password
   * @param driverName    driver name
   * @param minPool       minimum number of connections
   * @param maxPool       maximum number of connections
   */
  public ConnectionFactoryImpl(
		String URL, 
		String userName, 
		String password, 
		String driverName,
		int    minPool,
                int    maxPool
		)
  {
		this.driverName = driverName;
		this.URL = URL;
		this.userName = userName;
		this.password = password;
		this.minPool = minPool;
		this.maxPool = maxPool;

  }

  /**
   * Sets JDBC driver name
   * @param driverName    JDBC driver name
   */
  public void setDriverName (String driverName)
  {
	// REMOVE WHEN SUPPORTED:
	//unsupported();

	assertNotConfigured();
	// Delegate to ConnectionManager: this.driverName = driverName;
	if(connectionManager == null)
	{
		this.driverName = driverName;
	}
	else
	{
		try {
			connectionManager.setDriverName(driverName);
		} catch (Exception e) {
			throw new JDOFatalException(null, e);
		}
	}
  }

  /**
   * Returns JDBC driver name
   * @return      driver name
   */
  public String getDriverName ()
  {
	return driverName;
	//return connectionManager.getDriverName();
  }

  
  /**
   * Sets JDBC connection URL
   * @param URL   connection URL
   */
  public void setURL (String URL)
  {
	// REMOVE WHEN SUPPORTED:
	//unsupported();

	assertNotConfigured();
	// Delegate to ConnectionManager: this.URL = URL;
	if(connectionManager == null) 
        {
                this.URL = URL;
        } 
        else 
        { 
                try {
                	connectionManager.setURL(URL);
		} catch (Exception e) {
			throw new JDOFatalException(null, e);
		}
	}
  }

  /**
   * Returns connection URL
   * @return      connection URL
   */
  public String getURL ()
  {
	return URL;
	//return connectionManager.getURL();
  }

  
  /**
   * Sets database user
   * @param userName      database user
   */
  public void setUserName (String userName)
  {
	// REMOVE WHEN SUPPORTED:
	//unsupported();

	assertNotConfigured();
	// Delegate to ConnectionManager: this.userName = userName;
	if(connectionManager == null)
        {
                this.userName = userName;
	}
	else
        {
                try {
                	connectionManager.setUserName(userName);
		} catch (Exception e) {
			throw new JDOFatalException(null, e);
		}
	}
  }

  /**
   * Returns database user name
   * @return      current database user name
   */
  public String getUserName ()
  {
	return userName;
	//return connectionManager.getUserName();
  }

  
  /**
   * Sets database user password
   * @param password      database user password
   */
  public void setPassword (String password)
  {
	// REMOVE WHEN SUPPORTED:
	//unsupported();

	assertNotConfigured();
	// Delegate to ConnectionManager: this.password = password;
	if(connectionManager == null)
        {
                this.password = password;
        }
        else
        {
		try {
                	connectionManager.setPassword(password);
		} catch (Exception e) {
			throw new JDOFatalException(null, e);
		}
	}
  }

  
  /**
   * Sets minimum number of connections in the connection pool
   * @param minPool       minimum number of connections
   */
  public void setMinPool (int minPool)
  {
	assertNotConfigured();
	if(connectionManager == null) { 
                // Nothing to do yet 
                this.minPool = minPool; 
                return; 
        } 

	// Delegate to ConnectionManager: this.minPool = minPool;
	try {
                connectionManager.setMinPool(minPool);
	} catch (Exception e) {
		throw new JDOFatalException(null, e);
	}
  }

  /**
   * Returns minimum number of connections in the connection pool
   * @return      connection minPool
   */
  public int getMinPool ()
  {
	return minPool;
	//return connectionManager.getMinPool();
  }

  
  /**
   * Sets maximum number of connections in the connection pool
   * @param maxPool       maximum number of connections
   */
  public void setMaxPool (int maxPool)
  {
	assertNotConfigured();
	if(connectionManager == null) { 
                // Nothing to do yet 
                this.maxPool = maxPool; 
                return; 
        } 

	// Delegate to ConnectionManager: this.maxPool = maxPool;
	try {
                connectionManager.setMaxPool(maxPool);
	} catch (Exception e) {
		throw new JDOFatalException(null, e);
	}
  }

  /**
   * Returns maximum number of connections in the connection pool
   * @return      connection maxPool
   */
  public int getMaxPool ()
  {
	return maxPool;
	// return connectionManager.getMaxPool();
  }

  
  /**
   * Sets the amount of time, in milliseconds, between the connection
   * manager's attempts to get a pooled connection.
   * @param msInterval    the interval between attempts to get a database
   *                      connection, in milliseconds.
   */
  public void setMsInterval (int msInterval)
  {
	assertNotConfigured();
	if(connectionManager == null) { 
                // Nothing to do yet 
                this.msInterval = msInterval; 
                return; 
        } 

	// Delegate to ConnectionManager: this.msInterval = msInterval;
	try {
                connectionManager.setMsInterval(msInterval);
	} catch (Exception e) {
		throw new JDOFatalException(null, e);
	}
  }

  /**
   * Returns the amount of time, in milliseconds, between the connection
   * manager's attempts to get a pooled connection.
   * @return      the length of the interval between tries in milliseconds
   */
  public int getMsInterval ()
  {
	if (connectionManager == null)
		return msInterval;

	return connectionManager.getMsInterval();
  }

  
  /**
   * Sets the number of milliseconds to wait for an available connection
   * from the connection pool before throwing an exception
   * @param msWait        number in milliseconds
   */
  public void setMsWait (int msWait)
  {
	assertNotConfigured();
	if(connectionManager == null) { 
                // Nothing to do yet 
                this.msWait = msWait; 
                return; 
        } 

	// Delegate to ConnectionManager: this.msWait = msWait;
	try {
                connectionManager.setMsWait(msWait);
	} catch (Exception e) {
		throw new JDOFatalException(null, e);
	}
  }

  /**
   * Returns the number of milliseconds to wait for an available connection
   * from the connection pool before throwing an exception
   * @return      number in milliseconds
   */
  public int getMsWait ()
  {
	if (connectionManager == null)
                return msWait;

	return connectionManager.getMsWait();
  }

  
  /**
   * Sets the LogWriter to which messages should be sent
   * @param pw            logWriter
   */
  public void setLogWriter (PrintWriter logWriter)
  {
	assertNotConfigured();
	this.logWriter = logWriter; //RESOLVE
  }

  /**
   * Returns the LogWriter to which messages should be sent
   * @return      logWriter
   */
  public PrintWriter getLogWriter ()
  {
	return logWriter;
  }

 
  /**
   * Sets the number of seconds to wait for a new connection to be
   * established to the data source
   * @param loginTimeout           wait time in seconds
   */
  public void setLoginTimeout (int loginTimeout)
  {
	assertNotConfigured();
	if(connectionManager == null) { 
                // Nothing to do yet 
                this.loginTimeout = loginTimeout; 
                return; 
        } 

	// Delegate to ConnectionManager: this.loginTimeout = loginTimeout;
	try {
                connectionManager.setLoginTimeout(loginTimeout);
	} catch (Exception e) {
		throw new JDOFatalException(null, e);
	}
  }

  /**
   * Returns the number of seconds to wait for a new connection to be
   * established to the data source
   * @return      wait time in seconds
   */
  public int getLoginTimeout ()
  {
	if (connectionManager == null)
                return loginTimeout;

	try {
                return connectionManager.getLoginTimeout();
	} catch (Exception e) {
                return 0;
	}
  }
  /**
   **
   * Sets transaction isolation level for all connections of this ConnectionFactory.
   * All validation is done by java.sql.Connection itself, so e.g. while Oracle    
   * will not allow to set solation level to TRANSACTION_REPEATABLE_READ, this method
   * does not have any explicit restrictions
   *
   * @param level - one of the java.sql.Connection.TRANSACTION_* isolation values
   */
  public void setTransactionIsolation (int level)
  {
	assertNotConfigured();
	if(connectionManager == null) {
		// Nothing to do yet
		_txIsolation = level;
		return;
	}

	// verify that database supports it
	Connection conn = null;
	try {
		conn = connectionManager.getConnection();
		DatabaseMetaData dbMetaData = conn.getMetaData();
		if(dbMetaData.supportsTransactionIsolationLevel(level)) 
		{
                	_txIsolation = level;
		} 
		else 
		{
			throw new JDOFatalException(I18NHelper.getMessage(messages,
                                "connectionefactoryimpl.isolationlevel_notsupported", //NOI18N
				level)); 
		}
	} catch (Exception e) {
		throw new JDOFatalException(null, e);
	}
	finally
	{
		closeConnection(conn);
	}
  }

  /**
   * Gets this ConnectionFactory's current transaction isolation level.
   * @return      the current transaction isolation mode value as java.sql.Connection.TRANSACTION_*
   */
  public int getTransactionIsolation ()
  {
	if (connectionManager == null)
                return _txIsolation;

	Connection conn = null;
	try {
		// Delegate to the Connection
                if (_txIsolation == -1)
		{
		    synchronized(this)
		    {
			//Double check that it was not set before
                	if (_txIsolation == -1)
			{
				conn = connectionManager.getConnection();
				_txIsolation = conn.getTransactionIsolation();
			}
		    }
		}

		return _txIsolation;
	} catch (Exception e) {
                throw new JDOFatalException(null, e);
	}
	finally
	{
		closeConnection(conn);
	}
  }


  /**
   * Returns java.sql.Connection
   * @return      connection as java.sql.Connection
   */
  public Connection getConnection() 
  {
	// Delegate to ConnectionManager
	try {
		if (connectionManager == null)
			initialize();

                Connection conn = connectionManager.getConnection();
		conn.setTransactionIsolation(_txIsolation);

		return conn;

	} catch (SQLException e) {
		String sqlState = e.getSQLState();
		int  errorCode = e.getErrorCode();

		if (sqlState == null)
		{
			throw new JDODataStoreException(I18NHelper.getMessage(messages,
                                "connectionefactoryimpl.sqlexception", "null", "" + errorCode), e); //NOI18N
		}
		else
		{
			throw new JDODataStoreException(I18NHelper.getMessage(messages,
                                "connectionefactoryimpl.sqlexception", sqlState, "" + errorCode), e); //NOI18N
		}
	} catch (Exception e) {
                throw new JDOCanRetryException(I18NHelper.getMessage(messages, 
                                "connectionefactoryimpl.getconnection"), e); //NOI18N
	}
  }

  /**
  * Determines whether obj is a ConnectionFactoryImpl with the same configuration
  *
  * @param obj The possibly null object to check.
  * @return true if obj is equal to this ConnectionFactoryImpl; false otherwise.
  */
  public boolean equals(Object obj) {
	if ((obj != null) && (obj instanceof ConnectionFactoryImpl) ) {
		ConnectionFactoryImpl cf = (ConnectionFactoryImpl)obj;
		return (cf.URL.equals(this.URL) && cf.userName.equals(this.userName) && cf.driverName.equals(this.driverName) && cf.password.equals(this.password));
	}
	return false;
  }

  /**
  * Computes the hash code of this ConnectionFactoryImpl.
  *
  * @return A hash code of the owning ConnectionFactoryImpl as an int.
  */
  public int hashCode() {
  	return URL.hashCode() + userName.hashCode() + password.hashCode() + driverName.hashCode();
  }


	/**
	 * INTERNAL
	 * Marks Connectionfactory as fully configured
	 * @param	flag	boolean flag 
	 */
	public void configured(boolean flag)
	{
		_configured = flag;
	}

        /**
         * INTERNAL
         * Asserts that change to the property is allowed
         */
        private void assertNotConfigured() {
                if ( _configured) {
                        throw new JDOUnsupportedOptionException(I18NHelper.getMessage(messages,
                                "persistencemanagerfactoryimpl.configured")); //NOI18N
                }
        }

        /**
         * INTERNAL
         * Asserts that MsWait and MsInterval are properly configured
         */
        private void assertConnectionWait() {
                if ( msWait < 0 )  
                {
                        throw new JDOUserException(I18NHelper.getMessage(messages,
                                             "connection.connectionmanager.mswaitvalue")); // NOI18N
                }
                else if ( msInterval < 0 || msInterval > msWait || (msWait > 0 && msInterval == 0) )
                {
                                throw new JDOUserException(I18NHelper.getMessage(messages,
                                             "connection.connectionmanager.msintervalvalue")); // NOI18N
                }
        }


	/**
         * INTERNAL
	 * Attempts to create new connectionManager
         * Throws JDOFatalException
	 */
	private synchronized void initialize() {
		// If connectionManager was already initialized by another thread
		if (connectionManager != null)
			return;

		try {
		// Verify msWait/msInterval values
			assertConnectionWait();

                // need to use this constructor, as it calls internaly startUp() method
                // to initialize extra variables and enable pooling
			//java.sql.DriverManager.setLogWriter(logWriter);
               		connectionManager = new ConnectionManager(
                        		driverName,
                        		URL,
                        		userName,
                        		password,
                        		minPool,
                        		maxPool
                        );
			// MsWait MUST be set BEFORE MsInterval
                        connectionManager.setMsWait(this.msWait);
			connectionManager.setMsInterval(this.msInterval);
                        connectionManager.setLoginTimeout(this.loginTimeout);

                        if (_txIsolation > 0)
                                setTransactionIsolation(_txIsolation);
                        else
                                _txIsolation = getTransactionIsolation();

			// finished all configuration
			this.configured(true);

        	} catch (JDOException e) {
                	throw e;
        	} catch (Exception e) {
                	throw new JDOFatalException(null, e);
		}
	}

	/**
         * INTERNAL
         * Throws JDOUnsupportedOptionException
	 */
	private void unsupported() {
		throw new JDOUnsupportedOptionException(I18NHelper.getMessage(messages,
			"persistencemanagerfactoryimpl.notsupported")); //NOI18N
	}

	/**
         * Close a connection
         */
        private void closeConnection(Connection conn)
        {
                try
                {
			if (conn != null) conn.close();
                }
                catch (Exception e)
                {
                        // Recover?
                }
 
                conn = null;
 
        }
}
