/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@ow2.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
 *
 * Initial developer(s): ____________________________________.
 * Contributor(s): Adriana Danes
 *
 * --------------------------------------------------------------------------
 * $Id:JProp.java 10822 2007-07-04 08:26:06Z durieuxp $
 * --------------------------------------------------------------------------
 */


package org.ow2.jonas.lib.bootstrap;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.naming.Context;

import org.ow2.jonas.lib.util.ConfigurationConstants;
import org.ow2.jonas.properties.ServerProperties;


/**
 * This class manages configuration properties for a JOnAS Server.
 * It adopts the singleton design-pattern. Configuration parameters
 * are read from .properties file (jonas.properties or resource.properties).
 * In order to support dynamically created resources, JProp also allows for
 * a .properties file generation using a java.lang.Properties object content
 * @author jonas-team
 * @author Adriana Danes
 * @author Florent Benoit
 * <ul>
 * <li>
 * ../03/2003 Adriana Danes
 *   <ol>
 *   <li>Manage unique instances for resource.properties
 *   <li>Replace 'config' with 'conf'
 *   <li>Change initial configuration policy : read properties file in a sole location : JONAS_BASE/CONFIG_DIR/
 *   <li>Replace method name <code>getFilesEnv</code> to <code>getConfigFileEnv</code>
 *   </ol>
 * <li>
 * 05/05/2003 Adriana Danes. Support JProp instance creation for dynamically created resources.
 * </ul>
 * 05/2003 Florent Benoit. Add support of xml files and checkstyle
 */
public class JProp implements ServerProperties {

    /**
     * Domain name.
     */
    public static final String DOMAIN_NAME = "domain.name";

    /**
     * -Djonas.base property.
     */
    public static final String JONAS_BASE = "jonas.base";

    /**
     * jonas.master property.
     */
    public static final String JONAS_MASTER = "jonas.master";

    /**
     * jonas.development property.
     */
    public static final String JONAS_DEVELOPMENT = "jonas.development";

    /**
     * configuration directory name.
     */
    private static final String CONFIG_DIR = "conf";

    /**
     * Repositories directory name.
     */
    private static final String REPOSITORIES_DIR = "repositories";

    /**
     * System properties.
     */
    private static Properties systEnv = System.getProperties();

    /**
     * JONAS_ROOT.
     */
    private static String jonasRoot = systEnv.getProperty("jonas.root");

    /**
     * JONAS_BASE.
     */
    private static String jonasBase = systEnv.getProperty(JONAS_BASE);

    /**
     * Separator of file.
     */
    private static String fileSeparator = systEnv.getProperty("file.separator");

    /**
     * Content of the file (xml).
     */
    private String configFileXml  = null;

    /**
     * Content of a text file (may be a .properties file.
     */
    private String versionFileContent = null;

    /**
     * Properties of the config file.
     */
    private Properties configFileEnv  = new Properties();

    /**
     * System properties + properties of the file.
     */
    private Properties allEnv = null;

    /**
     * Name of the property file.
     */
    private String propFileName = null;

    /**
     * The JProp singleton for jonas.properties file.
     */
    private static JProp unique = null;

    /**
     * The multiple JProp objects for the resources configuration files (resource.properties)
     * the keys are the configuration file names and the values, the JProp objects.
     */
    private static Hashtable<String, JProp> multiple = new Hashtable<String, JProp>();

    /**
     * Default master value.
     */
    private boolean master = false;

    /**
     * Default execution mode value set to development.
     */
    private boolean development = true;

    /**
     * Private constructor which reads a resource.properties file.
     * @param fileName the file name to read (the resource name)
     */
    private JProp(final String fileName) {
        readFile(fileName);
    }

    /**
     * Public constructor which reads the JONAS properties file (jonas.properties).
     */
    public JProp() {
        readFile(ConfigurationConstants.DEFAULT_JONAS_PREFIX);
    }

    /**
     * private constructor which writes properties in the provided file.
     * @param fileName the file name in which configuration properties are written
     * @param props configuration properties used to initialize the resource
     */
    private JProp(final String fileName, final Properties props) {
        writePropsToFile(fileName, props);
    }

    /**
     * private constructor which writes text in the provided file.
     * @param fileName the file name in which configuration is written
     * @param txt xml configuration for this resource
     */
    //TODO to remove
    private JProp(final String fileName, final String txt) {
        writeXmlToFile(fileName, txt);
    }

    /**
     * Get the unique instance corresponding to the JOnAS server.
     * Create it at first call.
     * @return unique instance corresponding to the JOnAS server.
     */
    public static JProp getInstance() {
        if (unique == null) {
            unique = new JProp();
        }
        return unique;
    }

    /**
     * Get one of the multiple instances corresponding to a given resource.
     * Create it at first call with a given configuration file name
     * @param fileName the name of the configuration file which is given by the resource name
     * @return one of the multiple instances corresponding to a given resource.
     */
    public static JProp getInstance(final String fileName) {
        if (!multiple.containsKey(fileName)) {
            multiple.put(fileName, new JProp(fileName));
        }
        return multiple.get(fileName);
    }

    /**
     * Get one of the multiple instances corresponding to a given resource.
     * Create it at first call with a given configuration file name
     * @param fileName the name of the configuration file
     * @param props the content of the configuration file to be written in fileName
     * @return one of the multiple instances corresponding to a given resource.
     */
    public static JProp getInstance(final String fileName, final Properties props) {
        if (!multiple.containsKey(fileName)) {
            multiple.put(fileName, new JProp(fileName, props));
        }
        return multiple.get(fileName);
    }

    /**
     * Remove the JProp instance corresponding to the given file name from the 'multiple'
     * data structure.
     * @param fileName file name corresponding to the name of the resource to be removed
     */
    public static void removeInstance(final String fileName) {
        if (multiple.containsKey(fileName)) {
            multiple.remove(fileName);
        }
    }

    /**
     * Remove the JProp instance corresponding to the given file name from the 'multiple'
     * data structure and delete the file.
     * @param fileName file name corresponding to the name of the resource to be removed
     * and deleted from the file system
     */
    public static void deleteInstance(final String fileName) {
        if (multiple.containsKey(fileName)) {
            multiple.remove(fileName);
            // Check the JONAS_BASE environment property
            if (jonasBase == null) {
                return;
            }
            jonasBase = jonasBase.trim();

            // JONAS_BASE/conf/fileName
            String propFileName = jonasBase + fileSeparator + CONFIG_DIR + fileSeparator + fileName + ".properties";
            File del = new File(propFileName);
            del.delete();
        }
    }

    /**
     * Write configuration properties in file getProperty(JONAS_BASE)/conf/fileName
     * @param fileName name of the configuration file to write
     * @param props Properties to register in the fileName file
     * @throws Exception if it can't write to the specified file
     */
    private void writePropsToFile(final String fileName, final Properties props) {

        // Check the JONAS_BASE environment property
        if (jonasBase == null) {
            throw new RuntimeException("JOnAS configuration error: environment property jonas.base not set!");
        }
        jonasBase = jonasBase.trim();
        if (jonasBase.length() <= 0) {
            throw new RuntimeException("JOnAS configuration error: jonas.base is null");
        }

        // JONAS_BASE/conf/fileName
        propFileName = jonasBase + fileSeparator + CONFIG_DIR + fileSeparator + fileName + ".properties";

        try {
            FileOutputStream os = new FileOutputStream(propFileName);
            props.store(os, "This file is generated by JOnAS");
            os.close();
        } catch (FileNotFoundException e) {
            // File propFileName could not be opened
            propFileName = null;
            throw new RuntimeException("Cannot write config file :" + e);
        } catch (IOException e) {
            throw new RuntimeException("Cannot write config file :" + e);
        }
        configFileEnv = (Properties) props.clone();
        allEnv = configFileEnv;
    }


    /**
     * Write xml configuration in file getProperty(JONAS_BASE)/conf/fileName
     * @param fileName name of the configuration file to write
     * @param txt text to write
     * @throws Exception if it can't write to the specified file
     */
    //TODO to remove
    private void writeXmlToFile(final String fileName, final String txt) {

        // Check the JONAS_BASE environment property
        if (jonasBase == null) {
            throw new RuntimeException("JOnAS configuration error: environment property jonas.base not set!");
        }
        jonasBase = jonasBase.trim();
        // JONAS_BASE/conf/fileName
        propFileName = jonasBase + fileSeparator + CONFIG_DIR + fileSeparator + fileName + ".properties";

        try {
            BufferedWriter out = new BufferedWriter(new FileWriter(new File(propFileName)));
            out.write(txt);
            out.flush();
            out.close();
        } catch (FileNotFoundException e) {
            // File propFileName could not be opened
            propFileName = null;
            throw new RuntimeException("Cannot write xml configuration file:" + e);
        } catch (IOException e) {
            throw new RuntimeException("Cannot write xml configuration file:" + e);
        }
    }


    /**
     * Read the content of the specified file. It can be an xml or properties file
     * @param fileName name of the properties configuration file to read
     * @throws Exception if it fails
     */
    private void readFile(final String fileName) {
        // Check the JONAS_BASE environment property
        if (jonasBase == null) {
            throw new RuntimeException("JOnAS configuration error: environment property jonas.base not set!");
        }
        jonasBase = jonasBase.trim();
        if (jonasBase.length() <= 0) {
            throw new RuntimeException("JOnAS configuration error: jonas.base is null");
        }


        if (fileName.equals(ConfigurationConstants.DEFAULT_JONAS_VERSIONS)) {
            readVersionFile();
            return;
        }

        // JONAS_BASE/conf/fileName
        String fileFullPathname = jonasBase + fileSeparator + CONFIG_DIR + fileSeparator + fileName;

        if (fileName.equals(ConfigurationConstants.DEFAULT_JONAS_PREFIX) || fileName.equals(ConfigurationConstants.JONAS_PROPERTIES_PROP)) {
            //first read the jonas.properties bootstrap template (in order to get the work directory
            readPropsFile(fileFullPathname);

            //get the generated jonas.properties file to read
            fileFullPathname = getWorkDirectory() + File.separator + ConfigurationConstants.CONF_GENERATED_DIRECTORY +
                    File.separator + ConfigurationConstants.JONAS_PROPERTIES_PROP;

            //if the jonas properties is already generated, we read it
            if (new File(fileFullPathname).exists()) {
                readPropsFile(fileFullPathname);
            }

        } else {
            if (fileFullPathname.toLowerCase().endsWith(".xml")) {
                readXmlFile(fileFullPathname);
            } else {
                readPropsFile(fileFullPathname);
            }
        }
    }

    private void readVersionFile() {
        String fileFullPathname = jonasRoot + fileSeparator + ConfigurationConstants.DEFAULT_JONAS_VERSIONS;
        try {
            File f = new File(fileFullPathname);
            int length = (int) f.length();
            FileInputStream fis = new FileInputStream(f);
            byte[] buffer = new byte[length];
            fis.read(buffer);
            fis.close();
            versionFileContent = new String(buffer);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("Cannot find file " + fileFullPathname);
        } catch (IOException e) {
            throw new RuntimeException("Cannot read file " + fileFullPathname);
        }
    }

    /**
     * Read initial configuration properties in file getProperty(JONAS_BASE)/conf/fileName
     * These properties may be overridden by system properties, provided on the java command line.
     * @param fileName name of the configuration file to read
     * @throws Exception if it fails
     */
    private void readPropsFile(final String fileName) {

        // Update filename of this JProp
        this.propFileName = fileName;

        if (!fileName.endsWith(".properties")) {
            propFileName += ".properties";
        }

        File f = null;
        try {
            f = new File(propFileName);
            FileInputStream is = new FileInputStream(f);
            configFileEnv.load(is);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("Cannot find properties for " + propFileName);
        } catch (IOException e) {
            throw new RuntimeException("Cannot load properties for " + propFileName);
        }

        allEnv = (Properties) configFileEnv.clone();
        // Overriddes with syst properties
        if (f.getName().equalsIgnoreCase("jonas.properties")) {
            for (Enumeration<Object> e = systEnv.keys(); e.hasMoreElements();) {
                Object key   = e.nextElement();
                String value = ((String) systEnv.get(key)).trim();
                allEnv.put(key, value);
            }

            String serverName;
            // If no system properties is set for the server name and that the
            // property is missing in the jonas.properties file, use the
            // default value
            if (!systEnv.containsKey(ConfigurationConstants.JONAS_NAME_PROP)) {
                if (!configFileEnv.containsKey(ConfigurationConstants.JONAS_NAME_PROP)) {
                    allEnv.put(ConfigurationConstants.JONAS_NAME_PROP, ConfigurationConstants.DEFAULT_JONAS_NAME);
                } else {
                    allEnv.put(ConfigurationConstants.JONAS_NAME_PROP, configFileEnv.getProperty(ConfigurationConstants.JONAS_NAME_PROP));
                }
            }
            serverName = ((String) allEnv.get(ConfigurationConstants.JONAS_NAME_PROP)).trim();

            // If no system properties is set for the domain name and that the
            // property is missing in the jonas.properties file, use the
            // server name as default value
            if (!allEnv.containsKey(ConfigurationConstants.DOMAIN_NAME_PROP) && !systEnv.containsKey(ConfigurationConstants.DOMAIN_NAME_PROP)) {
                if (!configFileEnv.containsKey(ConfigurationConstants.DOMAIN_NAME_PROP)) {
                    allEnv.put(ConfigurationConstants.DOMAIN_NAME_PROP, serverName);
                } else {
                    allEnv.put(ConfigurationConstants.DOMAIN_NAME_PROP, configFileEnv.getProperty(ConfigurationConstants.DOMAIN_NAME_PROP));
                }
            }
        }

        if (allEnv.containsKey(JONAS_MASTER)) {
            if(Boolean.parseBoolean(allEnv.getProperty(JONAS_MASTER))) {
                setMaster(true);
            }
        }

        if (allEnv.containsKey(JONAS_DEVELOPMENT)) {
            if(!Boolean.parseBoolean(allEnv.getProperty(JONAS_DEVELOPMENT))) {
                setDevelopment(false);
            }
        }
    }

    /**
     * Read initial configuration in file getProperty(JONAS_BASE)/conf/fileName
     * @param fileName name of the xml configuration file to read
     */
    private void readXmlFile(final String fileName) {

        // Update filename of this JProp
        this.propFileName = fileName;

        try {
            File f = new File(propFileName);
            int length = (int) f.length();
            FileInputStream fis = new FileInputStream(f);
            byte[] buffer = new byte[length];
            fis.read(buffer);
            fis.close();
            configFileXml = new String(buffer);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("Cannot find file " + propFileName);
        } catch (IOException e) {
            throw new RuntimeException("Cannot read file " + propFileName);
        }
    }

    /**
     * Static method which return the jonas.root property
     * @return  the jonas.root property
     */
    public static String getJonasRoot() {
        return jonasRoot;
    }

    /**
     * Static method which return the jonas.base property
     * @return  the jonas.base property
     */
    public static String getJonasBase() {
        return jonasBase;
    }

    /**
     * Static method which returns the configuration directory in jonas.base
     * @return The configuration directory in jonas.base
     */
    public static String getConfDir() {
        return jonasBase + File.separator + CONFIG_DIR;
    }

    /**
     * Static method which returns the repositories directory in jonas.root
     * @return The repositories directory in jonas.root
     */
    public static String getRepositoriesRootDir() {
        return jonasRoot + File.separator + REPOSITORIES_DIR;
    }

    /**
     * Static method which returns the repositories directory in jonas.base
     * @return The repositories directory in jonas.base
     */
    public static String getRepositoriesBaseDir() {
        return jonasBase + File.separator + REPOSITORIES_DIR;
    }

    /**
     * Returns properties filename
     *
     * @return JOnAS properties filename
     */
    public String getPropFile() {
        return propFileName;
    }

    /**
     * Returns JOnAS environment as configured with configuration file properties content and
     * system properties.
     * @return JOnAS properties
     */
    public Properties getEnv() {
        return allEnv;
    }

    /**
     * Returns JOnAS environment as configured with files properties only.
     *
     * @return JOnAS properties
     */
    public Properties getConfigFileEnv() {
        return configFileEnv;
    }

    public String getPropFileName() {
        return propFileName;
    }
    /**
     * Returns xml content of the resource file.
     *
     * @return xml content of the resource file
     */
    public String getConfigFileXml() {
        return configFileXml;
    }

    public String getVersionFile() {
        return versionFileContent;
    }

    /**
     * Returns the value of the related property. With default values.
     * @param key the search key
     * @param defaultVal if the key is not found return this default value
     * @return property value
     */
    public String getValue(final String key, final String defaultVal) {
        String retProperty = allEnv.getProperty(key, defaultVal);
        return retProperty.trim();
    }

    /**
     * Returns the value of the related property.
     * The method returns null if the property is not found.
     * @param key the wanted key
     * @return property value, null if not exist
     */
    public String getValue(final String key) {

        String retProperty = allEnv.getProperty(key);
        if (retProperty != null) {
            retProperty = retProperty.trim();
        }
        return retProperty;
    }

    /**
     * Returns the value of the related property as boolean.
     * @param key the wanted key
     * @param def default run if not found
     * @return property value, true or false.
     */
    public boolean getValueAsBoolean(final String key, final boolean def) {
        boolean ret = def;
        String value = this.getValue(key);
        if (value != null && value.equalsIgnoreCase("true")) {
            ret = true;
        }
        return ret;
    }

    /**
     * Returns the value of the related property as String [].
     * The method returns null if the property is not found.
     * @param key the wanted key
     * @return property value, null if not exist
     */
    public String[] getValueAsArray(final String key) {

        String [] res = null;
        String value = this.getValue(key);
        if (value != null) {
            StringTokenizer st = new StringTokenizer(value, ",");
            res = new String [st.countTokens()];
            int i = 0;
            while (st.hasMoreTokens()) {
                res[i++] = st.nextToken().trim();
            }
        }
        return res;
    }

    /**
     * String representation of the object for trace purpose
     * @return String representation of this object
     */
    @Override
    public String toString() {
        String s = new String();
        for (Enumeration<Object> e = this.configFileEnv.keys(); e.hasMoreElements();) {
            Object key   = e.nextElement();
            Object value = this.configFileEnv.get(key);
            s = s.concat("   " + key + " = " + value + "\n");
        }
        if (s.length() > 0) {
            // take of the last '\n'
            s = s.substring(0, s.length() - 1);
        }
        return s;
    }

    /**
     * Bind all the properties found in file properties in a naming context
     * the naming context must be allocated by the caller
     * @param ctx given context for bindings properties
     * @throws Exception if it fails
     */
    public void env2Ctx(final Context ctx) throws Exception {
        Enumeration e = configFileEnv.propertyNames();
        String key = null;
        while (e.hasMoreElements()) {
            key = (String) e.nextElement();
            ctx.bind(key, configFileEnv.getProperty(key, ""));
        }
    }

    /**
     * Displays the JOnAS properties values, as they are set by the
     *  different property files.
     * @param args the arguments for launching this program
     */
    public static void main(final String args[]) {

        JProp jonasProperties = null;
        try {
            jonasProperties = JProp.getInstance();
        } catch (Exception e) {
            System.err.println(e);
            System.exit(2);
        }
        for (Enumeration<Object> e = jonasProperties.configFileEnv.keys(); e.hasMoreElements();) {
            Object key   = e.nextElement();
            Object value = jonasProperties.configFileEnv.get(key);
            System.out.println(key.toString() + "=" + value.toString());
        }
    }

    // -----------------------------------------------------------------------------
    // Implementation of ServerProperties
    // -----------------------------------------------------------------------------

    /**
     * @return The Domain Name
     */
    public String getDomainName() {
        return getValue(ConfigurationConstants.DOMAIN_NAME_PROP, null);
    }

    /**
     * @return the Server Name
     */
    public String getServerName() {
        return getValue(ConfigurationConstants.JONAS_NAME_PROP, null);
    }

    /**
     * @return the VERSIONS file content as a String.
     */
    public String getVersionsFile() {
        JProp jp = JProp.getInstance(ConfigurationConstants.DEFAULT_JONAS_VERSIONS);
        return jp.versionFileContent;
    }

    public boolean isMaster() {
        return master;
    }

    public void setMaster(final boolean master) {
        this.master = master;
    }

    public boolean isDevelopment() {
        return development;
    }

    public void setDevelopment(final boolean development) {
        this.development = development;
    }

    /**
     * @return work directory
     */
    public String getWorkDirectory() {
        String workDirProperty = getValue(ConfigurationConstants.WORK_DIRECTORY_PROP,
                ConfigurationConstants.DEFAULT_WORK_DIRECTORY);

        // If absolute, no JONAS_BASE prefix
        File f = new File(workDirProperty);
        if (f.isAbsolute()) {
            return workDirProperty;
        }
        // Else, prefix it with JONAS_BASE
        return jonasBase + File.separator + workDirProperty;
    }
}
