/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2007 Bull S.A.S.
 * 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.rar.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 javax.naming.Context;
import javax.naming.NamingException;

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.rar.ConnectorDTDs;
import org.ow2.jonas.deployment.rar.ConnectorSchemas;
import org.ow2.jonas.deployment.rar.JonasConnectorDTDs;
import org.ow2.jonas.deployment.rar.JonasConnectorSchemas;
import org.ow2.jonas.deployment.rar.RarDeploymentDesc;
import org.ow2.jonas.deployment.rar.RarDeploymentDescException;
import org.ow2.jonas.deployment.rar.rules.ConnectorRuleSet;
import org.ow2.jonas.deployment.rar.rules.JonasConnectorRuleSet;
import org.ow2.jonas.deployment.rar.xml.Connector;
import org.ow2.jonas.deployment.rar.xml.JonasConnector;
import org.ow2.jonas.lib.util.Log;




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

/**
 * Manage Rar deployment descriptors
 * @author Guillaume Sauthier
 */
public class RarDeploymentDescManager extends AbsDeploymentDescManager {

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

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

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

    /**
     * Digester use to parse ra.xml
     */
    private static JDigester connectorDigester = null;

    /**
     * Digester use to parse jonas-ra.xml
     */
    private static JDigester jonasConnectorDigester = null;

    /**
     * Rules to parse the ra.xml
     */
    private static ConnectorRuleSet connectorRuleSet = new ConnectorRuleSet();

    /**
     * Rules to parse the jonas-ra.xml
     */
    private static JonasConnectorRuleSet jonasConnectorRuleSet = new JonasConnectorRuleSet();

    /**
     * logger TODO Get a Resource Logger
     */
    private static Logger logger = Log.getLogger(Log.JONAS_DBM_PREFIX);

    /**
     * Private empty constructor for Utility Class
     */
    private RarDeploymentDescManager() {
    }

    /**
     * Get an instance of an RAR deployment descriptor by parsing the ra.xml and
     * jonas-ra.xml deployment descriptors.
     * @param ctx the context which contains the configuration in order to load
     *        the deployment descriptors. There are 6 possible parameters : -
     *        rarFileName is the path of the RAR file (required param). -
     *        classLoader is the classloader (required param) - altDD is the
     *        optional deployment descriptor (optional param).
     * @return an RAR deployment descriptor by parsing the ra.xml(or altdd) &
     *         jonas-ra.xml deployment descriptors.
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    public static RarDeploymentDesc getInstance(Context ctx) throws DeploymentDescException {

        // init xml contents values;
        String xmlContent = "";
        String jonasXmlContent = "";
        boolean altDD = false;

       String rarFileName = null;
        ClassLoader classLoader = null;
        try {
            rarFileName = (String) ctx.lookup("rarFileName");
            classLoader = (ClassLoader) ctx.lookup("classloader");
        } catch (NamingException e) {
            String err = "Error while getting parameter from context param ";
            throw new RarDeploymentDescException(err, e);
        }

        // optional parameter
        String raDeployDesc;
        try {
            raDeployDesc = (String) ctx.lookup("altDD");
            altDD = true;
        } catch (NamingException e) {
            // no alt DD
            raDeployDesc = "";
        }

        //rar file
        JarFile rarFile = null;

        //Input Stream
        InputStream raInputStream = null;
        InputStream jonasRaInputStream = null;

        //ZipEntry
        ZipEntry raZipEntry = null;
        ZipEntry jonasRaZipEntry = null;

        Connector connector = null;
        JonasConnector jonasConnector = null;

        //Build the file
        File fRar = new File(rarFileName);

        //Check if the file exists.
        if (!(fRar.exists())) {
            throw new RarDeploymentDescException("The file '" + rarFileName + "' was not found.");
        }

        // load deployment descriptor data (META-INF/ra.xml)
        boolean setupRa = true;
        boolean setupJonasRa = true;
        try {
            if (!altDD) {

                // If rar is a directory, there is no jar, just read the file from
                // the directory
                if (fRar.isDirectory()) {
                    File rarXmlF = new File(rarFileName, RA_FILE_NAME);
                    if (!rarXmlF.exists()) {
                        connector = null;
                        setupRa = false;
                    } else {
                        raInputStream = new FileInputStream(rarXmlF);
                        xmlContent = xmlContent(raInputStream);
                        raInputStream = new FileInputStream(rarXmlF);
                    }
                } else {
                    rarFile = new JarFile(rarFileName);

                    //Check the ra entry
                    raZipEntry = rarFile.getEntry(RA_FILE_NAME);
                    if (raZipEntry == null) {
                        connector = null;
                        setupRa = false;
                    } else {
                        //Get the stream
                        raInputStream = rarFile.getInputStream(raZipEntry);
                        xmlContent = xmlContent(raInputStream);
                        raInputStream = rarFile.getInputStream(raZipEntry);
                    }
                }
            } else {
                raInputStream = new FileInputStream(raDeployDesc);
                xmlContent = xmlContent(raInputStream);
                raInputStream = new FileInputStream(raDeployDesc);
            }

            if (fRar.isDirectory()) {
                //lookup a META-INF/jonas-ra.xml file
                File rarJXmlF = new File(rarFileName, JONAS_RA_FILE_NAME);
                if (rarJXmlF.exists()) {
                    jonasRaInputStream = new FileInputStream(rarJXmlF);
                    jonasXmlContent = xmlContent(jonasRaInputStream);
                    jonasRaInputStream = new FileInputStream(rarJXmlF);
                }
            } else {
                //Check the jonas-ra entry
                rarFile = new JarFile(rarFileName);
                jonasRaZipEntry = rarFile.getEntry(JONAS_RA_FILE_NAME);
                if (jonasRaZipEntry == null) {
                    jonasConnector = null;
                    setupJonasRa = false;
                } else {
                    //Get the stream
                    jonasRaInputStream = rarFile.getInputStream(jonasRaZipEntry);
                    jonasXmlContent = xmlContent(jonasRaInputStream);
                    jonasRaInputStream = rarFile.getInputStream(jonasRaZipEntry);
                }
            }
        } catch (Exception e) {
            if (rarFile != null) {
                try {
                    rarFile.close();
                } catch (IOException ioe) {
                    // Can't close the file
                    logger.log(BasicLevel.WARN, "Can't close '" + rarFileName + "'");
                }
            }
            throw new RarDeploymentDescException("Cannot read the XML deployment descriptors of the rar file '"
                    + rarFileName + "'.", e);
        }

        if (setupRa) {
            connector = loadConnector(new InputStreamReader(raInputStream), raDeployDesc);
            try {
                raInputStream.close();
            } catch (IOException e) {
                // can't close
                logger.log(BasicLevel.WARN, "Can't close META-INF/ra.xml of '" + rarFileName + "'");
            }
        }

        if (setupJonasRa) {
            jonasConnector = loadJonasConnector(new InputStreamReader(jonasRaInputStream), JONAS_RA_FILE_NAME);
            try {
                jonasRaInputStream.close();
            } catch (IOException e) {
                // can't close
                logger.log(BasicLevel.WARN, "Can't close META-INF/jonas-ra.xml of '" + rarFileName + "'");
            }
        }

        // instantiate deployment descriptor
        RarDeploymentDesc rdd = new RarDeploymentDesc(classLoader, connector, jonasConnector);
        rdd.setXmlContent(xmlContent);
        rdd.setJOnASXmlContent(jonasXmlContent);
        return rdd;
    }

    /**
     * Load the ra.xml file.
     * @param reader the Reader of the XML file.
     * @param fileName the name of the file (ra.xml).
     * @throws DeploymentDescException if the deployment descriptor is
     *         corrupted.
     * @return a connector object.
     */
    public static Connector loadConnector(Reader reader, String fileName) throws DeploymentDescException {

        Connector connector = new Connector();

        // Create if null
        if (connectorDigester == null) {
            connectorDigester = new JDigester(connectorRuleSet, parsingWithValidation, true, new ConnectorDTDs(),
                    new ConnectorSchemas());
        }

        try {
            connectorDigester.parse(reader, fileName, connector);
        } catch (DeploymentDescException e) {
            throw e;
        } finally {
            connectorDigester.push(null);
        }
        return connector;
    }

    /**
     * Load the jonas-ra.xml file.
     * @param reader the Reader of the XML file.
     * @param fileName the name of the file (jonas-ra.xml).
     * @throws DeploymentDescException if the deployment descriptor is
     *         corrupted.
     * @return a JonasConnector object.
     */
    public static JonasConnector loadJonasConnector(Reader reader, String fileName) throws DeploymentDescException {

        JonasConnector jonasConnector = new JonasConnector();

        // Create if null
        if (jonasConnectorDigester == null) {
            jonasConnectorDigester = new JDigester(jonasConnectorRuleSet, parsingWithValidation, true,
                    new JonasConnectorDTDs(), new JonasConnectorSchemas());
        }

        try {
            jonasConnectorDigester.parse(reader, fileName, jonasConnector);
        } catch (DeploymentDescException e) {
            throw e;
        } finally {
            jonasConnectorDigester.push(null);
        }
        return jonasConnector;
    }

    /**
     * Controls whether the parser is reporting all validity errors.
     * @return if true, all external entities will be read.
     */
    public static boolean getParsingWithValidation() {
        return RarDeploymentDescManager.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) {
        RarDeploymentDescManager.parsingWithValidation = validation;
    }

}