/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 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
 *
 * Initial developer(s): Florent BENOIT & Ludovic BERT
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ear.deployment.lib;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import org.objectweb.jonas_ear.deployment.api.EarDTDs;
import org.objectweb.jonas_ear.deployment.api.EarDeploymentDesc;
import org.objectweb.jonas_ear.deployment.api.EarDeploymentDescException;
import org.objectweb.jonas_ear.deployment.api.EarSchemas;
import org.objectweb.jonas_ear.deployment.api.JonasEarSchemas;
import org.objectweb.jonas_ear.deployment.rules.ApplicationRuleSet;
import org.objectweb.jonas_ear.deployment.rules.JonasApplicationRuleSet;
import org.objectweb.jonas_ear.deployment.xml.Application;
import org.objectweb.jonas_ear.deployment.xml.JonasApplication;

import org.objectweb.jonas_lib.deployment.api.DeploymentDescException;
import org.objectweb.jonas_lib.deployment.digester.JDigester;
import org.objectweb.jonas_lib.deployment.lib.AbsDeploymentDescManager;

import org.objectweb.jonas.common.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 specific EAR desployment descriptor
 * @author Florent Benoit
 * @author Ludovic Bert
 * @author Helene Joanin
 */
public class EarDeploymentDescManager extends AbsDeploymentDescManager {

    /**
     * Path of the application.xml deploymnet descriptor file
     */
    public static final String APPLICATION_FILE_NAME = "META-INF/application.xml";

    /**
     * Path of the jonas-application.xml deploymnet descriptor file
     */
    public static final String JONAS_APPLICATION_FILE_NAME = "META-INF/jonas-application.xml";


    /**
     * Digester used to parse application.xml
     */
    private static JDigester earDigester = null;

    /**
     * Digester used to parse jonas-application.xml
     */
    private static JDigester jonasEarDigester = null;

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

    /**
     * Rules to parse the jonas-application.xml
     */
    private static JonasApplicationRuleSet jonasApplicationRuleSet = new JonasApplicationRuleSet();

    /**
     * logger
     */
    private static Logger logger = Log.getLogger(Log.JONAS_EAR_PREFIX);

    /**
     * Flag for parser validation
     */
    private static boolean parsingWithValidation = true;

    /**
     * Private Empty constructor for utility class
     */
    private EarDeploymentDescManager() {
    }

    /**
     * Get an instance of an EAR deployment descriptor by parsing the
     * application.xml deployment descriptor.
     * @param earFileName the fileName of the war file for the deployment
     *        descriptors.
     * @param classLoaderForCls the classloader for the classes.
     * @return an EAR deployment descriptor by parsing the application.xml
     *         deployment descriptor.
     * @throws EarDeploymentDescException if the deployment descriptor is
     *         corrupted.
     */
    public static EarDeploymentDesc getDeploymentDesc(String earFileName, ClassLoader classLoaderForCls)
            throws EarDeploymentDescException {

        //ear file
        JarFile earFile = null;

        //Input Stream
        InputStream appInputStream = null;
        InputStream jonasApplicationInputStream = null;

        //ZipEntry
        ZipEntry appZipEntry = null;
        ZipEntry jonasApplicationZipEntry = null;

        Application application = null;
        JonasApplication jonasApplication = null;

        String xmlContent = "";
        String jonasXmlContent = "";

        //Check if the file exists.
        File earF = new File(earFileName);
        if (!earF.exists()) {
            throw new EarDeploymentDescException("The file '" + earFileName + "' was not found.");
        }

        // load deployment descriptor data (META-INF/application.xml)
        try {
            // Is it a directory ?
            if (earF.isDirectory()) {
                //lookup a META-INF/application.xml file
                File earXmlF = new File(earFileName, APPLICATION_FILE_NAME);
                if (!earXmlF.exists()) {
                    String err = "You have choose to deploy an ear directory but there is no " + APPLICATION_FILE_NAME
                            + " file in the directory " + earFileName;
                    throw new EarDeploymentDescException(err);
                }
                appInputStream = new FileInputStream(earXmlF);
                xmlContent = xmlContent(appInputStream);
                // necessary to have a not empty InputStream !!!
                appInputStream = new FileInputStream(earXmlF);

                //lookup a META-INF/jonas-application.xml file
                File jonasEarXmlF = new File(earFileName, JONAS_APPLICATION_FILE_NAME);
                // file is optional
                if (jonasEarXmlF.exists()) {
                    jonasApplicationInputStream = new FileInputStream(jonasEarXmlF);
                    jonasXmlContent = xmlContent(jonasApplicationInputStream);
                    // necessary to have a not empty InputStream !!!
                    jonasApplicationInputStream = new FileInputStream(jonasEarXmlF);
                }
            } else {
                // it is an .ear file
                earFile = new JarFile(earFileName);

                //Check the application entry
                appZipEntry = earFile.getEntry(APPLICATION_FILE_NAME);
                if (appZipEntry == null) {
                    throw new EarDeploymentDescException("The entry '" + APPLICATION_FILE_NAME
                            + "' was not found in the file '" + earFileName + "'.");
                }

                //Get the stream
                appInputStream = earFile.getInputStream(appZipEntry);
                xmlContent = xmlContent(appInputStream);
                // necessary to have a not empty InputStream !!!
                appInputStream = earFile.getInputStream(appZipEntry);

                // Check the jonas-application entry
                jonasApplicationZipEntry = earFile.getEntry(JONAS_APPLICATION_FILE_NAME);
                if (jonasApplicationZipEntry != null) {
                    //Get the stream
                    jonasApplicationInputStream = earFile.getInputStream(jonasApplicationZipEntry);
                    jonasXmlContent = xmlContent(jonasApplicationInputStream);
                    // necessary to have a not empty InputStream !!!
                    jonasApplicationInputStream = earFile.getInputStream(jonasApplicationZipEntry);
                }
            }
        } catch (Exception e) {
            if (earFile != null) {
                try {
                    earFile.close();
                } catch (IOException ioe) {
                    //We can't close the file
                    logger.log(BasicLevel.WARN, "Cannot close InputStream for '" + earFileName + "'");
                }
            }
            throw new EarDeploymentDescException("Cannot read the XML deployment descriptors of the ear file '"
                    + earFileName + "'.", e);
        }

        application = loadApplication(new InputStreamReader(appInputStream), APPLICATION_FILE_NAME);
        try {
            appInputStream.close();
        } catch (IOException e) {
            // Can't close the file
            logger.log(BasicLevel.WARN, "Cannot close InputStream for META-INF/application.xml in '" + earFileName
                    + "'");
        }

        // load jonas-client deployment descriptor data
        // (META-INF/jonas-client.xml)
        if (jonasApplicationInputStream != null) {
            jonasApplication = loadJonasApplication(new InputStreamReader(jonasApplicationInputStream), JONAS_APPLICATION_FILE_NAME);
            try {
                jonasApplicationInputStream.close();
            } catch (IOException e) {
                //We can't close the file
                logger.log(BasicLevel.WARN, "Cannot close InputStream for '" + earFileName + "'");
            }
        } else {
            jonasApplication = new JonasApplication();
        }


        // instantiate deployment descriptor
        EarDeploymentDesc earDD = new EarDeploymentDesc(classLoaderForCls, application, jonasApplication);
        earDD.setXmlContent(xmlContent);
        earDD.setJonasXmlContent(jonasXmlContent);
        return earDD;
    }

    /**
     * Load the application.xml file.
     * @param reader the Reader of the XML file.
     * @param fileName the name of the file (application.xml).
     * @throws EarDeploymentDescException if the deployment descriptor is
     *         corrupted.
     * @return an application object.
     */
    public static Application loadApplication(Reader reader, String fileName) throws EarDeploymentDescException {

        Application app = new Application();
        // Create if earDigester is null
        if (earDigester == null) {
            try {
                // Create and initialize the digester
                earDigester = new JDigester(appRuleSet, getParsingWithValidation(), true, new EarDTDs(),
                        new EarSchemas());
            } catch (DeploymentDescException e) {
                throw new EarDeploymentDescException(e);
            }
        }

        try {
            earDigester.parse(reader, fileName, app);
        } catch (DeploymentDescException e) {
            throw new EarDeploymentDescException(e);
        } finally {
            earDigester.push(null);
        }

        return app;
    }

    /**
     * Load the jonas-application.xml file.
     * @param reader the stream of the XML file.
     * @param fileName the name of the file (jonas-application.xml).
     * @return a structure containing the result of the jonas-application.xml
     *         parsing.
     * @throws EarDeploymentDescException if the deployment descriptor is
     *         corrupted.
     */
    public static JonasApplication loadJonasApplication(Reader reader, String fileName) throws EarDeploymentDescException {

        JonasApplication ja = new JonasApplication();

        // Create if null
        if (jonasEarDigester == null) {
            try {
            jonasEarDigester = new JDigester(jonasApplicationRuleSet, getParsingWithValidation(), true,
                    null, new JonasEarSchemas());
            } catch (DeploymentDescException e) {
                throw new EarDeploymentDescException(e);
            }
        }

        try {
            jonasEarDigester.parse(reader, fileName, ja);

        } catch (DeploymentDescException e) {
            throw new EarDeploymentDescException(e);
        } finally {
            jonasEarDigester.push(null);
        }
        return ja;
    }

    /**
     * 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) {
        EarDeploymentDescManager.parsingWithValidation = validation;
    }
}