/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.org
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */


package org.objectweb.jonas.dbm;

import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Set;
import java.util.Iterator;
import java.rmi.RemoteException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.NamingException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.XADataSource;
import javax.transaction.Transaction;
import org.enhydra.jdbc.pool.StandardPoolDataSource;
import org.objectweb.jonas.common.JProp;
import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.jmx.JmxService;
import org.objectweb.jonas.jmx.JonasObjectName;
import org.objectweb.jonas.jtm.TransactionService;
import org.objectweb.jonas.naming.CompNamingContext;
import org.objectweb.jonas.service.ServiceManager;
import org.objectweb.jonas.service.AbsServiceImpl;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.BasicLevel;


/**
 * DatabaseService acts as a factory for the DataSource objects.
 * Its goal is to create such objects and to register them in JNDI
 *
 * @author Philippe Durieux
 *
 * Contributor(s):
 *
 * 00/18/04 Jun Inamori (j-office@osa.att.ne.jp)
 *          New implementation of unbindDataSources for closing correctly 
 *          all connections at server shutdown.
 */
public class DataBaseServiceImpl extends AbsServiceImpl implements DataBaseService, DataBaseServiceImplMBean {

    static private Logger logger = null;

    // List of DataSource in use.
    // Better to use only 1 list ?
    private Vector cmList = new Vector();	// ConnectionManager
    private Vector ldsList = new Vector(); 	// StandardPoolDataSource

    // List of JonasXAPoolDataSource (used by JNDI lookup)
    private static Hashtable poolList = new Hashtable();

    // Names of the DataSource to create when starting the DataBase service
    private Vector dataSourceNames = new Vector();

    // Transaction Service reference
    private TransactionService transactionService = null;

    // Initial Context for Naming
    private Context ictx = null;
    
    
    // -------------------------------------------------------------------
    // Service Implementation
    // -------------------------------------------------------------------

    /**
     * Initialization of the DataBase service.
     * Configuration information is passed thru a Context object.
     */
    public void doInit(Context ctx) throws ServiceException {
        logger = Log.getLogger(Log.JONAS_DBM_PREFIX);
	// Avoid using NamingManager here: performance is not a goal here.
	try {
	    ictx = new InitialContext();
	} catch (NamingException e) {
	    logger.log(BasicLevel.ERROR, "Cannot create initial context when DataBase service initializing");
	    throw new ServiceException("Cannot create initial context when DataBase service initializing", e);
	}

	// Get the list of the datasource names
	String ds = null;
	try {
	    ds = (String)ctx.lookup("datasources");
	} catch (NamingException e) {
	    // No problem if there is no value for 'datasources'
	}
	if (ds != null) {
	    StringTokenizer st = new StringTokenizer(ds, ",");
	    while (st.hasMoreTokens()) {
		dataSourceNames.add(st.nextToken().trim());
	    }
	}

	// Get un reference to the Transaction service
	try {
	    transactionService =
		(TransactionService) ServiceManager.getInstance().getTransactionService();
	} catch (ServiceException e) {
	    logger.log(BasicLevel.ERROR, "Cannot get the Transaction service: "+e);
	    throw e;
	}

	logger.log(BasicLevel.DEBUG, "DataBaseService initialized");
	
    }

    /**
     * Starting DataBase service
     * Initialization of the service is already done.
     */
    public void doStart() throws ServiceException {

	// creates connection managers for each datasource
	String dsName = null;
	for (int i = 0; i < dataSourceNames.size(); i++) {
	    dsName = (String) dataSourceNames.elementAt(i);
	    try {
		JProp prop = new JProp(dsName);
		logger.log(BasicLevel.DEBUG, "Creating Datasource "+dsName);
		createDataSource(prop.getFilesEnv());
	    } catch (Exception e) {
		logger.log(BasicLevel.ERROR, "JOnAS: Cannot create datasource: "+dsName+" "+e);
		logger.log(BasicLevel.ERROR, "Please check if "+dsName+".properties is available");
	    }
	}

	try {
	    // Register DataBaseService MBean : JmxDataBaseService 
	    ((JmxService)ServiceManager.getJmxService()).getJmxServer().registerMBean(this, JonasObjectName.databaseService());
	} catch (ServiceException se) {
	    // Jmx Service not available, do nothing
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "DataBaseService: Cannot start the DataBase service:\n"+e);
	    throw new ServiceException("DataBaseService: Cannot start the DataBase service", e);
	}

    }

    /**
     * Stopping DataBase service
     * Unbind DataSources
     */
    public void doStop() throws ServiceException {
	try {
	    unbindDataSources();
	} catch (NamingException e) {
	    logger.log(BasicLevel.ERROR, "Cannot unbind datasources " + e);
	    throw new ServiceException("Cannot unbind datasources ", e);
	}

	try {
	    MBeanServer mbeanServer = ((JmxService)ServiceManager.getJmxService()).getJmxServer();
	    // unregister Database Service MBean : DatabaseServiceImplMBean 
	    mbeanServer.unregisterMBean(JonasObjectName.databaseService());
	    // unregister all Datasource
	    Set objectNames = mbeanServer.queryNames(JonasObjectName.allDatasources(), null) ;
	    Iterator it  = objectNames.iterator();
	    while (it.hasNext()) {
		mbeanServer.unregisterMBean((ObjectName)it.next());
	    }
	} catch (ServiceException se) {
	    // Jmx Service not available, do nothing
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "EJBService: Cannot stop the DataBase service:\n"+e);
	    throw new ServiceException("DataBaseService: Cannot stop the DataBase service",e);
	}

	logger.log(BasicLevel.DEBUG, "DataBaseService stopped");
    }
    
    // -------------------------------------------------------------------
    // DataBaseService Implementation
    // -------------------------------------------------------------------

    /**
     * Creates a ConnectionManager (implementation of sql.dataSource).
     *  @param dsd - a set of properties that describes a dataSource and the ConnectionPool
     *  <p>If datasource.factory is set, we create a JonasXAPoolDataSource.
     *  If not set, we create an old fashioned ConnectionManager</p>
     */
    public void createDataSource(Properties dsd) throws Exception {

	// Get properties common to all DataSource types
	String dsName = dsd.getProperty("datasource.name");
	String className = dsd.getProperty("datasource.classname", "no class name");
	String url = dsd.getProperty("datasource.url", "no url");
	String description = dsd.getProperty("datasource.description", "no desc");
	String user = dsd.getProperty("datasource.username", "");
	String password = dsd.getProperty("datasource.password", "");
	String xaFactory = dsd.getProperty("datasource.factory", "none");
	String xaName = dsd.getProperty("datasource.xadataname", "none");
	String connCheckLevel = dsd.getProperty("jdbc.connchecklevel", "1");
	String connMaxAge = dsd.getProperty("jdbc.connmaxage", "30");
	String defaultStatement = dsd.getProperty("jdbc.connteststmt", "SELECT 1");

	if (xaFactory.equals("none")) {
	    // ConnectionManager (JOnAS DataSource)
	    logger.log(BasicLevel.DEBUG, "create JOnAS ConnectionManager");
	    ConnectionManager ds = new ConnectionManager();
	    ds.setDataSourceName(dsName);
	    ds.setUrl(url);
	    ds.setClassName(className);
	    ds.setUserName(user);
	    ds.setPassword(password);
	    ds.setTransactionIsolation(dsd.getProperty("datasource.isolationlevel", ""));
	    ds.setDataSourceXADataName(xaName);
	    ds.setDataSourceDescription(description);
	    ds.poolConfigure(connCheckLevel, connMaxAge, defaultStatement);
	    cmList.addElement(ds);
	    ictx.rebind(dsName, ds);
	    logger.log(BasicLevel.WARN, "Mapping ConnectionManager "+url+" on "+dsName);
	    try {
		// MBean Datasource registering
		((JmxService)ServiceManager.getJmxService()).getJmxServer()
		    .registerMBean(ds,JonasObjectName.datasource(ds.getDSName()));
	    } catch (ServiceException se) {
		// Jmx Service not available, do nothing
	    }
	    
	} else {
	    // JonasXAPoolDataSource (LUTRIS DataSource)
	    logger.log(BasicLevel.DEBUG, "create Lutris DataSource");
	    JonasXAPoolDataSource ds = new JonasXAPoolDataSource();
	    boolean debug = logger.isLoggable(BasicLevel.DEBUG);
	    ds.setDebug(debug);
	    ds.setLogWriter(Log.getLogWriter("org.objectweb.jonas.dbm"));
	    
	    ds.setCheckLevelObject((new Integer(connCheckLevel)).intValue());
	    ds.setLifeTime((new Integer(connMaxAge)).intValue()*60*1000);
	    ds.setJdbcTestStmt(defaultStatement);
	    try {
		ds.setMinSize((new Integer(dsd.getProperty("jdbc.minconpool", "10"))).intValue());
		ds.setMaxSize((new Integer(dsd.getProperty("jdbc.maxconpool", "20"))).intValue());
	    } catch (Exception e) {
		logger.log(BasicLevel.ERROR, " Cannot set min and max size of the pool");
	    }
	    
	    ds.setDataSourceName(xaName);
	    ds.setDSName(dsName);
	    ds.setUser(user);
	    ds.setPassword(password);
	    ds.setClassName(className);
	    ds.setUrl(url);
	    ds.setXAFactory(xaFactory);
	    ds.setDataSourceDescription(description);
	    ds.setTransactionManager(transactionService.getTransactionManager());
	    ds.setSleepTime((new Integer(dsd.getProperty("jdbc.sleeptimepool", "300000"))).intValue());
	    String gc = dsd.getProperty("jdbc.gcpool", "false");
	    ds.setGC(gc.equals("true"));

	    String GENERATIONPOOL = "jdbc.generationpool";
	    String DEFAULT_GENERATIONPOOL = "1";
	    if (!GENERATIONPOOL.equals(DEFAULT_GENERATIONPOOL))
		ds.setGeneration((new Integer(dsd.getProperty(GENERATIONPOOL, DEFAULT_GENERATIONPOOL))).intValue());
	    
	    String DEADLOCKPOOL = "jdbc.deadlockpool";
	    String DEFAULT_DEADLOCKPOOL = "300000";	// 5 minutes
	    ds.setDeadLockMaxWait((new Integer(dsd.getProperty(DEADLOCKPOOL, DEFAULT_DEADLOCKPOOL))).intValue());
	    
	    String LOOPWAITPOOL = "jdbc.loopwaitpool";
	    String DEFAULT_LOOPWAITPOOL = "10000";		// 10 seconds
	    ds.setDeadLockRetryWait((new Integer(dsd.getProperty(LOOPWAITPOOL, DEFAULT_LOOPWAITPOOL))).intValue());

	    ldsList.addElement(ds);

	    // rebind in JNDI.
	    // Set it in the poolList : lookup must retrieve this object later.
	    poolList.put(xaName, ds);
	    ictx.rebind(dsName, ds);

	    // bind all information from the XA factory
	    try {
		CommonDataSource connect = (CommonDataSource)(Class.forName(xaFactory).newInstance());
		connect.setProperties(url, className, user, password);
		connect.setTransactionManager(transactionService.getTransactionManager());
		connect.setMinCon((new Integer(dsd.getProperty("datasource.mincon", "5"))).intValue());
		connect.setMaxCon((new Integer(dsd.getProperty("datasource.maxcon", "10"))).intValue());
		connect.setDebug(debug);
		connect.setLogWriter(Log.getLogWriter("org.objectweb.jonas.dbm"));

		ds.xads = (XADataSource)connect;
		ictx.rebind(xaName, connect);
	    } catch (ClassNotFoundException e) {
		logger.log(BasicLevel.ERROR, " Class Not Found Exception");
		throw e;
	    } catch (InstantiationException e) {
		logger.log(BasicLevel.ERROR, " Cannot create instance of XAFactory");
		throw e;
	    } catch (IllegalAccessException e) {
		logger.log(BasicLevel.ERROR, " Illegal Access Exception");
		throw e;
	    } catch(SQLException e) {
		logger.log(BasicLevel.ERROR, " SQLException:"+e);
		throw e;
	    }
	    try {
		// MBean Datasource registering
		((JmxService)ServiceManager.getJmxService()).getJmxServer().
		    registerMBean(ds, JonasObjectName.datasource(ds.getDSName()));
	    } catch (ServiceException se) {
		// Jmx Service not available, do nothing
	    } 

	    logger.log(BasicLevel.WARN, "Mapping JonasXAPoolDataSource "+url+" on "+dsName);
            logger.log(BasicLevel.WARN, "XADataSource = "+xaName);
	}
    }

    /**
     * Propagation of the new configuration of the trace functionnalities
     * in the driver
    */
    public void traceJdbc() {

	if (ldsList.size() > 0) {
	    PrintWriter pr = Log.getLogWriter("org.objectweb.jonas.dbm");
	    for (Enumeration lk = ldsList.elements(); lk.hasMoreElements(); ) {
		JonasXAPoolDataSource cm = (JonasXAPoolDataSource)lk.nextElement();
		boolean debug = logger.isLoggable(BasicLevel.DEBUG);
		cm.setDebug(debug);
		cm.setLogWriter(pr);
		CommonDataSource connect = (CommonDataSource)cm.xads;
		connect.setDebug(debug);
		connect.setLogWriter(pr);
	    }
	}
    }
    
    /*
     * Unbind dataSource names from the Naming
     */
    public void unbindDataSources() throws NamingException {

	logger.log(BasicLevel.DEBUG, "entering");

	try {
	    if (cmList.size() > 0) {
		String dsn = null;
		for (Enumeration lk = cmList.elements(); lk.hasMoreElements(); ) {
		    ConnectionManager cm = (ConnectionManager) lk.nextElement();
		    cm.closeAllConnection();
		    dsn = cm.getDataSourceName();
		    ictx.unbind(dsn);
		}
	    }
	    if (ldsList.size() > 0) {
		for (Enumeration lk = ldsList.elements(); lk.hasMoreElements(); ) {
		    StandardPoolDataSource cm = (StandardPoolDataSource) lk.nextElement();
		    cm.stopPool();
		    String dsn = cm.getDataSourceName();
		    ictx.unbind(dsn);
		}
	    }
	} catch (NamingException e) {
	    logger.log(BasicLevel.ERROR, "cannot unbind DataSources : " + e);
	    throw e;
	}
	
	// unbind all mbean 
	try {
	    MBeanServer mbeanServer = ((JmxService)ServiceManager.getJmxService()).getJmxServer();
	    // unregister all Datasource
	    Set objectNames = mbeanServer.queryNames(JonasObjectName.allDatasources(), null) ;
	    Iterator it  = objectNames.iterator();
	    while (it.hasNext()) {
		mbeanServer.unregisterMBean((ObjectName)it.next());
	    }
	} catch (ServiceException se) {
	    // Jmx Service not available, do nothing
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "DataBase Service : Cannot unbind the DataSource:\n"+e);
	}	
    }

    /**
     * get the XAPoolDataSource matching the DataSource name
     */
    public static JonasXAPoolDataSource getXAPoolDataSource(String dsname) {
        JonasXAPoolDataSource cm = (JonasXAPoolDataSource) poolList.get(dsname);
	return cm;
    }

    //------------------------------------------------------------------------------------------

    /**
     * MBean method: 
     * @return Integer Total Number of Datasource available in JOnAS
     */ 
    public Integer getCurrentNumberOfDataSource() {
	return new Integer(cmList.size() + ldsList.size());
    }
    
    /**
     * MBean method:
     * @return Integer Total Number of JDBC connection open
     */ 
    public Integer getTotalCurrentNumberOfJDBCConnectionOpen(){
	int result = 0;
	if (cmList.size() > 0) {
	    for (Enumeration lk = cmList.elements(); lk.hasMoreElements(); ) {
		ConnectionManager cm = (ConnectionManager)lk.nextElement();
		result += cm.getCurrentNumberOfJDBCConnectionOpen().intValue();
	    }
	}
	if (ldsList.size() > 0) {
	    for (Enumeration lk = ldsList.elements(); lk.hasMoreElements(); ) {
		JonasXAPoolDataSource cm = (JonasXAPoolDataSource)lk.nextElement();
		result += cm.getCurrentNumberOfJDBCConnectionOpen().intValue();
	    }
	}
	return new Integer(result);
    }
	
    /**
     * MBean method:
     * @return true if the datasource is loaded
     */
    public boolean isLoadedDataSource(String dsName) {
	logger.log(BasicLevel.DEBUG, "entering");
	boolean result = false;
	if (cmList.size() > 0) {
	    for (Enumeration lk = cmList.elements(); lk.hasMoreElements(); ) {
		ConnectionManager cm = (ConnectionManager)lk.nextElement();
		if (cm.getDSName().equals(dsName)) 
		    return true;
	    }
	}
	if (ldsList.size() > 0) {
	    for (Enumeration lk = ldsList.elements(); lk.hasMoreElements(); ) {
		JonasXAPoolDataSource cm = (JonasXAPoolDataSource)lk.nextElement();
		if (cm.getDSName().equals(dsName)) 
		    return true;
	    }
	}
	return result;
    }

    /**
     * MBean method:
     * @param fname of the data source to unload
     */
    public void unloadDataSource(String dsName) {
	try {
	    if (cmList.size() > 0) {
		for (Enumeration lk = cmList.elements(); lk.hasMoreElements(); ) {
		    ConnectionManager cm = (ConnectionManager)lk.nextElement();
		    if (cm.getDSName().equals(dsName)) {
			// unbind data source 
			cm.closeAllConnection();
			String dsn = cm.getDataSourceName();
			ictx.unbind(dsn);
			// remove datasource 
			cmList.remove(cm);
			try {
			    // unregistering MBean
			    ((JmxService)ServiceManager.getJmxService()).getJmxServer().
				unregisterMBean(JonasObjectName.datasource(cm.getDSName()));
			} catch  (ServiceException se) {
			    // Jmx Service not available, do nothing
			}
			return;
		    }
		}
	    }
	    if (ldsList.size() > 0) {
		for (Enumeration lk = ldsList.elements(); lk.hasMoreElements(); ) {
		    JonasXAPoolDataSource cm = (JonasXAPoolDataSource)lk.nextElement();
		    if (cm.getDSName().equals(dsName)) {
			// unbind data source
			cm.stopPool();
			String dsn = cm.getDataSourceName();
			ictx.unbind(dsn);
			// remove datasource
			ldsList.remove(cm);
			try {
			    // unregistering MBean
			    ((JmxService)ServiceManager.getJmxService()).getJmxServer().
				unregisterMBean(JonasObjectName.datasource(cm.getDSName()));
			}  catch  (ServiceException se) {
			    // Jmx Service not available, do nothing
			}
			return;
		    }
		}
	    }
	}  catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "cannot unload DataSources : " + e);
	}
    }

    /**      
     * MBean method: 
     * @return datasource properties from a local file
     */
    public Properties getDataSourcePropertiesFile(String dsFile) {
	try {
	    return new JProp(dsFile).getFilesEnv();
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "Please check if "+dsFile+".properties is available in JONAS_ROOT/config, HOME, or .");
	    return null;
	}
    }

    /**
     * MBean method:
     * load new datasource with this parametters
     */
    public void loadDataSource(Properties prop) throws RemoteException {
	String dsName = prop.getProperty("datasource.name");
	try {
	    if (isLoadedDataSource(dsName)) {
		unloadDataSource(dsName);
	    }
	    createDataSource(prop);
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "JOnAS: Cannot create datasource: "+dsName);
	    throw new RemoteException("JOnAS: Cannot create datasource: "+dsName+",\n"+e);
	}	
    }
    
    
    
}
	
