/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2009 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
 *
 * Initial developer(s): Ludovic BERT & Florent BENOIT
 * --------------------------------------------------------------------------
 * $Id: WebDeploymentDescManager.java 17993 2009-07-21 21:30:03Z loris $
 * --------------------------------------------------------------------------
 */

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.rmi.RemoteException;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import javax.annotation.Resource.AuthenticationType;
import javax.ejb.EJBContext;
import javax.ejb.MessageDrivenContext;
import javax.ejb.SessionContext;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import javax.xml.ws.WebServiceContext;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.easybeans.resolver.api.EZBJNDIBeanData;
import org.ow2.easybeans.resolver.api.EZBJNDIData;
import org.ow2.easybeans.resolver.api.EZBRemoteJNDIResolver;
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.EjbRef;
import org.ow2.jonas.deployment.common.xml.JonasEjbRef;
import org.ow2.jonas.deployment.common.xml.JonasMessageDestination;
import org.ow2.jonas.deployment.common.xml.JonasResource;
import org.ow2.jonas.deployment.common.xml.JonasServiceRef;
import org.ow2.jonas.deployment.common.xml.MessageDestinationRef;
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.deploy.impl.helper.UnpackDeployableHelper;
import org.ow2.util.ee.metadata.common.api.enc.IENCBinding;
import org.ow2.util.ee.metadata.common.api.struct.IJAnnotationResource;
import org.ow2.util.ee.metadata.common.api.struct.IJEjbEJB;
import org.ow2.util.ee.metadata.common.api.struct.IJaxwsWebServiceRef;
import org.ow2.util.ee.metadata.common.impl.enc.ENCBindingException;
import org.ow2.util.ee.metadata.common.impl.enc.ENCBindingHolder;
import org.ow2.util.ee.metadata.war.api.IWarClassMetadata;
import org.ow2.util.ee.metadata.war.api.IWarDeployableMetadata;
import org.ow2.util.ee.metadata.war.impl.enc.ENCBindingBuilder;
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 originalWarURL URL of the WAR
     * @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 originalWarURL, final ClassLoader loaderForCls,
            final ClassLoader earLoader) throws DeploymentDescException {
        return getDeploymentDesc(null, originalWarURL, 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();
        }

        URL warURL = unpackedWarURL;
        if (unpackedWarURL == null) {
            warURL = originalWarURL;
        }
        File warFile = URLUtils.urlToFile(warURL);

        // Check if the war exists ...
        if (!warFile.exists()) {
            throw new WebContainerDeploymentDescException("Cannot get the deployment descriptor for '" + warFile
                    + "'. 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 '" + warFile
                        + "', the setAltDD function was badly called");
            }
            // Now we can get the optional alt-dd url file
            altDDUrl = urlAltddBindings.get(warURL);
        }

        // ... 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(warFile.getPath(), loaderForCls, altDDUrl.getFile());
            } else {
                webDD = getInstance(warFile.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();
                String remote = element.getRemote();
                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, remote, 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 local = element.getLocal();
            String ejbName = getJndiName(originalWarURL, ejblink, earLoader, ejbRefType, local, 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 itf the interface name of the ejb-link.
     * @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 String itf, final boolean isEjbRef) throws DeploymentDescException {
        String jndiName = null;
        try {
            // Now ask EJB deployment Desc manager :
            jndiName = ejbDDManager.getJndiName(warURL, ejbLink, earLoader, ejbType, null, isEjbRef);
        } catch (DeploymentDescException e) {
            // log first exception
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Unable to get JNDI name with the EJB DD manager", e);
            }

            // Was not able to get a JNDI name, try with the remote object.
            EZBRemoteJNDIResolver jndiResolver = null;

            // Get object
            Object o = null;
            try {
                o = new InitialContext().lookup("EZB_Remote_JNDIResolver");
            } catch (NamingException ne) {
                // No Remote JNDI resolver, so throw first exception
                logger.log(BasicLevel.DEBUG, "No Remote EJB3 JNDI Resolver found");
                throw e;
            }

            // Object is here, so we can cast it
            jndiResolver = (EZBRemoteJNDIResolver) PortableRemoteObject.narrow(o, EZBRemoteJNDIResolver.class);

            // Ask the Resolver with interface name and bean name
            String interfaceName = itf;
            String beanName = ejbLink;
            List<EZBJNDIBeanData> jndiDataList = null;
            try {
                jndiDataList = jndiResolver.getEJBJNDINames(interfaceName, beanName);
            } catch (RemoteException re) {
                // No Remote JNDI resolver, so throw first exception
                throw new DeploymentDescException("Unable to get EJB-LINK for interface'" + interfaceName + "' and beanName'" + beanName + "'", re);
            }

            // Data is here, check if it is empty or not
            if (jndiDataList.size() == 0) {
                throw new DeploymentDescException("Unable to get EJB-LINK for interface'" + interfaceName + "' and beanName'" + beanName + "', no data was found on the remote side.");
            } else if (jndiDataList.size() > 1) {
                // too many entries
                throw new DeploymentDescException("Unable to get EJB-LINK for interface'" + interfaceName + "' and beanName'" + beanName + "', too many answers : '" + jndiDataList + "'.");
            }

            // Only one item found, so get JNDI Name from this object
            EZBJNDIBeanData jndiData = jndiDataList.get(0);
            // Update JNDI name
            jndiName = jndiData.getName();
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Found JNDI Name '" + jndiName + "' for interface'" + interfaceName + "' and beanName'" + beanName + "', too many answers : '" + jndiDataList + "'.");
            }
        }

        // return the value
        return jndiName;

    }

    /**
     * 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;
        JonasMessageDestination md = null;

        // contains ejb-jar name
        if (mdLink.indexOf("#") > 0) {
            ejbJarLink = mdLink.split("#")[0];
            destNameLink = mdLink.split("#")[1];
        } else {
            // ask for any jar
           destNameLink = mdLink;
        }
        if (ejbJarLink != null) {
            // 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);
            }

            md = dd.getJonasMessageDestination(mdLink);
        }

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

            // Not found, try with the remote resolver

            // Was not able to get a JNDI name, try with the remote object.
            EZBRemoteJNDIResolver jndiResolver = null;

            // Get object
            Object o = null;
            try {
                o = new InitialContext().lookup("EZB_Remote_JNDIResolver");
            } catch (NamingException ne) {
                // No Remote JNDI resolver, so throw first exception
                logger.log(BasicLevel.DEBUG, "No Remote EJB3 JNDI Resolver found");
                throw e;
            }

            // Object is here, so we can cast it
            jndiResolver = (EZBRemoteJNDIResolver) PortableRemoteObject.narrow(o, EZBRemoteJNDIResolver.class);

            // Ask the Resolver
            List<EZBJNDIData> jndiDataList = null;
            try {
                jndiDataList = jndiResolver.getMessageDestinationJNDINames(destNameLink);
            } catch (RemoteException re) {
                // No Remote JNDI resolver, so throw first exception
                throw new WebContainerDeploymentDescException("Unable to get EJB-LINK for destination '" + destNameLink + "'", re);
            }

            // Data is here, check if it is empty or not
            if (jndiDataList.size() == 0) {
                throw new WebContainerDeploymentDescException("Unable to get EJB-LINK for destination '" + destNameLink + "'");
            } else if (jndiDataList.size() > 1) {
                // too many entries
                throw new WebContainerDeploymentDescException("Unable to get EJB-LINK for destination '" + destNameLink + "', too many answers : '" + jndiDataList + "'.");
            }

            // Only one item found, so get JNDI Name from this object
            EZBJNDIData jndiData = jndiDataList.get(0);
            // Update JNDI name
            return jndiData.getName();
        }

        // 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 + "'");
            }
        }

        // Compute Metadatas
        IWarDeployableMetadata warDeployableMetadata = getWarMetadata(fWar);

        // Create a structure that holds ENCBinding(s)
        ENCBindingHolder holder = completeWebApp(warDeployableMetadata,
                                                 webApp,
                                                 jonasWebApp,
                                                 classLoaderForCls);

        Map<String, IWarClassMetadata> pojos = findPOJOWebService(warDeployableMetadata);

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

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

        return webDD;
    }

    private static Map<String, IWarClassMetadata> findPOJOWebService(final IWarDeployableMetadata warDeployableMetadata) {

        Map<String, IWarClassMetadata> services = new Hashtable<String, IWarClassMetadata>();
        Collection<IWarClassMetadata> classes = warDeployableMetadata.getWarClassMetadataCollection();

        for (IWarClassMetadata clazz : classes) {
            // 512 is the marker byte for interface in ASM
            boolean isInterface = (clazz.getJClass().getAccess() & 512) != 0;
            boolean isAbstract= (clazz.getJClass().getAccess() & 1024) != 0;

            // Only deal with concrete classes annotated with @WebService or @WebServiceProvider
            boolean isAnnotated = (clazz.getWebServiceMarker() != null);
            if (!isInterface && !isAbstract && isAnnotated) {
                String classname = clazz.getJClass().getName().replace('/', '.');
                services.put(classname, clazz);
            }
        }

        return services;
    }

    /**
     * Complete the given webapp object and jonasWebApp by reading annotations.
     * @param warDeployableMetadata source metadata
     * @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 ENCBindingHolder completeWebApp(final IWarDeployableMetadata warDeployableMetadata,
                                                  final WebApp webApp,
                                                  final JonasWebApp jonasWebApp,
                                                  final ClassLoader classLoader) throws WebContainerDeploymentDescException {

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

        // Get @Resource annotations bindings
        List<IENCBinding<IJAnnotationResource>> resourcesBindings = encBindingHolder.getResourceBindings();
        // Adds each binding as a resource object
        for (IENCBinding<IJAnnotationResource> resourceBinding : resourcesBindings) {
            // get object
            IJAnnotationResource annotationResource = resourceBinding.getValue();

            // Already present ?
            String resourceName = resourceBinding.getName();
            if (containsResource(resourceName, webApp)) {
                continue;
            }

            // get type
            String type = annotationResource.getType();

            // Destination link
            String messageDestinationLink = annotationResource.getMessageDestinationLink();

            // Resource ref
            if (messageDestinationLink == null) {
                // Exclude declaration of resource-ref for SessionContext, EJBContext or MessageDrivenContext
                if (!type.equals(SessionContext.class.getName()) &&
                        !type.equals(EJBContext.class.getName()) &&
                        !type.equals(MessageDrivenContext.class.getName()) &&
                        !type.equals(WebServiceContext.class.getName())) {


                    // Build a new object and add it
                    ResourceRef resourceRef = new ResourceRef();
                    webApp.addResourceRef(resourceRef);

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

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

                    // Sets type
                    resourceRef.setResType(type);

                    // 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);
                    }
                }
            }  else {
                // Message destination ref
                MessageDestinationRef messageDestinationRef = new MessageDestinationRef();
                webApp.addMessageDestinationRef(messageDestinationRef);

                // Sets the name
                messageDestinationRef.setMessageDestinationRefName(resourceBinding.getName());

                // Set auth
                messageDestinationRef.setMessageDestinationLink(messageDestinationLink);

                // Sets type
                messageDestinationRef.setMessageDestinationType(annotationResource.getType());

                // if there is a mapped name, add a jonas-resource element
                String mappedName = annotationResource.getMappedName();
                if (mappedName != null) {
                    JonasMessageDestination jonasMessageDestination = new JonasMessageDestination();
                    jonasMessageDestination.setMessageDestinationName(annotationResource.getName());
                    jonasMessageDestination.setJndiName(mappedName);
                    jonasWebApp.addJonasMessageDestination(jonasMessageDestination);
                }

            }
        }



        // Iterates on jonas-service-ref elements to see if there are some values to override
        List<IENCBinding<IJaxwsWebServiceRef>> webServicesBindings = encBindingHolder.getWebServicesBindings();
        for (IENCBinding<IJaxwsWebServiceRef> binding : webServicesBindings) {
            IJaxwsWebServiceRef wsr = binding.getValue();
            JonasServiceRef jsr = findJonasServiceRef(jonasWebApp.getJonasServiceRefList(), wsr.getName());

            // Found a matching jonas-service-ref
            if (jsr != null) {
                mergeWebServiceRef(jsr, wsr);
            }
        }

        // Get @EJB annotations bindings
        List<IENCBinding<IJEjbEJB>> ejbsBindings = encBindingHolder.getEJBBindings();

        // Adds each binding as a resource object
        for (IENCBinding<IJEjbEJB> ejbBinding : ejbsBindings) {
            // Build a new object and add it
            EjbRef ejbRef = new EjbRef();
            webApp.addEjbRef(ejbRef);

            // Sets the name
            ejbRef.setEjbRefName(ejbBinding.getName());


            // get object
            IJEjbEJB jEJB = ejbBinding.getValue();

            // if there is a mapped name, add a jonas-resource element
            String mappedName = jEJB.getMappedName();
            if (mappedName != null) {
                JonasEjbRef jonasEjbRef = new JonasEjbRef();
                jonasEjbRef.setEjbRefName(ejbBinding.getName());
                jonasEjbRef.setJndiName(mappedName);
                jonasWebApp.addJonasEjbRef(jonasEjbRef);
            } else {
                // Ejb link ?
                String beanName = ejbBinding.getValue().getBeanName();
                if (beanName != null && !"".equals(beanName)) {
                    ejbRef.setEjbLink(beanName);
                }

                // Bean interface ?
                String beanInterface = ejbBinding.getValue().getBeanInterface();
                if (beanInterface != null && !"".equals(beanInterface)) {
                    ejbRef.setRemote(beanInterface);
                }
            }

        }

        return encBindingHolder;
    }

    /**
     * Extract all the metadatas from the webapp.
     * @param fWar the webapp
     * @return the metadata structure associated to this webapp
     * @throws WebContainerDeploymentDescException if an error occured during annotation/XML parsing
     */
    public static IWarDeployableMetadata getWarMetadata(final File fWar) throws WebContainerDeploymentDescException {
        // Analyze metadata of the webapp

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

        IWarDeployableMetadata warDeployableMetadata;
        WARDeployable warDeployable;
        try {
            // Workaround for http://bugs.sun.com/view_bug.do?bug_id=6548436
            warDeployable = WARDeployable.class.cast(DeployableHelper.getDeployable(webArchive));
            // check if war is uncompressed
            if (!fWar.isDirectory()) {
                warDeployable = UnpackDeployableHelper.unpack(warDeployable);
                logger.log(BasicLevel.DEBUG, "Unpack a war to create metadata");
            }
            warDeployableMetadata = WarDeployableMetadataFactoryHolder.getWarDeployableMetadataFactory()
                    .createDeployableMetadata(warDeployable);
        } catch (Exception e) {
            throw new WebContainerDeploymentDescException(e);
        }
        return warDeployableMetadata;
    }

    /**
     * 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;
    }
}
