/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 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
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.deployment.domain.lib;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import org.ow2.jonas.deployment.common.DeploymentDescException;
import org.ow2.jonas.deployment.common.digester.JDigester;
import org.ow2.jonas.deployment.common.lib.AbsDeploymentDescManager;
import org.ow2.jonas.deployment.domain.DomainMap;
import org.ow2.jonas.deployment.domain.DomainMapException;
import org.ow2.jonas.deployment.domain.DomainSchemas;
import org.ow2.jonas.deployment.domain.rules.DomainRuleSet;
import org.ow2.jonas.deployment.domain.xml.Domain;
import org.ow2.jonas.lib.execution.ExecutionResult;
import org.ow2.jonas.lib.execution.IExecution;
import org.ow2.jonas.lib.execution.RunnableHelper;
import org.ow2.jonas.lib.util.Log;

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

/**
 * This class extends the AbsDeploymentDescriptor class of JOnAS It provides a
 * description of the domain map
 * @author Adriana Danes
 * @author S. Ali Tokmen
 */
public class DomainMapManager extends AbsDeploymentDescManager {

    /**
     * Path of the domain.xml configuration file
     */
    public static final String DOMAIN_FILE_NAME = "domain.xml";

    /**
     * Digester used to parse domain.xml
     */
    private static JDigester domainDigester = null;

    /**
     * Rules to parse the application.xml
     */
    private static DomainRuleSet domainRuleSet = new DomainRuleSet();

    /**
     * logger
     */
    private static Logger logger = Log.getLogger("org.ow2.jonas.deployment.domain");

    /**
     * Flag for parser validation
     */
    private static boolean parsingWithValidation = true;
    /**
     * Private Empty constructor for utility class
     */
    private DomainMapManager() {
    }

    /**
     * Get an instance of a DomainMap by parsing the domain.xml configuration file.
     * @param domainFileName used when specific domain description file name has to be used
     * @param classLoaderForCls the classloader for the classes.
     * @return a DomainMap instance by parsing the domain.xml file
     * @throws DomainMapException if the domain.xml file is
     *         corrupted.
     */
    public static DomainMap getDomainMap(String domainFileName, ClassLoader classLoaderForCls)
    throws DomainMapException {

        //Input Stream
        InputStream domainInputStream = null;
        String fileName = null;
        if (domainFileName == null) {
            // domain.xml in JONAS_BASE/conf
            fileName = System.getProperty("jonas.base") + File.separator
            + "conf" + File.separator
            + DOMAIN_FILE_NAME;
        } else {
            fileName = domainFileName;
        }
        // load domain.xml
        File domainFile = new File(fileName);
        if (!domainFile.exists()) {
            domainInputStream = classLoaderForCls.getResourceAsStream(DOMAIN_FILE_NAME);
        } else {
            try {
                domainInputStream = new FileInputStream(domainFile);
            } catch (Exception e) {
                throw new DomainMapException("Cannot read the " + DOMAIN_FILE_NAME, e);
            }
        }

        Domain domain = loadDomain(new InputStreamReader(domainInputStream), DOMAIN_FILE_NAME);
        try {
            domainInputStream.close();
        } catch (IOException e) {
            // Can't close the file
            logger.log(BasicLevel.WARN, "Cannot close InputStream for " + DOMAIN_FILE_NAME);
        }

        // instantiate the domain map
        DomainMap domainMap = new DomainMap(domain);
        return domainMap;
    }

    /**
     * Load the domain.xml file.
     * @param reader the Reader of the XML file.
     * @param fileName the name of the file (domain.xml).
     * @throws DomainMapException if the file is
     *         corrupted.
     * @return a Domain object.
     */
    public static Domain loadDomain(Reader reader, String fileName) throws DomainMapException {

        Domain domain = new Domain();
        // Create if domainDigester is null
        if (domainDigester == null) {
            try {
                // Create and initialize the digester

                domainDigester = new JDigester(domainRuleSet, getParsingWithValidation(), true, null,
                        new DomainSchemas());
            } catch (DeploymentDescException e) {
                throw new DomainMapException(e);
            }
        }

        // Specify the TC ClassLoader to use
        final Reader myReader = reader;
        final String myFilename = fileName;
        final Domain myDomain = domain;
        IExecution<Void> exec = new IExecution<Void>() {
            public Void execute() throws Exception {
                try {
                    domainDigester.parse(myReader, myFilename, myDomain);
                } catch (DeploymentDescException e) {
                    throw new DomainMapException(e);
                } finally {
                    domainDigester.push(null);
                }
                return null;
            }
        };

        // Execute
        ExecutionResult<Void> result = RunnableHelper.execute(Domain.class.getClassLoader(), exec);

        // Throw an Exception if needed
        if (result.hasException()) {
            throw (DomainMapException) result.getException();
        }

        return domain;
    }

    /**
     * Save the domain.xml file.
     * @param domain domain representation.
     * @param fileName the name of the file (domain.xml).
     * @throws DomainMapException  Problem writing into file
     */
    public static void saveDomain(Domain domain, String fileName) throws DomainMapException {
        if (fileName == null) {
            // domain.xml in JONAS_BASE/conf
            fileName = System.getProperty("jonas.base") + File.separator
            + "conf" + File.separator
            + DOMAIN_FILE_NAME;
        }

        try {
            BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
            out.write(domain.toXML());
            out.close();
        } catch(Exception e) {
            throw new DomainMapException("Failed writing into domain.xml", e);
        }
    }

    /**
     * Controls whether the parser is reporting all validity errors.
     * @return if true, all external entities will be read.
     */
    public static boolean getParsingWithValidation() {
        return parsingWithValidation;
    }

    /**
     * Controls whether the parser is reporting all validity errors.
     * @param validation if true, all external entities will be read.
     */
    public static void setParsingWithValidation(boolean validation) {
        DomainMapManager.parsingWithValidation = validation;
    }
}