/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/common/Domain.java,v 1.53 2005/04/04 13:38:21 luetzkendorf Exp $
 * $Revision: 1.53 $
 * $Date: 2005/04/04 13:38:21 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.apache.slide.common;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.slide.authenticate.SecurityToken;
import org.apache.slide.event.DomainEvent;
import org.apache.slide.event.EventDispatcher;
import org.apache.slide.extractor.ExtractorManager;
import org.apache.slide.store.Store;
import org.apache.slide.util.conf.Configuration;
import org.apache.slide.util.conf.ConfigurationElement;
import org.apache.slide.util.conf.ConfigurationException;
import org.apache.slide.util.conf.Populate;
import org.apache.slide.util.logger.Logger;
import org.xml.sax.InputSource;

/**
 * The Domain controls access to its registered namespaces and performs
 * initialization and connection management on behalf of the namespaces.
 *
 * <p>
 *   From the client application's perspective, the domain represents the only
 *   directly accessible object. It is through this object that the client
 *   gains access to namespaces, using the static method
 *   <code>Domain.accessNamespace()</code>.
 * <p/>
 * <p>
 *   Since the domain acts as the root of a directory service, it is a static
 *   entity and there can be only one domain per JVM.
 * </p>
 * <h3>Initialization</h3>
 * <p>
 *   When Slide is first initialized, the Domain configuration is loaded.
 *   The location of the domain configuration file is given through the
 *   <code>org.apache.slide.domain</code> property in the Slide properties, or
 *   can be specified as argument to the static <code>Domain.init()</code>
 *   method.
 * </p>
 * <p>
 *   The domain configuration is written by an administrator and describes how
 *   each namespace is to be initialized. It includes information like:
 *   <ul>
 *     <li>
 *       The low-level services the namespace uses (structure store, content
 *       store, etc.)
 *     </li>
 *     <li>
 *       The namespace base topology, which includes the location of the base
 *       actions and paths in the namespace.
 *     </li>
 *   </ul>
 * </p>
 * <h3>Access and Security</h3>
 * <p>
 *   After initialization is complete, the client application can request
 *   access to the domain. It uses one of the two methods:
 *   <ul>
 *     <li>
 *       {@link #accessNamespace Domain.accessNamespace(SecurityToken
 securityObject, String namespace)}
 *       <br>
 *       Used to access a specific namespace. This method returns a
 *       <code>NamespaceAccessToken</code>, which thereafter must be used by
 *       the client application to perform operations on the namespace.
 *       <br><br>
 *     </li>
 *     <li>
 *       {@link #accessDomain Domain.accessDomain(SecurityToken
 securityObject)}
 *       <br>
 *       Enumerates the registered namespaces with this domain. This allows
 *       application to browse the list of available namespaces.
 *     </li>
 *   </ul>
 * </p>
 * <p>
 *   The Domain uses an object (argument <code>securityObject</code>) to
 *   decide whether or not the client should be granted access. A good
 *   candidate is a reference to the client servlet or servlet context.<br><br>
 *   <i><small>Note: Currently, access control on namespaces is not
 *   implemented.</small></i>
 * </p>
 *
 * @version $Revision: 1.53 $
 */
public final class Domain {
    
    
    // -------------------------------------------------------------- Constants
    
    
    private final static String LOG_CHANNEL = Domain.class.getName();
    
    
    // ----------------------------------------------------- Instance Variables
    
    
    /**
     * Compatibility with the new embedded domain.
     */
    private static EmbeddedDomain domain;
    
    
    /**
     * Namespaces hashtable.
     */
    private static Hashtable namespaces;
    
    private static boolean namespacesInitialized = false;
    
    /**
     * Active namespaces hashtable.
     */
    private static Hashtable activeNamespaces;
    
    
    /**
     * Slide logger.
     */
    private static Logger logger;
    
    
    /**
     * Default namespace.
     */
    private static String defaultNamespace;
    
    
    /**
     * Domain parameters
     */
    private static Hashtable parameters;
    
    /**
     * Properties set in domain.xml.
     */
    private static Hashtable properties;
    
    // --------------------------------------------------------- Public Methods
    
    
    /**
     * Tests if the domain has been initialized before.
     *
     * @return boolean True if the domain has already been initialized
     */
    public static boolean isInitialized() {
        
        return ((domain != null) || (namespaces != null));
        
    }
    
    /**
     *  Return true, if all namespaces have been initialized.
     */
    public static boolean namespacesAreInitialized() {
        return namespacesInitialized;
    }
    
    /**
     * Set the domain as having been initialized before.
     */
    public static void setInitialized(boolean initialized) {
        if (initialized) {
            if( namespaces == null ) namespaces = new Hashtable();
            if( activeNamespaces == null ) activeNamespaces = new Hashtable();
            if( parameters == null ) parameters = new Hashtable();
            if( properties == null) properties = new Hashtable();
            
            if (logger == null) {
                logger = new org.apache.slide.util.logger.SimpleLogger();
                logger.setLoggerLevel(Logger.INFO);
            }
            
            namespacesInitialized = true;
        }
        else {
            // TODO: don't know what to do here ???
        }
    }
    
    
    /**
     * Return the default namespace of this domain.
     *
     * @return the name of the default namespace
     */
    public static String getDefaultNamespace() {
        
        if (!isInitialized())
            selfInit();
        
        if (domain != null)
            return domain.getDefaultNamespace();
        
        return defaultNamespace;
        
    }
    
    
    /**
     * Access a Namespace.
     *
     * @param token Entity which wants access
     * @param namespaceName Name of the namespace on which access is requested
     * @return NamespaceAccessToken Access token to the namespace
     */
    public static NamespaceAccessToken accessNamespace(SecurityToken token,
                                                       String namespaceName) {
        
        if (!isInitialized())
            selfInit();
        
        if (domain != null)
            return domain.getNamespaceToken(namespaceName);
        
        Namespace namespace = (Namespace) namespaces.get(namespaceName);
        if (namespace == null)
            return null;
        else
            return new NamespaceAccessTokenImpl(namespace);
        
    }
    
    
    /**
     * Enumerate namespace names.
     */
    public static Enumeration enumerateNamespaces() {
        
        if (!isInitialized())
            return (new Vector()).elements();
        
        if (domain != null)
            return domain.enumerateNamespaces();
        
        return (namespaces.keys());
        
    }
    
    
    /**
     * Close a namespace.
     *
     * @param token Namespace access token
     */
    public static void closeNamespace(NamespaceAccessToken token) {
        token.disconnect();
        activeNamespaces.remove(token.getName());
    }
    
    
    /**
     * Clsose a namespace.
     *
     * @param token Entity which wants to close the namespace
     * @param namespaceName Name of the namespace
     */
    public static void closeNamespace(SecurityToken token,
                                      String namespaceName) {
        try {
            Namespace namespace = (Namespace) namespaces.get(namespaceName);
            namespace.disconnectServices();
            activeNamespaces.remove(namespaceName);
        } catch(Exception e) {
        }
    }
    
    
    /**
     * Access a Domain.
     *
     * @param token Service who wants access
     * @return DomainAccessToken Access token to the domain
     */
    public static DomainAccessToken accessDomain(SecurityToken token) {
        // Not implemented
        return null;
    }
    
    /**
     * holds the expanded file name of domain.xml
     **/
    private static String domainFileName = "Domain.xml";
    
    /**
     * Access the file name of domain.xml.
     *
     * @return String the expanded file name as a string.
     */
    public static String getDomainFileName() {
        return domainFileName;
    }
    
    
    
    /**
     * Domain initialization routine using Avalon configuration parser.
     *
     * @param configurationURL The file name to read the configuration
     */
    public static void init(java.net.URL configurationURL) throws Exception {
        if (isInitialized())
            return;
        
        domainFileName = configurationURL.getFile();
        init(configurationURL.openStream());
    }
    
    /**
     * Domain initialization routine using Avalon configuration parser.
     *
     * @param configurationFileName The file name to read the configuration
     */
    public static void init(String configurationFileName) throws Exception {
        if (isInitialized())
            return;
        
        domainFileName = configurationFileName;
        init(new FileInputStream(configurationFileName));
    }
    
    
    /**
     * Domain initialization routine using Avalon configuration parser.
     *
     * @param configurationInputStream The file name to read the configuration
     */
    public static void init(InputStream configurationInputStream)
        throws Exception {
        
        if (isInitialized())
            return;
        
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setNamespaceAware(false);
        factory.setValidating(false);
        SAXParser parser = factory.newSAXParser();
        Populate pop = new Populate();
        Configuration slideConfiguration =
            new ConfigurationElement(pop.load(new InputSource
                                                  (configurationInputStream), parser.getXMLReader()));
        
        Domain.init(slideConfiguration);
        
    }
    
    
    /**
     * Domain initialization routine using Avalon configuration parser.
     *
     * @param configuration Avalon configuration object
     */
    public static void init(Configuration configuration) {
        
        if (isInitialized())
            return;
        
        for(Enumeration e = configuration.getConfigurations("property"); e.hasMoreElements();) {
            Configuration property = (Configuration)e.nextElement();
            String name = property.getAttribute("name");
            String value = property.getValue();
            if (name != null && value != null) {
                setProperty(name, value);
            }
        }
        
        parameters = new Hashtable();
        
        defaultNamespace = configuration.getAttribute("default", "slide");
        parameters.put( "default", defaultNamespace );
        
        String loggerClass = configuration.getAttribute
            ("logger", "org.apache.slide.util.logger.SimpleLogger");
        parameters.put("logger", loggerClass);
        
        try {
            logger = (Logger) (Class.forName(loggerClass).newInstance());
            int loggerLevel = configuration.getAttributeAsInt("logger-level", Logger.INFO);
            logger.setLoggerLevel(loggerLevel);
            parameters.put("logger-level", "" + loggerLevel);
        } catch (Exception e) {
            if (logger == null) {
                System.err.println("Slide domain: initialization of logger failed.");
                e.printStackTrace();
            } else {
                error(e);
            }
            throw new DomainInitializationFailedError("Logger Problem: " + e.toString());
        }
        
        info("Initializing Domain");
        
        Enumeration eventConfigurations = configuration.getConfigurations("events");
        if ( eventConfigurations.hasMoreElements() ) {
            Configuration eventConfiguration = (Configuration)eventConfigurations.nextElement();
            EventDispatcher.getInstance().configure(eventConfiguration);
        }

        Enumeration extractorConfigurations = configuration.getConfigurations("extractors");
        if ( extractorConfigurations.hasMoreElements() ) {
            Configuration extractorConfiguration = (Configuration)extractorConfigurations.nextElement();
            ExtractorManager.getInstance().configure(extractorConfiguration);
        }
        
        namespaces = new Hashtable();
        activeNamespaces = new Hashtable();
        
        // Now initializing the domain
        
        // Loading configuration
        Properties properties =
            org.apache.slide.util.Configuration.getDefault();
        info("Domain configuration : " + properties.toString());
        
        // Loading domain parameters
        Enumeration parametersEnum =
            configuration.getConfigurations("parameter");
        while( parametersEnum.hasMoreElements() ) {
            Configuration p = (Configuration)parametersEnum.nextElement();
            parameters.put( p.getAttribute("name"), p.getValue() );
        }
        info( "Domain parameters: "+String.valueOf(parameters) );
        
        // Loading namespaces
        Enumeration namespaceDefinitions =
            configuration.getConfigurations("namespace");
        
        while (namespaceDefinitions.hasMoreElements()) {
            
            initNamespace((Configuration) namespaceDefinitions.nextElement());
            
        }
        
        if (namespaces.isEmpty()) {
            throw new DomainInitializationFailedError();
        }
        
        namespacesInitialized = true;

        if (DomainEvent.DOMAIN_INITIALIZED.isEnabled()) {
            EventDispatcher.getInstance().fireEvent(DomainEvent.DOMAIN_INITIALIZED, 
                    new DomainEvent(Domain.class));
        }
    }
    
    
    // --------------------------------------------------------- Logger Methods
    
    
    /**
     * Log.
     *
     * @param data The object to log.
     * @param channel The channel name used for logging.
     * @param level The level used for logging.
     */
    public static void log(Object data, String channel, int level) {
        logger.log(data, channel, level);
    }
    
    
    /**
     * Log.
     *
     * @param data The object to log.
     * @param level The level used for logging.
     */
    public static void log(Object data, int level) {
        logger.log(data,LOG_CHANNEL, level);
    }
    
    
    /**
     * Log.
     *
     * @param data The object to log.
     */
    public static void log(Object data) {
        logger.log(data,LOG_CHANNEL,Logger.DEBUG);
    }
    
    
    /**
     * Debug.
     *
     * @param data The object to log
     */
    public static void debug(Object data) {
        log(data,LOG_CHANNEL, Logger.DEBUG);
    }
    
    
    /**
     * Error.
     *
     * @param data The object to log
     */
    public static void error(Object data) {
        log(data,LOG_CHANNEL, Logger.ERROR);
    }
    
    
    /**
     * Error.
     *
     * @param data The object to log
     * @param t Throwable object
     */
    public static void error(Object data, Throwable t) {
        log(data + " - " + t.getMessage(),LOG_CHANNEL, Logger.ERROR);
        log(t,LOG_CHANNEL, Logger.ERROR);
    }
    
    
    /**
     * Info.
     *
     * @param data The object to log
     */
    public static void info(Object data) {
        log(data,LOG_CHANNEL, Logger.INFO);
    }
    
    
    /**
     * Warning.
     *
     * @param data The object to log
     */
    public static void warn(Object data) {
        log(data,LOG_CHANNEL, Logger.WARNING);
    }
    
    
    /**
     * Check if the channel with the specified level is enabled for logging.
     * This implementation ignores the channel specification
     *
     * @param channel The channel specification
     * @param level   The level specification
     */
    public static boolean isEnabled(String channel, int level) {
        return logger.isEnabled(channel, level);
    }
    
    
    /**
     * Check if the default channel with the specified level is enabled for
     * logging.
     *
     * @param level   The level specification
     */
    public static boolean isEnabled(int level) {
        return logger.isEnabled(LOG_CHANNEL,level);
    }
    
    
    /**
     * Check if the default channel with the DEBUG level is enabled for
     * logging.
     */
    public static boolean isDebugEnabled() {
        return isEnabled(LOG_CHANNEL,Logger.DEBUG);
    }
    
    
    /**
     * Check if the default channel with the WARNING level is enabled for
     * logging.
     */
    public static boolean isWarningEnabled() {
        return isEnabled(LOG_CHANNEL,Logger.WARNING);
    }
    
    
    /**
     * Check if the default channel with the INFO level is enabled for logging.
     */
    public static boolean isInfoEnabled() {
        return isEnabled(LOG_CHANNEL,Logger.INFO);
    }
    
    
    /**
     * Check if the default channel with the ERROR level is enabled for
     * logging.
     */
    public static boolean isErrorEnabled() {
        return isEnabled(LOG_CHANNEL,Logger.ERROR);
    }
    
    
    // -------------------------------------------------------- Package Methods
    
    
    /**
     * Set the embedded domain field.
     */
    static void setDomain(EmbeddedDomain domain) {
        Domain.domain = domain;
    }
    
    
    /**
     * Start domain (doesn't do anything yet).
     */
    static void start()
        throws Exception {
    }
    
    
    /**
     * Stop domain.
     */
    static void stop()
        throws Exception {
        
        Enumeration active = activeNamespaces.elements();
        while (active.hasMoreElements()) {
            ((Namespace) active.nextElement()).disconnectServices();
        }
        
    }
    
    
    /**
     * Add a namespace to the domain.
     *
     * @param namespace Namespace to be added
     */
    static void addNamespace(Namespace namespace) {
        namespaces.put(namespace.getName(), namespace);
        activeNamespaces.put(namespace.getName(), namespace);
    }
    
    
    /**
     * Get a namespace.
     *
     * @param namespaceName Name of the namespace
     * @return Namespace
     */
    static Namespace getNamespace(String namespaceName) {
        return (Namespace) namespaces.get(namespaceName);
    }
    
    
    /**
     * Get a domain parameter.
     * @param name the parameter name
     * @return the parameter value
     */
    public static String getParameter( String name ) {
        return (String)parameters.get( name );
    }
    
    
    /**
     * Get a domain parameter.
     * @param name the parameter name
     * @param defaultValue the default value to be returned
     * @return the parameter value
     */
    public static String getParameter( String name, String defaultValue ) {
        String result = (String)parameters.get( name );
        if( result == null )
            result = defaultValue;
        return result;
    }
    
    /**
     * Get a domain parameter - possibly overlaid by a store specific value.
     *
     * @param    name                the parameter name
     * @param    defaultValue        the default value
     * @param    store               the store to check for store-specific values
     *
     * @return   the parameter value
     *
     */
    public static String getParameter(String name, String defaultValue, Store store) {
        String result = (String)store.getParameter(name);
        if (result == null) {
            result = (String)parameters.get(name);
            if (result == null)
                result = defaultValue;
        }
        return result;
    }
    
    /**
     * Set the specified parameters
     *
     * @param    parameters          the parameters
     *
     */
    static void setParameters( Hashtable parameters ) {
        Domain.parameters = parameters;
    }
    
    public static String getProperty( String name ) {
    	if (properties != null) {
    		return (String)properties.get(name);
    	} else {
    		return null;
    	}
    }
    
    public static void setProperty( String name, String value) {
        if (properties == null) {
            properties = new Hashtable();
        }
        properties.put(name, value);
    }
    
    /**
     * Set the logger to be used by Slide.
     *
     * @param logger Logger the domain will use
     */
    static void setLogger(Logger logger) {
        Domain.logger = logger;
    }
    
    
    /**
     * Get the Domain logger.
     *
     * @return The domain logger
     */
    static Logger getLogger() {
        return Domain.logger;
    }
    
    
    /**
     * Default initialization of the domain.
     */
    static void selfInit() {
        
        String loggerClass = "org.apache.slide.util.logger.SimpleLogger";
        
        if (logger == null) {
            try {
                logger = (Logger)(Class.forName(loggerClass).newInstance());
                logger.setLoggerLevel(Logger.INFO);
            }
            catch (Exception e) {
                error(e);
                throw new DomainInitializationFailedError
                    ("Logger Problem: " + e.toString());
            }
        }
        
        info("Auto-Initializing Domain");
        
        // Now initializing the domain
        
        // Loading configuration
        Properties configuration =
            org.apache.slide.util.Configuration.getDefault();
        info("Domain configuration : " + configuration.toString());
        
        // First, retrieve the domain XML definition file from
        // the configuration
        String fileName =
            org.apache.slide.util.Configuration.getDefault().getProperty
            (org.apache.slide.util.Configuration.Property.DomainInitFilename,
             "Domain.xml");
        
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setNamespaceAware(false);
            factory.setValidating(false);
            SAXParser parser = factory.newSAXParser();
            
            FileInputStream is = new FileInputStream(fileName);
            //init(reader);
            Populate pop = new Populate();
            Configuration slideConfiguration =
                new ConfigurationElement(pop.load(new InputSource(is),
                                                  parser.getXMLReader()));
            
            init(slideConfiguration);
            
        } catch (javax.xml.parsers.FactoryConfigurationError e) {
            throw new DomainInitializationFailedError(e.getMessage());
        } catch (Exception e) {
            throw new DomainInitializationFailedError(e.getMessage());
        }
        
        info("Domain initialization complete");
        
    }
    
    
    // -------------------------------------------------------- Private Methods
    
    
    /**
     * Initializes a new namespace based on the given configuration data.
     *
     * @param configuration Configuration object
     */
    private static void initNamespace(Configuration configuration) {
        
        try {
            
            try {
                info("Initializing namespace : "
                         + configuration.getAttribute("name"));
            } catch (ConfigurationException e) {
                error(e);
            }
            
            String loggerClass = configuration.getAttribute
                ("logger", null);
            
            Logger namespaceLogger = null;
            if (loggerClass==null) {
                // if there is no logger defined on the namespace
                // use the domain logger
                namespaceLogger=logger;
            }
            else {
                try {
                    namespaceLogger =
                        (Logger) (Class.forName(loggerClass).newInstance());
                    namespaceLogger.setLoggerLevel(configuration.getAttributeAsInt
                                                       ("logger-level", Logger.INFO));
                } catch (Exception e) {
                    error(e);
                }
            }
            
            Namespace namespace = new Namespace();
            namespace.setName(configuration.getAttribute("name"));
            namespace.setLogger(namespaceLogger);
            
            Configuration namespaceConfigurationDefinition =
                configuration.getConfiguration("configuration");
            namespace.loadParameters(namespaceConfigurationDefinition);
            
            Configuration namespaceDefinition =
                configuration.getConfiguration("definition");
            namespace.loadDefinition(namespaceDefinition);
            addNamespace(namespace);
            try {
                Configuration namespaceBaseDataDefinition =
                    configuration.getConfiguration("data");
                namespace.loadBaseData(namespaceBaseDataDefinition);
            } catch (ConfigurationException e) {
                info("No basedata found for the namespace");
            }
            
            namespace.loadConfiguration(namespaceConfigurationDefinition);

            try {
                Configuration namespaceExtractorsDefinition = 
                    configuration.getConfiguration("extractors");
                namespace.loadExtractors(namespaceExtractorsDefinition);
            } catch (ConfigurationException e) {
                // ignore
            }

            // preparation to add services, please ignore now
            try {
                Configuration services = configuration.getConfiguration("services");
                Enumeration s = services.getConfigurations("service");
                while (s.hasMoreElements()) {
                    Configuration service = (Configuration)s.nextElement();
                    System.out.println("&&&&&&Name       = " + service.getName());
                    System.out.println("&&&&&&className = " + service.getAttribute("classname"));
                    System.out.println("&&&&&&serviceName  = " + service.getAttribute("name"));
                    Enumeration s_pars = service.getConfigurations("parameter");
                    while (s_pars.hasMoreElements()) {
                        Configuration s_par = (Configuration)s_pars.nextElement();
                        System.out.println("&&&&&&PAR Name       = " + s_par.getName());
                        System.out.println("&&&&&&PAR Name       = " + s_par.getAttribute("name"));
                        System.out.println("&&&&&&Par Val        = " + s_par.getValue());
                    }
                }
            }
            catch (ConfigurationException e){
                // silently ignore it ==> no services
            }
            catch (Exception e){
                error(e);
            }
            // preparation to add services, please ignore now
            
            
            
            info("Namespace configuration complete");
            
            if (DomainEvent.NAMESPACE_INITIALIZED.isEnabled()) {
                EventDispatcher.getInstance().fireEvent(DomainEvent.NAMESPACE_INITIALIZED, 
                        new DomainEvent(Domain.class, namespace));
            }
            
        } catch (Throwable t) {
            error(t);
        }
        
    }
    
    
    /**
     * Constructor
     *
     */
}

