/**
 * 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
 *
 * --------------------------------------------------------------------------
 * $Id: WSDeploymentDescManager.java 18310 2009-08-20 12:23:22Z sauthieg $
 * --------------------------------------------------------------------------
 */

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

import java.io.File;
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.net.URLConnection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
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.ejb.BeanDesc;
import org.ow2.jonas.deployment.ejb.DeploymentDesc;
import org.ow2.jonas.deployment.ejb.SessionStatelessDesc;
import org.ow2.jonas.deployment.ejb.lib.EjbDeploymentDescManager;
import org.ow2.jonas.deployment.web.WebContainerDeploymentDesc;
import org.ow2.jonas.deployment.web.WebContainerDeploymentDescException;
import org.ow2.jonas.deployment.web.lib.WebDeploymentDescManager;
import org.ow2.jonas.deployment.ws.JonasWsSchemas;
import org.ow2.jonas.deployment.ws.PortComponentDesc;
import org.ow2.jonas.deployment.ws.ServiceDesc;
import org.ow2.jonas.deployment.ws.WSDeploymentDesc;
import org.ow2.jonas.deployment.ws.WSDeploymentDescException;
import org.ow2.jonas.deployment.ws.WsSchemas;
import org.ow2.jonas.deployment.ws.rules.JonasWebservicesRuleSet;
import org.ow2.jonas.deployment.ws.rules.WebservicesRuleSet;
import org.ow2.jonas.deployment.ws.xml.JonasWebservices;
import org.ow2.jonas.deployment.ws.xml.Webservices;
import org.ow2.jonas.lib.util.Log;
import org.ow2.util.url.URLUtils;

/**
 * This class provide a way for managing the WSDeploymentDesc. Note that there
 * is 1 instance of the WSDeploymentDescManager on each JOnAS server.
 * @author Guillaume Sauthier
 * @author Xavier Delplanque
 * @author Helene Joanin
 */
public class WSDeploymentDescManager extends AbsDeploymentDescManager {

    /**
     * ejb-jar.xml filename.
     */
    public static final String WS_EJBJAR_FILE_NAME = "META-INF/webservices.xml";

    /**
     * jonas-ejb-jar.xml filename.
     */
    public static final String JONAS_WS_EJBJAR_FILE_NAME = "META-INF/jonas-webservices.xml";

    /**
     * ejb-jar.xml filename.
     */
    public static final String WS_WEBAPP_FILE_NAME = "WEB-INF/webservices.xml";

    /**
     * jonas-ejb-jar.xml filename.
     */
    public static final String JONAS_WS_WEBAPP_FILE_NAME = "WEB-INF/jonas-webservices.xml";

    /**
     * Digester used to parse webservices.xml.
     */
    private static JDigester wsDigester = null;

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

    /**
     * Rules to parse the webservices.xml.
     */
    private static WebservicesRuleSet wsRuleSet = new WebservicesRuleSet();

    /**
     * Rules to parse the jonas-webservices.xml.
     */
    private static JonasWebservicesRuleSet jwsRuleSet = new JonasWebservicesRuleSet();

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

    /**
     * The unique instance of the WSDeploymentDescManager.
     */
    private static WSDeploymentDescManager unique = null;

    /**
     * is the manager instanciated ?
     */
    private static boolean isInstanciated = false;

    /**
     * Logger for the deployment desc manager.
     */
    private static Logger logger = Log.getLogger("org.ow2.jonas.ws");

    /**
     * Associates module or webservices dd URL and a WSDeploymentDesc.
     */
    private Map<URL, WSDeploymentDesc> urlWsddBindings;

    /**
     * Associates a given ClassLaoder (ear/ejb/web) to a list of URL containing
     * (or not) WSDD.
     */
    private Map<ClassLoader, List<URL>> classLoader2URLs;

    /**
     * EJB Descriptor Manager.
     */
    private EjbDeploymentDescManager ejbManager = null;

    /**
     * Web Descriptor Manager.
     */
    private WebDeploymentDescManager webManager = null;

    /**
     * Constructor : creates a WSDeploymentDescManager object. Used only when
     * loading files in the running JOnAS instance.
     */
    private WSDeploymentDescManager() {
        urlWsddBindings = new HashMap<URL, WSDeploymentDesc>();
        classLoader2URLs = new Hashtable<ClassLoader, List<URL>>();

        ejbManager = EjbDeploymentDescManager.getInstance();
        webManager = WebDeploymentDescManager.getInstance();
    }

    /**
     * Get the unique instance of the WSDeploymentDescManager.
     * @return the instance of the WSDeploymentDescManager.
     */
    public static WSDeploymentDescManager getInstance() {
        if (!isInstanciated) {
            isInstanciated = true;
            unique = new WSDeploymentDescManager();

        }
        return unique;
    }

    /**
     * Get the specified WebService deployment descriptor. Used by WsGen.
     * @param file module (ejbjar or war). It is required that file is not a
     *        directory.
     * @param jarCL the classloader where classes are stored.
     * @return the module webservices deployment descriptor if it exists, null
     *         else.
     * @throws WSDeploymentDescException when WSDeploymentDesc cannot be created
     *         with the given files.
     */
    public static WSDeploymentDesc getDeploymentDesc(final String file, final ClassLoader jarCL)
            throws WSDeploymentDescException {

        URL fileUrl = null;
        try {
            fileUrl = new File(file).toURL();
        } catch (MalformedURLException mue) {
            throw new WSDeploymentDescException(mue);
        }

        WSDeploymentDesc wsdd = loadDeploymentDesc(fileUrl, jarCL);

        // webservices.xml not present in file, no resolution needed
        if (wsdd == null) {
            return null;

        }

        // Resolve ejb-link and servlet-link elements
        List<ServiceDesc> sdl = wsdd.getServiceDescs();

        for (int i = 0; i < sdl.size(); i++) {
            List<PortComponentDesc> pcdl = sdl.get(i).getPortComponents();

            for (int j = 0; j < pcdl.size(); j++) {
                // Resolve the ejb-link and servlet-link of each port components
                PortComponentDesc pcd = pcdl.get(j);
                String sibLink = pcd.getSibLink();

                // a link is defined, we resolve it
                if (pcd.hasBeanImpl()) {
                    SessionStatelessDesc desc = getBeanDesc(file, sibLink, jarCL);
                    pcd.setDesc(desc);

                } else if (pcd.hasJaxRpcImpl()) {
                    WebContainerDeploymentDesc desc = getWebDesc(file, sibLink, jarCL);
                    pcd.setDesc(desc);

                }
            }
        }

        return wsdd;
    }

    /**
     * Get the specified ws deployment descriptor and put it in the cache if it
     * is not in.
     * @param url module (ejbjar or war). It can be a directory or a jar file.
     * @param jarCL classloader used to load bean classes.
     * @param earCL the parent classloader (the ear classloader). Null when not
     *        in the case of an ear application.
     * @return the module webservices deployment descriptor if it exists, null
     *         else.
     * @throws WSDeploymentDescException when WSDeploymentDesc cannot be created
     *         with the given files.
     */
    public WSDeploymentDesc getDeploymentDesc(final URL url, final ClassLoader jarCL, final ClassLoader earCL)
            throws WSDeploymentDescException {
        return getDeploymentDesc(url, null, jarCL, earCL);
    }

    /**
     * Get the specified ws deployment descriptor and put it in the cache if it
     * is not in.
     * @param url module (ejbjar or war). It can be a directory or a jar file.
     * @param unpackedURL Unpacked URL of the module archive
     * @param jarCL classloader used to load bean classes.
     * @param earCL the parent classloader (the ear classloader). Null when not
     *        in the case of an ear application.
     * @return the module webservices deployment descriptor if it exists, null
     *         else.
     * @throws WSDeploymentDescException when WSDeploymentDesc cannot be created
     *         with the given files.
     */
    public WSDeploymentDesc getDeploymentDesc(final URL url, final URL unpackedURL, final ClassLoader jarCL,
            final ClassLoader earCL) throws WSDeploymentDescException {

        WSDeploymentDesc wsdd = null;
        File moduleFile = URLUtils.urlToFile(url);
        if (moduleFile.exists()) {
            // if the module has yet been loaded
            if (!urlWsddBindings.containsKey(url)) {
                wsdd = getDeploymentDescriptor(url, unpackedURL, jarCL, earCL);
            } else { // module already loaded
                // Can be null if no webservices.xml has been found in specified
                // module
                wsdd = urlWsddBindings.get(url);
            }
        } else {
            throw new WSDeploymentDescException("'" + moduleFile.getName() + "' doesn't exist");
        }

        // Store binding
        ClassLoader keyCL;
        if (earCL != null) {
            keyCL = earCL;
        } else {
            keyCL = jarCL;
        }
        List<URL> urls = classLoader2URLs.get(keyCL);
        if (urls == null) {
            urls = new Vector<URL>();
            classLoader2URLs.put(keyCL, urls);
        }
        urls.add(url);

        return wsdd;
    }

    /**
     * return the specified webservices deployment descriptor and put it in
     * cache.
     * @param url an existing module (ejbjar or war). It can be a directory or a
     *        jar file
     * @param unpackedURL Unpacked URL of the module archive
     * @param jarCL classloader used to load war or ejbjar classes.
     * @param earCL the parent classloader (the ear classloader). Null when not
     *        in the case of an ear application.
     * @return the module webservices deployment descriptor if it exists, null
     *         else.
     * @throws WSDeploymentDescException when WSDeploymentDesc cannot be created
     *         with the given files.
     */
    private WSDeploymentDesc getDeploymentDescriptor(final URL url, final URL unpackedURL, final ClassLoader jarCL,
            final ClassLoader earCL) throws WSDeploymentDescException {

        URL baseURL = url;
        if (unpackedURL != null) {
            baseURL = unpackedURL;
        }
        WSDeploymentDesc wsdd = loadDeploymentDesc(baseURL, jarCL);

        // add in cache the deployment descriptor and return it
        urlWsddBindings.put(url, wsdd);

        // No WSDeploymentDesc, return null
        if (wsdd == null) {
            return null;
        }

        // Resolve ejb-link and servlet-link elements
        List<ServiceDesc> sdl = wsdd.getServiceDescs();

        for (int i = 0; i < sdl.size(); i++) {
            List<PortComponentDesc> pcdl = sdl.get(i).getPortComponents();

            for (int j = 0; j < pcdl.size(); j++) {
                // Resolve the ejb-link and servlet-link of each port components
                PortComponentDesc pcd = pcdl.get(j);
                String sibLink = pcd.getSibLink();

                // a link is defined, we resolve it
                if (pcd.hasBeanImpl()) {
                    SessionStatelessDesc desc = getBeanDesc(url.getFile(), sibLink, jarCL, earCL);
                    pcd.setDesc(desc);

                } else if (pcd.hasJaxRpcImpl()) {
                    WebContainerDeploymentDesc desc = getWebDesc(url.getFile(), sibLink, jarCL, earCL);
                    pcd.setDesc(desc);

                }
            }
        }

        return wsdd;
    }

    /**
     * Load webservices.xml in the WSDeploymentDesc structure (null if
     * webservices.xml not found). Can take as parameter a directory (like an
     * unpacked jar root directory), a XML file name (path to
     * (META-INF|WEB-INF)/webservices.xml) or a packed jar.
     * @param url the url to load (can be a directory, xml, jar).
     * @param cl the classLoader to use for loading files.
     * @return WSDeploymentDesc object.
     * @throws WSDeploymentDescException when parsing error.
     */
    private static WSDeploymentDesc loadDeploymentDesc(final URL url, final ClassLoader cl) throws WSDeploymentDescException {

        URLClassLoader ucl = (URLClassLoader) cl;

        // get the Webservices object for this module
        InputStream isWebservices = null;

        // get the JonasWebservices object for this module
        InputStream isJonasWebservices = null;

        URL wsXml = null;
        URL jwsXml = null;

        try {

            boolean foundInEjb = false;
            for (Enumeration<URL> ejbWebservices = ucl.findResources("META-INF/webservices.xml"); ejbWebservices
                    .hasMoreElements()
                    && !foundInEjb;) {
                wsXml = ejbWebservices.nextElement();
                if (isResourceInFile(url, wsXml)) {
                    foundInEjb = true;
                }
            }

            // if DD found in Ejb
            if (foundInEjb) {
                isWebservices = openInputStream(wsXml);

                foundInEjb = false;
                // try load jonas DD
                for (Enumeration<URL> ejbJonasWebservices = ucl.findResources("META-INF/jonas-webservices.xml"); ejbJonasWebservices
                        .hasMoreElements()
                        && !foundInEjb;) {
                    jwsXml = ejbJonasWebservices.nextElement();
                    if (isResourceInFile(url, jwsXml)) {
                        foundInEjb = true;
                    }
                }
                if (foundInEjb) {
                    isJonasWebservices = openInputStream(jwsXml);

                }
            }

            boolean foundInWeb = false;
            for (Enumeration<URL> webWebservices = ucl.findResources("WEB-INF/webservices.xml"); webWebservices
                    .hasMoreElements()
                    && !foundInWeb;) {
                wsXml = webWebservices.nextElement();
                if (isResourceInFile(url, wsXml)) {
                    foundInWeb = true;
                }
            }

            // if DD found in Web
            if (foundInWeb) {
                isWebservices = openInputStream(wsXml);

                foundInWeb = false;
                // try load jonas DD
                for (Enumeration<URL> webJonasWebservices = ucl.findResources("WEB-INF/jonas-webservices.xml"); webJonasWebservices
                        .hasMoreElements()
                        && !foundInWeb;) {
                    jwsXml = webJonasWebservices.nextElement();
                    if (isResourceInFile(url, jwsXml)) {
                        foundInWeb = true;
                    }
                }
                if (foundInWeb) {
                    isJonasWebservices = openInputStream(jwsXml);
                }
            }
        } catch (IOException ioe) {
            String err = "Cannot open Streams on WebServices Deployment Descriptor for '" + url + "'";
            throw new WSDeploymentDescException(err, ioe);
        }

        // webservices.xml not found in module, return null
        if (isWebservices == null) {
            return null;
        }

        // Load and Parse files
        Webservices webservices = loadWebservices(new InputStreamReader(isWebservices), wsXml.getFile());
        JonasWebservices jonasWebservices = null;

        if (isJonasWebservices != null) {
            jonasWebservices = loadJonasWebservices(new InputStreamReader(isJonasWebservices), jwsXml.getFile());
        }

        try {
            if (isWebservices != null) {
                isWebservices.close();
            }
            if (isJonasWebservices != null) {
                isJonasWebservices.close();
            }
        } catch (IOException ioe) {
            // nothing to do, just log a warning
            String err = "Cannot close InputStreams for '" + url + "'";
            logger.log(BasicLevel.WARN, err);
        }

        // Create DeploymentDesc only if version is supported
        if ("1.1".equals(webservices.getVersion())) {
            return new WSDeploymentDesc(cl, logger, webservices, jonasWebservices);
        }

        return null;

    }

    /**
     * @param xml URL
     * @return InputStream of the URL Content
     * @throws IOException If stream cannot be opened.
     */
    private static InputStream openInputStream(final URL xml) throws IOException {
        URLConnection conn = xml.openConnection();
        conn.setUseCaches(false);
        conn.setDefaultUseCaches(false);
        InputStream is = conn.getInputStream();

        return is;
    }

    /**
     * Returns true if the URL resource is located in the specified file.
     * @param file file to search in
     * @param resource resource name to search
     * @return true if the URL resource is located in the specified file.
     */
    private static boolean isResourceInFile(final URL file, final URL resource) {

        boolean within = false;

        File f = new File(file.getFile());
        if (f.isDirectory()) {
            // test directory
            // resource need to be of the form :
            // <resource> = <file>/META-INF/webservices.xml for example
            if (resource.toString().startsWith(file.toString())) {
                within = true;
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Directory case. found '" + resource + "' in '" + file + "'");
                }
            }
        } else if (f.isFile()) {
            if (f.getPath().endsWith(".xml")) {
                // test XML
                // resource need to be of the form :
                // <file> = <resource>
                if (file.equals(resource)) {
                    within = true;
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "XML case. found '" + resource + "' in '" + file + "'");
                    }
                }
            } else if ((f.getPath().endsWith(".war")) || (f.getPath().endsWith(".jar"))) {
                // test JarFile
                // resource need to be of the form :
                // <resource> = jar:<file>!/META-INF/webservices.xml for example
                int index = resource.toString().indexOf("!/");
                if (index != -1) {
                    if (resource.toString().startsWith(("jar:" + file.toString()))) {
                        within = true;
                        if (logger.isLoggable(BasicLevel.DEBUG)) {
                            logger.log(BasicLevel.DEBUG, "Archive case. found '" + resource + "' in '" + file + "'");
                        }
                    }
                }
            }
        }
        return within;
    }

    /**
     * return a 'xml' object containing webservices.xml informations.
     * @param reader webservices.xml file input stream reader
     * @param fileName webservices.xml file name
     * @return a 'xml' object containing webservices.xml informations
     * @throws WSDeploymentDescException when Webservices cannot be created with
     *         the given files.
     */
    public static Webservices loadWebservices(final Reader reader, final String fileName) throws WSDeploymentDescException {

        Webservices ws = new Webservices();

        // Create if wsDigester is null
        if (wsDigester == null) {
            try {
                // Create and initialize the digester
                wsDigester = new JDigester(wsRuleSet, getParsingWithValidation(), true, null, new WsSchemas());
            } catch (DeploymentDescException e) {
                throw new WSDeploymentDescException(e);
            }
        }

        try {
            wsDigester.parse(reader, fileName, ws);
        } catch (DeploymentDescException e) {
            throw new WSDeploymentDescException(e);
        } finally {
            wsDigester.push(null);
        }

        return ws;
    }

    /**
     * return a 'xml' object containing jonas-webservices.xml informations.
     * @param reader jonas-webservices.xml file input stream reader
     * @param fileName jonas-webservices.xml file name
     * @return a 'xml' object containing jonas-webservices.xml informations
     * @throws WSDeploymentDescException when JonasWebservices cannot be created
     *         with the given files.
     */
    public static JonasWebservices loadJonasWebservices(final Reader reader, final String fileName)
            throws WSDeploymentDescException {

        JonasWebservices jws = new JonasWebservices();

        // Create if wsDigester is null
        if (jwsDigester == null) {
            try {
                // Create and initialize the digester
                jwsDigester = new JDigester(jwsRuleSet, getParsingWithValidation(), true, null, new JonasWsSchemas());
            } catch (DeploymentDescException e) {
                throw new WSDeploymentDescException(e);
            }
        }

        try {
            jwsDigester.parse(reader, fileName, jws);
        } catch (DeploymentDescException e) {
            throw new WSDeploymentDescException(e);
        } finally {
            jwsDigester.push(null);
        }

        return jws;

    }

    /**
     * Get the size of the cache (number of entries in the cache). Used only for
     * debugging.
     * @return the size of the cache (number of entries in the cache).
     */
    public int getCacheSize() {
        return urlWsddBindings.size();

    }

    /**
     * Clear the cache.
     */
    public void clearCache() {
        urlWsddBindings = new HashMap<URL, WSDeploymentDesc>();
    }

    /**
     * Remove the DD cache for the specified ClassLoader.
     * @param cl the Key ClassLoader
     */
    public void removeCache(final ClassLoader cl) {
        List<URL> urls = classLoader2URLs.remove(cl);
        if (urls != null) {
            for (URL url : urls) {
                urlWsddBindings.remove(url);
            }
        }
    }

    /**
     * Return a string representation of the cache. (Used only for debugging).
     * @return a string representation of the cache.
     */
    @Override
    public String toString() {
        return "" + getCacheSize();

    }

    /**
     * Return a StatelessSessionDesc found in filename with the name link. Used
     * by WsGen only. (No JOnAS running).
     * @param filename the ejb-jar file to load.
     * @param link the ejb-name to retrieve.
     * @param cl the classLoader to use.
     * @return he StatelessSessionDesc associated with this port component
     * @throws WSDeploymentDescException if ejb-link not found or something went
     *         wrong when parsing ejb deployment desc.
     */
    private static SessionStatelessDesc getBeanDesc(final String filename, final String link, final ClassLoader cl)
            throws WSDeploymentDescException {

        BeanDesc bd = null;

        try {
            // Note : Because we are in the static getDeploymentDesc
            // we have the right (accordingly to classloader hierarchie) to use
            // real EjbJar Manager
            // ---------------------------------------------------
            // Do not add import on EjbDeploymentDescManager !!!!
            DeploymentDesc dd = org.ow2.jonas.deployment.ejb.lib.EjbDeploymentDescManager.getDeploymentDesc(filename, cl);
            bd = dd.getBeanDesc(link);

        } catch (DeploymentDescException dde) {
            throw new WSDeploymentDescException("DeploymentDescException when searching '" + link + "' in '" + filename + "'",
                    dde);
        }

        if (bd == null) {
            throw new WSDeploymentDescException("Unable to find the ejb-link '" + link + "' in '" + filename + "'");
        }

        if (!(bd instanceof SessionStatelessDesc)) {
            throw new WSDeploymentDescException("ejb-link '" + link + "' must be a Stateless Session Bean not a '"
                    + bd.getClass().getName() + "'");
        }

        return (SessionStatelessDesc) bd;

    }

    /**
     * Return a StatelessSessionDesc found in filename with the name link. Used
     * when JOnAS is running.
     * @param filename the ejb-jar file to load.
     * @param link the ejb-name to retrieve.
     * @param jarCL the loader for classes.
     * @param earCL the EAR classLoader .
     * @return the StatelessSessionDesc associated with this port component
     * @throws WSDeploymentDescException if ejb-link not found or something went
     *         wrong when parsing ejb deployment desc.
     */
    private SessionStatelessDesc getBeanDesc(final String filename, final String link, final ClassLoader jarCL,
            final ClassLoader earCL) throws WSDeploymentDescException {
        URL url;

        try {
            url = new File(filename).toURL();

        } catch (MalformedURLException mue) {
            throw new WSDeploymentDescException("Url Error with '" + filename + "'", mue);
        }

        BeanDesc bd = null;

        try {
            DeploymentDesc dd = ejbManager.getDeploymentDesc(url, jarCL, earCL);
            bd = dd.getBeanDesc(link);

        } catch (DeploymentDescException dde) {
            throw new WSDeploymentDescException("Unable to load EjbJar '" + url + "'", dde);
        }

        // Ejb Link not found
        if (bd == null) {
            throw new WSDeploymentDescException("Unable to find the ejb-link '" + link + "' in '" + filename + "'");
        }

        if (!(bd instanceof SessionStatelessDesc)) {
            throw new WSDeploymentDescException("ejb-link '" + link + "' must be a Stateless Session Bean");
        }

        return (SessionStatelessDesc) bd;

    }

    /**
     * Return the Web Deployment Desc found in filename with the name link.
     * @param filename the war file to load.
     * @param link the servlet-name to retrieve.
     * @param cl the classLoader to use.
     * @return the Web Deployment Desc associated with this port component
     * @throws WSDeploymentDescException if servlet-link not found or something
     *         went wrong when parsing web deployment desc.
     */
    private static WebContainerDeploymentDesc getWebDesc(final String filename, final String link, final ClassLoader cl)
            throws WSDeploymentDescException {

        WebContainerDeploymentDesc wd = null;

        try {
            // Note : Because we are in the static getDeploymentDesc
            // we have the right (accordingly to classloader hierarchie) to use
            // real Web Manager
            // ---------------------------------------------------
            // Do not add import on WebDeploymentDescManager !!!!
            wd = org.ow2.jonas.deployment.web.lib.WebDeploymentDescManager.getDeploymentDesc(filename, cl);
        } catch (WebContainerDeploymentDescException wcdde) {
            throw new WSDeploymentDescException("Error while reading/parsing " + filename, wcdde);
        }

        // link not found in this deployment desc
        if (wd.getServletClassname(link) == null) {
            throw new WSDeploymentDescException("Unable to find the servlet-link '" + link + "' in '" + filename + "'");
        }

        return wd;

    }

    /**
     * Return the Web Deployment Desc found in filename with the name link. Used
     * when JOnAS is up and running.
     * @param filename the war file to load.
     * @param link the servlet-name to retrieve.
     * @param jarCL the classLoader to us.
     * @param earCL the EAR classLoader.
     * @return the Web Deployment Desc associated with this port component
     * @throws WSDeploymentDescException if servlet-link not found or something
     *         went wrong when parsing web deployment desc.
     */
    private WebContainerDeploymentDesc getWebDesc(final String filename, final String link, final ClassLoader jarCL,
            final ClassLoader earCL) throws WSDeploymentDescException {

        URL url;

        try {
            url = new File(filename).toURL();
        } catch (MalformedURLException mue) {
            throw new WSDeploymentDescException("Url Error with '" + filename + "'", mue);
        }

        WebContainerDeploymentDesc wd = null;

        try {
            wd = webManager.getDeploymentDesc(url, jarCL, earCL);
        } catch (DeploymentDescException wcdde) {
            throw new WSDeploymentDescException("Error while reading/parsing " + filename, wcdde);
        }

        // link not found in this deployment desc
        if (wd.getServletClassname(link) == null) {
            throw new WSDeploymentDescException("Unable to find the servlet-link '" + link + "' in '" + filename + "'");
        }

        return wd;

    }

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

    /**
     * Return the port component desc from the pcLink string. pcLink format :
     * filename.[jar or war]#portComponentName in the same Ear File
     * @param callerURL the url of the module being parsed. This is needed
     *        because pcLink is relative. With the url and the pcLink, we can
     *        know where the file is locate.
     * @param portComponentLinkName 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
     */
    public PortComponentDesc getPortComponentDesc(final URL callerURL, final String portComponentLinkName,
            final ClassLoader moduleLoader, final ClassLoader earLoader) throws WSDeploymentDescException {

        // Extract from the pc link
        // - the name of the file
        // - the name of the bean
        String moduleLink = null;
        String pcNameLink = null;
        URL moduleLinkUrl = null;
        URLClassLoader loaderForCls = null;

        // port-component-link can use directly the portComponentName without #
        // See Bug #300592
        if (!portComponentLinkName.matches(".*#.*")) {
            // no xxx#yyy
            // we just have the port link
            pcNameLink = portComponentLinkName;

            moduleLinkUrl = callerURL;
            loaderForCls = (URLClassLoader) moduleLoader;
        } else {
            // xxx#yyy

            // Check the format of the pc-link. It must contains .jar# or .war#
            if ((portComponentLinkName.toLowerCase().indexOf(".war" + AbsDeploymentDescManager.LINK_SEPARATOR) == -1)
                    && (portComponentLinkName.toLowerCase().indexOf(".jar" + AbsDeploymentDescManager.LINK_SEPARATOR) == -1)) {
                // the pc link is not in war or jar file
                String err = "PC-link "
                        + portComponentLinkName
                        + " has a bad format. Correct format : filename.[jar or war]#portComponentName or just portComponentName";
                throw new WSDeploymentDescException(err);
            }
            StringTokenizer st = new StringTokenizer(portComponentLinkName, LINK_SEPARATOR);

            // We must have only two elements after this step, one for the
            // fileName
            // before the # and the name of the bean after the # char
            if (st.countTokens() != 2 || portComponentLinkName.startsWith(AbsDeploymentDescManager.LINK_SEPARATOR)
                    || portComponentLinkName.endsWith(AbsDeploymentDescManager.LINK_SEPARATOR)) {
                throw new WSDeploymentDescException("PC-link " + portComponentLinkName + " has a bad format. "
                        + "Correct format : filename.[jar or war]#portComponentName or just portComponentName");
            }

            // Get the token
            moduleLink = st.nextToken();
            pcNameLink = st.nextToken();

            // Now construct the URL from the absolute path from the url module
            // and
            // the relative path from moduleJarLink
            try {
                moduleLinkUrl = new File(new File(callerURL.getFile()).getParent() + File.separator + moduleLink).toURL();
            } catch (MalformedURLException mue) {
                String err = "Error when creating an url for the module filename. Error :" + mue.getMessage();
                throw new WSDeploymentDescException(err, mue);
            } catch (IOException ioe) {
                String err = "Error when creating/accessing a file. Error :" + ioe.getMessage();
                throw new WSDeploymentDescException(err, ioe);
            }

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

            URL[] ddURL = new URL[1];
            ddURL[0] = moduleLinkUrl;
            loaderForCls = new URLClassLoader(ddURL, earLoader);

        }

        // We've got the url
        // Now, We can ask the Deployment Descriptor of this url
        WSDeploymentDesc wsDD = null;

        try {
            wsDD = getDeploymentDesc(moduleLinkUrl, loaderForCls, earLoader);
        } catch (DeploymentDescException e) {
            String err = "Cannot get the deployment descriptor for '" + moduleLinkUrl.getFile() + "'.";
            throw new WSDeploymentDescException(err, e);
        }
        if (wsDD == null) {
            // the module doesn't contain port components.
            String err = "Port component link " + pcNameLink + " not found in " + moduleLinkUrl.getFile();
            throw new WSDeploymentDescException(err);
        }

        // get port component desc //pcNameLink
        List<ServiceDesc> sdl = wsDD.getServiceDescs();
        boolean isFound = false;
        PortComponentDesc pcd = null;
        for (int i = 0; (i < sdl.size()) && !isFound; i++) {
            if (sdl.get(i) != null) {
                pcd = sdl.get(i).getPortComponent(pcNameLink);
                isFound = (pcd != null);
                // we stop when we have found the good portComponent
            }
        }
        if (!isFound) {
            // the module doesn't contain port components.
            String err = "the port component link " + pcNameLink + " doesn't exist in " + moduleLinkUrl.getFile();
            throw new WSDeploymentDescException(err);
        }

        return pcd;
    }
}
