/**
 * 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
 *
 * Initial developer(s): Ludovic BERT & Florent BENOIT
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.deployment.web.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.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Hashtable;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import javax.annotation.Resource.AuthenticationType;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.deployment.api.IServiceRefDesc;
import org.ow2.jonas.deployment.common.DeploymentDescException;
import org.ow2.jonas.deployment.common.EjbLocalRefDesc;
import org.ow2.jonas.deployment.common.EjbRefDesc;
import org.ow2.jonas.deployment.common.MessageDestinationRefDesc;
import org.ow2.jonas.deployment.common.digester.JDigester;
import org.ow2.jonas.deployment.common.lib.AbsDeploymentDescManager;
import org.ow2.jonas.deployment.common.xml.JonasMessageDestination;
import org.ow2.jonas.deployment.common.xml.JonasResource;
import org.ow2.jonas.deployment.common.xml.ResourceRef;
import org.ow2.jonas.deployment.ejb.lib.EjbDeploymentDescManager;
import org.ow2.jonas.deployment.web.JonasWebAppDTDs;
import org.ow2.jonas.deployment.web.JonasWebAppSchemas;
import org.ow2.jonas.deployment.web.WebAppDTDs;
import org.ow2.jonas.deployment.web.WebAppSchemas;
import org.ow2.jonas.deployment.web.WebContainerDeploymentDesc;
import org.ow2.jonas.deployment.web.WebContainerDeploymentDescException;
import org.ow2.jonas.deployment.web.rules.JonasWebAppRuleSet;
import org.ow2.jonas.deployment.web.rules.WebAppRuleSet;
import org.ow2.jonas.deployment.web.xml.JonasWebApp;
import org.ow2.jonas.deployment.web.xml.WebApp;
import org.ow2.jonas.deployment.ws.PortComponentDesc;
import org.ow2.jonas.deployment.ws.PortComponentRefDesc;
import org.ow2.jonas.deployment.ws.WSDeploymentDescException;
import org.ow2.jonas.deployment.ws.lib.WSDeploymentDescManager;
import org.ow2.jonas.lib.util.Log;
import org.ow2.util.archive.api.IArchive;
import org.ow2.util.archive.impl.ArchiveManager;
import org.ow2.util.ee.deploy.api.deployable.WARDeployable;
import org.ow2.util.ee.deploy.impl.helper.DeployableHelper;
import org.ow2.util.ee.metadata.common.api.struct.IJAnnotationResource;
import org.ow2.util.ee.metadata.war.api.IWarDeployableMetadata;
import org.ow2.util.ee.metadata.war.impl.enc.ENCBindingBuilder;
import org.ow2.util.ee.metadata.war.impl.enc.ENCBindingException;
import org.ow2.util.ee.metadata.war.impl.enc.EZBENCBindingHolder;
import org.ow2.util.ee.metadata.war.impl.enc.IBinding;
import org.ow2.util.url.URLUtils;

/**
 * This class provide a way for managing the WebContainerDeploymentDesc. Note
 * that there is an intance of the WebDeploymentDescManager on each JOnAS
 * server.
 * @author Ludovic Bert
 * @author Florent Benoit
 */
public class WebDeploymentDescManager extends AbsDeploymentDescManager {

    /**
     * The path to the web.xml file.
     */
    public static final String WEB_FILE_NAME = "WEB-INF/web.xml";

    /**
     * The path to the jonas-web.xml file.
     */
    public static final String JONAS_WEB_FILE_NAME = "WEB-INF/jonas-web.xml";

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

    /**
     * Digester use to parse web.xml.
     */
    private static JDigester webAppDigester = null;

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

    /**
     * Rules to parse the web.xml.
     */
    private static WebAppRuleSet webAppRuleSet = new WebAppRuleSet();

    /**
     * Rules to parse the jonas-web.xml.
     */
    private static JonasWebAppRuleSet jonasWebAppRuleSet = new JonasWebAppRuleSet();

    /**
     * The unique instance of the WebDeploymentDescManager.
     */
    private static WebDeploymentDescManager unique;

    /**
     * Reference on the EjbDeploymentDescManager.
     */
    private EjbDeploymentDescManager ejbDDManager = null;

    /**
     * Reference on the WSDeploymentDescManager.
     */
    private WSDeploymentDescManager wsDDManager = null;

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

    /**
     * The cache used when static getDeploymentDesc are called (WsGen case).
     */
    private static Hashtable<String, WebContainerDeploymentDesc> staticCache =
        new Hashtable<String, WebContainerDeploymentDesc>();

    /**
     * Contructs a unique new WebDeploymentDescManager.
     */
    private WebDeploymentDescManager() {
        ejbDDManager = EjbDeploymentDescManager.getInstance();
        earCLAltDDBindings = new Hashtable<ClassLoader, Hashtable<URL, URL>>();
    }

    /**
     * Associate a ear classLoader to an hashtable which contains Association
     * between Urls of wars and their optional alt-dd.
     */
    private Hashtable<ClassLoader, Hashtable<URL, URL>> earCLAltDDBindings = null;

    /**
     * Get an instance of the WebDeploymentDescManager.
     * @return the instance of the WebDeploymentDescManager.
     */
    public static WebDeploymentDescManager getInstance() {
        if (unique == null) {
            unique = new WebDeploymentDescManager();
        }
        return unique;
    }

    /**
     * Get the specified web deployment descriptor.
     * @param unpackedWarURL URL where the WAR has been unpacked. Deployment
     *        descriptors will be loaded from that URL.
     * @param loaderForCls classloader used to load web classes.
     * @param earLoader the ear classloader.
     * @return WebContainerDeploymentDesc the web deployment descriptor.
     * @throws DeploymentDescException when WebContainerDeploymentDesc cannot be
     *         created with the given files.
     */
    public WebContainerDeploymentDesc getDeploymentDesc(final URL unpackedWarURL, final ClassLoader loaderForCls,
            final ClassLoader earLoader) throws DeploymentDescException {
        return getDeploymentDesc(unpackedWarURL, null, loaderForCls, earLoader);
    }

    /**
     * Get the specified web deployment descriptor.
     * @param unpackedWarURL URL where the WAR has been unpacked. Deployment
     *        descriptors will be loaded from that URL.
     * @param originalWarURL Original URL of WAR file, only used for EARs in
     *        order to find the EJB-jars.
     * @param loaderForCls classloader used to load web classes.
     * @param earLoader the ear classloader.
     * @return WebContainerDeploymentDesc the web deployment descriptor.
     * @throws DeploymentDescException when WebContainerDeploymentDesc cannot be
     *         created with the given files.
     */
    public WebContainerDeploymentDesc getDeploymentDesc(final URL unpackedWarURL, final URL originalWarURL,
            final ClassLoader loaderForCls, final ClassLoader earLoader) throws DeploymentDescException {
        // load an instance of the WebService Manager
        if (wsDDManager == null) {
            wsDDManager = WSDeploymentDescManager.getInstance();
        }

        File unpackedWarFile = URLUtils.urlToFile(unpackedWarURL);

        // Check if the war exists ...
        if (!unpackedWarFile.exists()) {
            throw new WebContainerDeploymentDescException("Cannot get the deployment descriptor for '" + unpackedWarFile
                    + "'. The file doesn't exist.");
        }

        // url used to load an alternate DDesc in the EAR case
        URL altDDUrl = null;

        // check if it's an Ear case or not
        Hashtable<URL, URL> urlAltddBindings = null;
        if (earLoader != null) {
            // Mapping ?
            urlAltddBindings = earCLAltDDBindings.get(earLoader);
            if (urlAltddBindings == null) {
                // If there is no mapping, the setAltDD function was badly
                // called
                throw new WebContainerDeploymentDescException("Cannot find if there is alt-dd for '" + unpackedWarFile
                        + "', the setAltDD function was badly called");
            }
            // Now we can get the optional alt-dd url file
            altDDUrl = urlAltddBindings.get(unpackedWarURL);
        }

        // ... and get the instance of the WebContainerDeploymentDesc.
        // If there is an alternate url for the web.xml, call the method with
        // this param.
        WebContainerDeploymentDesc webDD = null;
        try {
            if (altDDUrl != null) {
                webDD = getInstance(unpackedWarFile.getPath(), loaderForCls, altDDUrl.getFile());
            } else {
                webDD = getInstance(unpackedWarFile.getPath(), loaderForCls);
            }
        } catch (DeploymentDescException dde) {
            throw new WebContainerDeploymentDescException(dde);
        }

        // Resolve the ejb-link for ejb-ref
        EjbRefDesc[] ejbRef = webDD.getEjbRefDesc();
        for (EjbRefDesc element : ejbRef) {
            if (element.getJndiName() == null) {
                String ejbLink = element.getEjbLink();
                String ejbRefType = element.getEjbRefType();
                if (ejbLink != null) {
                    if (earLoader == null) {
                        throw new WebContainerDeploymentDescException(
                                "Ejb-link is not authorized from a single war. The war must be in an ear.");
                    } else {
                        String jndiName = getJndiName(originalWarURL, ejbLink, earLoader, ejbRefType, true);
                        element.setJndiName(jndiName);
                    }
                }
            }
        }

        // Resolve the ejb-link for ejb-local-ref
        EjbLocalRefDesc[] ejbLocalRef = webDD.getEjbLocalRefDesc();
        for (EjbLocalRefDesc element : ejbLocalRef) {
            String ejblink = element.getEjbLink();
            if (earLoader == null) {
                throw new WebContainerDeploymentDescException(
                        "Ejb-link is not authorized from a single war. The war must be in an ear.");
            }
            String ejbRefType = element.getEjbRefType();
            String ejbName = getJndiName(originalWarURL, ejblink, earLoader, ejbRefType, false);
            element.setJndiLocalName(ejbName);
        }

        // Resolve the port-component-link for service-ref
        IServiceRefDesc[] serviceRef = webDD.getServiceRefDesc();

        for (IServiceRefDesc element : serviceRef) {
            List<PortComponentRefDesc> pcRefs = element.getPortComponentRefs();
            for (int j = 0; j < pcRefs.size(); j++) {
                // for each service portComponents : resolve links
                PortComponentRefDesc pcr = pcRefs.get(j);
                String pclink = pcr.getPortComponentLink();
                if (pclink != null) {
                    // a pc link is defined, we resolve it
                    PortComponentDesc pcDesc = getPCDesc(originalWarURL, pclink, loaderForCls, earLoader);
                    pcr.setPortComponentDesc(pcDesc);
                }
            }
        }

        // Resolve the message-destination-link for message-destination-ref
        MessageDestinationRefDesc[] mdRef = webDD.getMessageDestinationRefDesc();
        for (MessageDestinationRefDesc element : mdRef) {
            if (element.getJndiName() == null) {
                String mdLink = element.getMessageDestinationLink();
                String mdType = element.getMessageDestinationType();
                String mdUsage = element.getMessageDestinationUsage();
                if (mdLink != null) {
                    if (earLoader == null) {
                        throw new WebContainerDeploymentDescException(
                                "Message-destination-link is not authorized from a single client jar. "
                              + "The client jar must be in an ear.");
                    } else {
                        String mdName = getMDJndiName(originalWarURL, mdLink, mdType, mdUsage, earLoader);
                        element.setJndiName(mdName);
                    }
                }
            }
        }

        return webDD;
    }

    /**
     * Return the port component desc from the pcLink string. pcLink format :
     * filename.[jar or war]#portComponentName in the same Ear File
     * @param warURL the url of the war being parsed. This is needed because
     *        pcLink is relative. With the url and the pcLink, we can know where
     *        the file is locate.
     * @param pcLink the pcLink tag of an port-component-ref.
     * @param earLoader the classloader of the ear.
     * @param moduleLoader classlaoder of the current module
     * @return the pcLink portComponent.
     * @throws WSDeploymentDescException when it failed
     */
    private PortComponentDesc getPCDesc(final URL warURL, final String pcLink, final ClassLoader moduleLoader,
            final ClassLoader earLoader) throws WSDeploymentDescException {
        // now ask WS Manager for port-component-desc
        return wsDDManager.getPortComponentDesc(warURL, pcLink, moduleLoader, earLoader);
    }

    /**
     * Return the JNDI name from the ejbLink string. ejbLink format :
     * filename.jar#beanName in the same Ear File beanName in the same ejb-jar
     * file.
     * @param warURL the url of the war being parsed. This is needed because
     *        ejbLink is relative. With the url and the ejbLink, we can know
     *        where the file is locate.
     * @param ejbLink the ejbLink tag of an ejb-ref.
     * @param earLoader the classloader of the ear.
     * @param ejbType the type of the referenced ejb in the ejb-ref tag.
     * @param isEjbRef true if the jndi name to resolve is an ejb-ref
     * @return the JNDI name if found, null otherwise
     * @throws DeploymentDescException when it failed
     */
    private String getJndiName(final URL warURL, final String ejbLink, final ClassLoader earLoader, final String ejbType,
            final boolean isEjbRef) throws DeploymentDescException {
        // Now ask EJB deployment Desc manager :
        return ejbDDManager.getJndiName(warURL, ejbLink, earLoader, ejbType, null, isEjbRef);
    }

    /**
     * Return the JNDI name from the mdLink string. mdLink format :
     * filename.jar#mdName in the same Ear File
     * @param warURL the url of the jar being parsed. This is needed because
     *        mdLink is relative. With the url and the mdLink, we can know where
     *        the file is locate.
     * @param mdLink the mdLink tag of a message-destination-ref
     * @param mdType the type of the referenced mdb in the
     *        message-destination-ref tag.
     * @param mdUsage the usage of the referenced mdb in the
     *        message-destination-ref tag.
     * @param earLoader the classloader of the ear.
     * @return the JNDI name if found, null otherwise
     * @throws WebContainerDeploymentDescException when it failed
     */
    private String getMDJndiName(final URL warURL, final String mdLink, final String mdType, final String mdUsage,
            final ClassLoader earLoader) throws WebContainerDeploymentDescException {
        // Extract from the mdb link
        // - the name of the file
        // - the name of the destination
        String ejbJarLink = null;
        String destNameLink = null;
        org.ow2.jonas.deployment.ejb.DeploymentDesc dd = null;

        // Check the format of the ejb-link. It must contains .jar#
        if (mdLink.toLowerCase().indexOf(".jar#") == -1) {
            String err = "Message-destination-link " + mdLink
                    + " has a bad format. Correct format :  filename.jar#messageDestinationName";
            throw new WebContainerDeploymentDescException(err);
        }

        StringTokenizer st = new StringTokenizer(mdLink, LINK_SEPARATOR);

        // We must have only two elements after this step, one for the fileName
        // before the # and the name of the message-destination after the # char
        if (st.countTokens() != 2 || mdLink.startsWith(LINK_SEPARATOR) || mdLink.endsWith(LINK_SEPARATOR)) {

            String err = "Message-destination-link " + mdLink
                    + " has a bad format. Correct format :  filename.jar#messageDestinationName.";
            throw new WebContainerDeploymentDescException(err);
        }

        // Get the token
        ejbJarLink = st.nextToken();
        destNameLink = st.nextToken();

        // Check if ejbJarLink is a jar or not
        if (!ejbJarLink.endsWith(".jar")) {
            String err = "Ejbjar filename " + ejbJarLink + " from the message-destination-link " + mdLink
                    + " has a bad format. Correct format :  filename.jar";
            throw new WebContainerDeploymentDescException(err);
        }

        // Now construct the URL from the absolute path from the url warURL and
        // the relative path from ejbJarLink
        URL ejbJarLinkUrl = null;
        try {
            ejbJarLinkUrl = new File(new File(warURL.getFile()).getParent() + File.separator + ejbJarLink).getCanonicalFile()
                    .toURL();
        } catch (MalformedURLException mue) {
            String err = "Error when creating an url for the ejb jar filename. Error :" + mue.getMessage();
            throw new WebContainerDeploymentDescException(err);
        } catch (IOException ioe) {
            String err = "Error when creating/accessing a file. Error :" + ioe.getMessage();
            throw new WebContainerDeploymentDescException(err);
        }

        // Check if the jar exist.
        if (!new File(ejbJarLinkUrl.getFile()).exists()) {
            String err = "Cannot get the deployment descriptor for '" + ejbJarLinkUrl.getFile() + "'. The file doesn't exist.";
            throw new WebContainerDeploymentDescException(err);
        }

        // We've got the url
        // Now, We can ask the Deployment Descriptor of this url
        URL[] ddURL = new URL[1];
        ddURL[0] = ejbJarLinkUrl;
        URLClassLoader loaderForClsEjb = new URLClassLoader(ddURL, earLoader);
        try {
            dd = ejbDDManager.getDeploymentDesc(ejbJarLinkUrl, loaderForClsEjb, earLoader);
        } catch (DeploymentDescException e) {
            String err = "Cannot get the deployment descriptor for '" + ejbJarLinkUrl.getFile() + "'.";
            throw new WebContainerDeploymentDescException(err, e);
        }

        JonasMessageDestination md = dd.getJonasMessageDestination(mdLink);

        if (md == null) {
            String err = "No message-destination-link was found for '" + mdLink + "' in the file " + warURL.getFile()
                    + " specified.";
            throw new WebContainerDeploymentDescException(err);
        }

        // Check if the type & usage of the message-destination-ref is correct.
        // For now checkTypeUsage(warURL, mdType, mdUsage, dd);

        return md.getJndiName();
    }

    /**
     * Make a cleanup of the cache of deployment descriptor. This method must be
     * invoked after the ear deployment by the EAR service.
     * @param earClassLoader the ClassLoader of the ear application to remove
     *        from the cache.
     */
    public void removeCache(final ClassLoader earClassLoader) {
        // Remove the altdd mapping
        earCLAltDDBindings.remove(earClassLoader);

        // Then remove the cache of the ejb dd manager
        ejbDDManager.removeCache(earClassLoader);
    }

    /**
     * Set the alt deployment desc which are used instead of the web.xml file
     * which is in the war file. The alt-dd tag is in the application.xml file
     * of the ear files and is used ony in the EAR case. ie : deployment of wars
     * packaged into EAR applications. alt-dd tag is optionnal
     * @param earClassLoader the ear classloader which is used for mapped the
     *        URLs of the wars to the Alt dd.
     * @param urls the urls of the wars
     * @param altDDs the alt-dd name for the specified war URLs
     */
    public void setAltDD(final ClassLoader earClassLoader, final URL[] urls, final URL[] altDDs) {
        // Associate an url to a altDD url
        Hashtable<URL, URL> urlAltddBindings = new Hashtable<URL, URL>();

        // Fill the hashtable for each url
        for (int i = 0; i < urls.length; i++) {
            if (altDDs[i] != null) {
                urlAltddBindings.put(urls[i], altDDs[i]);
            }
        }

        // Bind the hashtable
        earCLAltDDBindings.put(earClassLoader, urlAltddBindings);
    }

    /**
     * Get the size of the cache (number of entries in the cache). This method
     * is used only for the tests.
     * @return the size of the cache (number of entries in the cache).
     */
    public int getCacheSize() {
        int bufferSize = 0;

        for (ClassLoader loader : earCLAltDDBindings.keySet()) {
            bufferSize = bufferSize + earCLAltDDBindings.get(loader).size();
        }

        return bufferSize;
    }

    /**
     * Get the specified web deployment descriptor.
     * @param filename the filename where to load xml deployment descriptors.
     * @param loader classloader used to load web classes.
     * @return WebContainerDeploymentDesc the web deployment descriptor.
     * @throws WebContainerDeploymentDescException when
     *         WebContainerDeploymentDesc cannot be created with the given
     *         files.
     */
    public static WebContainerDeploymentDesc getDeploymentDesc(final String filename, final ClassLoader loader)
            throws WebContainerDeploymentDescException {
        WebContainerDeploymentDesc wcdd = null;
        // if Desc already parsed
        if (staticCache.containsKey(filename)) {
            wcdd = staticCache.get(filename);
        } else {
            // Check if the war exists ...
            if (!new File(filename).exists()) {
                String err = "Cannot get the deployment descriptor for ";
                err += "'" + filename + "'. The file doesn't exist.";
                throw new WebContainerDeploymentDescException(err);
            }
            // get the DeploymentDesc
            try {
                wcdd = getInstance(filename, loader);
            } catch (DeploymentDescException dde) {
                throw new WebContainerDeploymentDescException(dde);
            }
            // put in cache
            staticCache.put(filename, wcdd);
        }

        return wcdd;
    }

    /**
     * Get an instance of a WEB deployment descriptor by parsing the web.xml and
     * jonas-web.xml deployment descriptors.
     * @param warFileName the fileName of the war file for the deployment
     *        descriptors.
     * @param classLoaderForCls the classloader for the classes.
     * @param altWebXmlFilename the fileName to the web.xml for the alt-dd tag
     *        in the Ear Case. This is used for specify an alternate DDesc file.
     * @return a WEB deployment descriptor by parsing the web.xml and
     *         jonas-web.xml deployment descriptors.
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    public static WebContainerDeploymentDesc getInstance(final String warFileName, final ClassLoader classLoaderForCls,
            final String altWebXmlFilename) throws DeploymentDescException {
        // init xml contents values;
        String xmlContent = "";
        String jonasXmlContent = "";

        // war file
        JarFile warFile = null;

        // Input streams
        InputStream webInputStream = null;
        InputStream jonasWebInputStream = null;

        // ZipEntry
        ZipEntry webZipEntry = null;
        ZipEntry jonasWebZipEntry = null;

        // Webapps
        WebApp webApp;
        JonasWebApp jonasWebApp;

        // Build the file
        File fWar = new File(warFileName);

        // Check if the file exists.
        if (!(fWar.exists())) {
            String err = "' " + warFileName + "' was not found.";
            throw new WebContainerDeploymentDescException(err);
        }

        // Check if the Alt deploymentDesc file exists.
        // But only if it's a non null value because it's optionnal.
        if ((altWebXmlFilename != null) && (!new File(altWebXmlFilename).exists())) {
            String err = "The file for the altdd tag for the EAR case '" + altWebXmlFilename + "' was not found.";
            throw new WebContainerDeploymentDescException(err);
        }

        // load the web-app deployment descriptor data (WEB-INF/web.xml
        // and WEB-INF/jonas-web.xml)
        try {
            // No alt-dd case
            if (altWebXmlFilename == null) {

                // If warFile is a directory, there is no jar. Check file in the
                // directory
                if (fWar.isDirectory()) {
                    // lookup a WEB-INF/web.xml file
                    File webXmlF = new File(warFileName, WEB_FILE_NAME);
                    if (!webXmlF.exists()) {
                        String err = "You have choose to deploy a war directory but there is no " + WEB_FILE_NAME
                                + " file in the directory " + warFileName;
                        throw new WebContainerDeploymentDescException(err);
                    }
                    webInputStream = new FileInputStream(webXmlF);
                    xmlContent = xmlContent(webInputStream);
                    webInputStream = new FileInputStream(webXmlF);
                } else {
                    warFile = new JarFile(warFileName);
                    // Lookup in the JAR
                    // Check the web entry
                    webZipEntry = warFile.getEntry(WEB_FILE_NAME);
                    if (webZipEntry == null) {
                        throw new WebContainerDeploymentDescException("The entry '" + WEB_FILE_NAME
                                + "' was not found in the file '" + warFileName + "'.");
                    }
                    // Get the stream
                    webInputStream = warFile.getInputStream(webZipEntry);
                    xmlContent = xmlContent(webInputStream);
                    webInputStream = warFile.getInputStream(webZipEntry);
                }
            } else {
                webInputStream = new FileInputStream(altWebXmlFilename);
                xmlContent = xmlContent(webInputStream);
                webInputStream = new FileInputStream(altWebXmlFilename);
            }

            // This is a directory
            if (fWar.isDirectory()) {
                // lookup a WEB-INF/jonas-web.xml file
                File webJXmlF = new File(warFileName, JONAS_WEB_FILE_NAME);
                if (webJXmlF.exists()) {
                    jonasWebInputStream = new FileInputStream(webJXmlF);
                    jonasXmlContent = xmlContent(jonasWebInputStream);
                    jonasWebInputStream = new FileInputStream(webJXmlF);
                }
            } else {
                if (warFile == null) {
                    warFile = new JarFile(warFileName);
                }

                // Check the jonas web entry
                jonasWebZipEntry = warFile.getEntry(JONAS_WEB_FILE_NAME);

                // Get the stream
                if (jonasWebZipEntry != null) {
                    jonasWebInputStream = warFile.getInputStream(jonasWebZipEntry);
                    jonasXmlContent = xmlContent(jonasWebInputStream);
                    jonasWebInputStream = warFile.getInputStream(jonasWebZipEntry);

                }
            }
        } catch (Exception e) {
            if (warFile != null) {
                try {
                    warFile.close();
                } catch (IOException ioe) {
                    // We can't close the file
                    logger.log(BasicLevel.WARN, "Can't close file '" + warFileName + "'");
                }
            }
            throw new WebContainerDeploymentDescException("Cannot read the XML deployment descriptors of the war file '"
                    + warFileName + "'.", e);
        }

        webApp = loadWebApp(new InputStreamReader(webInputStream), WEB_FILE_NAME);
        try {
            webInputStream.close();
        } catch (IOException e) {
            // Nothing to do
            logger.log(BasicLevel.WARN, "Can't close InputStream of web.xml from '" + warFileName + "'");
        }

        // load jonas-web-app deployment descriptor data
        // (WEB-INF/jonas-web.xml)
        if (jonasWebInputStream != null) {
            jonasWebApp = loadJonasWebApp(new InputStreamReader(jonasWebInputStream), JONAS_WEB_FILE_NAME);
            try {
                jonasWebInputStream.close();
            } catch (IOException e) {
                // Nothing to do
                logger.log(BasicLevel.WARN, "Can't close InputStream of jonas-web.xml from '" + warFileName + "'");
            }
        } else {
            jonasWebApp = new JonasWebApp();
        }

        // close the zip
        if (warFile != null) {
            try {
                warFile.close();
            } catch (IOException ioe) {
                // We can't close the file
                logger.log(BasicLevel.WARN, "Can't close file '" + warFileName + "'");
            }
        }

        EZBENCBindingHolder holder = completeWebApp(fWar, webApp, jonasWebApp, classLoaderForCls);

        // Instantiate web deployment descriptor
        WebContainerDeploymentDesc webDD = new WebContainerDeploymentDesc(warFileName, classLoaderForCls, webApp, jonasWebApp);
        webDD.setXmlContent(xmlContent);
        webDD.setJOnASXmlContent(jonasXmlContent);

        // Store the EncBindingHolder
        webDD.setENCBindingHolder(holder);

        return webDD;
    }

    /**
     * Complete the given webapp object and jonasWebApp by reading annotations.
     * @param fWar given war file to analyze
     * @param webApp the web app struct
     * @param jonasWebApp the jonas webapp struct
     * @param classLoader the classloader used to get the classes
     * @return the binding holder
     * @throws WebContainerDeploymentDescException if complete is not done
     */
    public static EZBENCBindingHolder completeWebApp(final File fWar, final WebApp webApp, final JonasWebApp jonasWebApp,
            final ClassLoader classLoader) throws WebContainerDeploymentDescException {
        // Analyze metadata of the webapp

        // First, get an archive
        IArchive webArchive = ArchiveManager.getInstance().getArchive(fWar);

        IWarDeployableMetadata warDeployableMetadata;
        WARDeployable warDeployable;
        try {
            warDeployable = (WARDeployable) DeployableHelper.getDeployable(webArchive);
            warDeployableMetadata = WarDeployableMetadataFactoryHolder.getWarDeployableMetadataFactory()
                    .createDeployableMetadata(warDeployable);
        } catch (Exception e) {
            throw new WebContainerDeploymentDescException(e);
        }

        // Now, complete the standard webapp object with the annotations found
        EZBENCBindingHolder encBindingHolder = null;
        try {
            encBindingHolder = ENCBindingBuilder.analyze(warDeployableMetadata);
        } catch (ENCBindingException e) {
            logger.log(BasicLevel.ERROR, "Unable to analyze metadata of '" + fWar + "'", e);
        }

        // FIXME verify if encBindingHolder==null

        // Get @Resource annotations bindings
        List<IBinding<IJAnnotationResource>> resourcesBindings = encBindingHolder.getResourceBindings();
        // Adds each binding as a resource object
        for (IBinding<IJAnnotationResource> resourceBinding : resourcesBindings) {
            // Build a new object and add it
            ResourceRef resourceRef = new ResourceRef();
            webApp.addResourceRef(resourceRef);

            // Sets the name
            resourceRef.setResRefName(resourceBinding.getName());

            // get object
            IJAnnotationResource annotationResource = resourceBinding.getValue();

            // Set auth
            AuthenticationType authType = annotationResource.getAuthenticationType();
            if (authType.equals(AuthenticationType.CONTAINER)) {
                resourceRef.setResAuth("Container");
            } else {
                resourceRef.setResAuth("Application");
            }

            // Sets type
            resourceRef.setResType(annotationResource.getType());

            // if there is a mapped name, add a jonas-resource element
            String mappedName = annotationResource.getMappedName();
            if (mappedName != null) {
                JonasResource jonasResource = new JonasResource();
                jonasResource.setResRefName(annotationResource.getName());
                jonasResource.setJndiName(mappedName);
                jonasWebApp.addJonasResource(jonasResource);
            }
        }

        return encBindingHolder;
    }

    /**
     * Get an instance of a WEB deployment descriptor by parsing the web.xml and
     * jonas-web.xml deployment descriptors.
     * @param warFileName the fileName of the war file for the deployment
     *        descriptors.
     * @param classLoaderForCls the classloader for the classes.
     * @return a WEB deployment descriptor by parsing the web.xml and
     *         jonas-web.xml deployment descriptors.
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    public static WebContainerDeploymentDesc getInstance(final String warFileName, final ClassLoader classLoaderForCls)
            throws DeploymentDescException {
        return getInstance(warFileName, classLoaderForCls, null);
    }

    /**
     * Load the web.xml file.
     * @param reader the reader of the XML file.
     * @param fileName the name of the file (web.xml).
     * @return a structure containing the result of the web.xml parsing.
     * @throws DeploymentDescException if the deployment descriptor is
     *         corrupted.
     */
    public static WebApp loadWebApp(final Reader reader, final String fileName) throws DeploymentDescException {
        WebApp webApp = new WebApp();

        // Create if null
        if (webAppDigester == null) {
            webAppDigester = new JDigester(webAppRuleSet, getParsingWithValidation(), true, new WebAppDTDs(),
                    new WebAppSchemas());
            webAppDigester.setUseContextClassLoader(false);
        }

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

    /**
     * Load the jonas-web.xml file.
     * @param reader the Reader of the XML file.
     * @param fileName the name of the file (jonas-web.xml).
     * @return a structure containing the result of the jonas-web.xml parsing.
     * @throws DeploymentDescException if the deployment descriptor is
     *         corrupted.
     */
    public static JonasWebApp loadJonasWebApp(final Reader reader, final String fileName) throws DeploymentDescException {
        JonasWebApp jonasWebApp = new JonasWebApp();

        // Create if null
        if (jonasWebAppDigester == null) {
            jonasWebAppDigester = new JDigester(jonasWebAppRuleSet, getParsingWithValidation(), true, new JonasWebAppDTDs(),
                    new JonasWebAppSchemas());
        }

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

    /**
     * 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(final boolean validation) {
        WebDeploymentDescManager.parsingWithValidation = validation;
    }
}
