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

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import javax.naming.Context;
import javax.naming.NamingException;

import org.objectweb.jonas.common.JProp;
import org.objectweb.jonas.naming.CompNamingContext;
import org.objectweb.jonas.common.Log;

import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.BasicLevel;

/**
 * This class implements a manager of org.objectweb.jonas.service.Service for JOnAS.
 *
 * @author Helene Joanin
 * Contributor(s):
 * 01/06/15: Regis Le Brettevillois - Libelis 
 *
 *
 * The services are defined in the jonas.properties files
 *  - 'jonas.services' property defines the list of the services,
 *  - for each service
 *     - 'jonas.service.<service-name>.class' property defines the class of the
 *       implementation of the service,
 *     - 'jonas.service.<service-name>.XXXX' properties define the configuration
 *       of the service
 */

public class ServiceManager {

    static final String SERVICES_PROP_NAME = JProp.JONASPREFIX + ".services";
    static final String PREFIX_SERVICE_PROP_NAME = JProp.JONASPREFIX + ".service";
    static final String JONAS_NAME = "jonas.name";
    static private Logger logger = null;

    // The ServerManager singleton value
    private static ServiceManager unique = null;

    // JOnAS properties
    static JProp props = null;
    // The list of the managed services
    static List services = null;
    // The managed services and their names
    static Map servicesByName = null;
    // The Configuration context for each managed service
    static Map contextsByService = null;

    /**
     * Private constructor to force only 1 instance.
     */
    private ServiceManager() {
        logger = Log.getLogger(Log.JONAS_SERVER_PREFIX);
    }

    /**
     * Get the unique instance.
     * Create it at first call.
     */
    public static ServiceManager getInstance() {
	if (unique == null) {
	    unique = new ServiceManager();
	}
	return unique;
    }

    /**
     * Returns the service which have the given name
     */
    public static Service getService(String name) throws ServiceException {
	Service s = (Service) servicesByName.get(name);
	if (s == null) {
	    throw new ServiceException("Unknown service '"+name+"'");
	}
	return s;
    }

    /**
     * Return the EJB service (service's name: 'ejb')
     */
    public static Service getEjbService() throws ServiceException {
	return((Service) getService("ejb"));
    }

    /**
     * Return the DataBase service (service's name: 'dbm')
     */
    public static Service getDataBaseService() throws ServiceException {
	return((Service) getService("dbm"));
    }

    /**
     * Return the Transaction service (service's name: 'jtm')
     */
    public static Service getTransactionService() throws ServiceException {
	return((Service) getService("jtm"));
    }

    /**
     * Return the JMS service (service's name: 'jms')
     */
    public static Service getJmsService() throws ServiceException {
	return((Service) getService("jms"));
    }

    /**
     * Return the Security service (service's name: 'security')
     */
    public static Service getSecurityService() throws ServiceException {
	return((Service) getService("security"));
    }

     /**
     * Return the JMX service (service's name: 'jmx')
     */
    public static Service getJmxService() throws ServiceException {
	return((Service) getService("jmx"));
    }

    /**
     * Return the Registry  service (service's name: 'registry')
     */
    public static Service getRegistryService() throws ServiceException {
	return((Service) getService("registry"));
    }

    /**
     * Return the Tomcat  service (service's name: 'tomcat')
     */
    public static Service getTomcatService() throws ServiceException {
	return((Service) getService("tomcat"));
    }

   /**
     * Starts the managed services.
     */
    public void startServices() throws ServiceException {
        logger.log(BasicLevel.DEBUG,"entering");
	if (props == null) {
	    try {
		props = new JProp();
	    } catch (Exception e) {
		throw new ServiceException("Cannot start services", e);
	    }
	}
	Service[] services = getServices();
	for (int i = 0; i < services.length; i++) {
	    Service service = services[i];
	    try {
		service.init((Context)contextsByService.get((Object)service));
		service.start();       
		logger.log(BasicLevel.WARN, service.getName()+" service started");
		
		try {
		    org.objectweb.jonas.naming.NamingManager.getInstance();       
		} catch (NamingException e) {
		    logger.log(BasicLevel.ERROR, "JOnAS: Cannot start Naming Manager ("+e+")");
		}
	    } catch (ServiceException e) {
		// Stop the services that have been already started
		String stopMessage = "";
		try {
		    stopServices();
		} catch (ServiceException se) {
		    stopMessage = "\n"+se.toString(); 
		}
		throw new ServiceException("Cannot init/start the service '"
					   +service.getName()+"': " + e
					   +stopMessage);
	    }
	}
    }

    /**
     * Returns the list of the managed services
     */
    public Service[] getServices() throws ServiceException {
	if (services == null) {
	    readServices();
	}
	Service[] ss = new Service[services.size()];
	for (int i = 0; i<services.size(); i++) {
	    ss[i] = (Service) services.get(i);
	}
	return ss;
    }

    /**
     * return the list of services + registry if needed
     */

    public String [] getServiceNames() {
	String [] serviceNames;
	serviceNames = props.getValueAsArray(SERVICES_PROP_NAME);
	if (serviceNames == null){
            return null;
        }
	if (!serviceNames[0].equals("registry")){
	    String [] arrayservices = new String[serviceNames.length+1];
	    arrayservices[0] = "registry";
	    for (int i = 0; i < serviceNames.length; i++)
		arrayservices[i+1]=serviceNames[i];
	    return arrayservices;
	} else {
	    return serviceNames;
	}
    }
    
    /**
     * For each service, create it and its associated configuration context.
     * (creates services, contextsByService and servicesByName)
     */
    protected void readServices() throws ServiceException {
        logger.log(BasicLevel.DEBUG,"entering");
	services = new ArrayList();
	contextsByService = new Hashtable();
	servicesByName = new Hashtable();
	String [] serviceNames;
	serviceNames = getServiceNames();
	if (serviceNames == null) {
	    throw new ServiceException("Property '"+SERVICES_PROP_NAME+"' is missing in '"
				       +JProp.JONASPREFIX+"' properties file"); 
	}
	for (int i = 0; i < serviceNames.length; i++) {
	    String serviceName = serviceNames[i];
	    Service serviceObj = createServiceFrom(serviceName);
	    if (serviceObj != null) {
		try {
		    Context ctx = createServiceContextFor(serviceName);
		    services.add(serviceObj);
		    contextsByService.put(serviceObj, ctx);
		    servicesByName.put(serviceName, serviceObj);
		} catch (NamingException e) {
		    throw new ServiceException("cannot create the context name of the service '"
					       +serviceName+"'",
					       e);
		}
	    }
	}
    }

    /**
     * Creates and returns the service which have the given name.
     * (Uses the 'jonas.service.<serviceName>.class' property for that)
     */
    protected Service createServiceFrom(String serviceName) throws ServiceException {
        logger.log(BasicLevel.DEBUG," "+serviceName);
	String prefixPropName = PREFIX_SERVICE_PROP_NAME+"."+serviceName;
	String serviceClassName = props.getValue(prefixPropName+".class");
	if (serviceClassName == null) {
	    throw new ServiceException("Property '"+prefixPropName+".class' missing in '"
				       +JProp.JONASPREFIX+"' properties file");
	}
	try {
	    ClassLoader classLoader = this.getClass().getClassLoader();
	    if (classLoader == null) {
		classLoader = ClassLoader.getSystemClassLoader();
	    }
	    Class serviceClass = classLoader.loadClass(serviceClassName);
	    Service service = (Service)serviceClass.newInstance();
	    service.setName(serviceName);
	    return(service);
	} catch (Exception e) {
	    throw new ServiceException("Error when creating the service '"+serviceName+"'", e);
	}
    }

    /**
     * Creates and returns the context for the configuration of the service
     * which have the given name.
     * (Uses the 'jonas.service.<serviceName>.XXXX' properties for that)
     */
    protected Context createServiceContextFor(String serviceName) throws NamingException {
        logger.log(BasicLevel.DEBUG," "+serviceName);
	String prefixPropName = PREFIX_SERVICE_PROP_NAME+"."+serviceName;
	CompNamingContext ctx = new CompNamingContext(serviceName);
	for (Enumeration e = props.getEnv().propertyNames(); e.hasMoreElements() ;) {
	    String propName = (String)e.nextElement();
	    if (propName.startsWith(prefixPropName+".")) {
		if (!propName.equals(prefixPropName+".class")) {
		    ctx.rebind(propName.substring(prefixPropName.length()+1,propName.length()),
			       props.getValue(propName));
		}
	    }			    
	}
	// set Jonas name properties to all ctx
	ctx.rebind(JONAS_NAME, props.getValue(JONAS_NAME));
	return ctx;
    }

    /**
     * Management Method: Stop all services.
     * The services are stopped in the reverse order of their starting.
     * (Continue the processing of stopping for the others services, even if there is a problem
     * for one service.)
     */
    public void stopServices() throws ServiceException  {
	String msgError = new String();
	String sepError = "";
	Service[] services = getServices();
	for (int i = services.length-1; i >= 0; i--) {
	    Service service = services[i];
	    if (service.isStarted()) {
		try {
		    service.stop();
		    logger.log(BasicLevel.WARN, service.getName()+" service stopped");
		} catch (ServiceException e) {
		    msgError = msgError.concat(sepError+"Cannot stop the service '"
					       +service.getName()+"': " + e);
		    sepError = "\n";
		}
	    }
	}
	if (msgError.length() != 0) {
		throw new ServiceException(msgError);
	}
    }
}
