/**
 * 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: JAXRPCService.java 19368 2010-03-05 16:39:18Z alitokmen $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.ws.jaxrpc.base;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.xml.namespace.QName;

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.HandlerDesc;
import org.ow2.jonas.deployment.web.WebContainerDeploymentDesc;
import org.ow2.jonas.deployment.web.WebContainerDeploymentDescException;
import org.ow2.jonas.deployment.web.wrapper.WebManagerWrapper;
import org.ow2.jonas.deployment.ws.JaxRpcPortComponentDesc;
import org.ow2.jonas.deployment.ws.PortComponentDesc;
import org.ow2.jonas.deployment.ws.SSBPortComponentDesc;
import org.ow2.jonas.deployment.ws.ServiceDesc;
import org.ow2.jonas.deployment.ws.WSDLFile;
import org.ow2.jonas.deployment.ws.WSDeploymentDesc;
import org.ow2.jonas.deployment.ws.WSDeploymentDescException;
import org.ow2.jonas.deployment.ws.wrapper.WSManagerWrapper;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.loader.WebappClassLoader;
import org.ow2.jonas.lib.naming.ComponentContext;
import org.ow2.jonas.lib.naming.URLFactory;
import org.ow2.jonas.lib.service.AbsServiceImpl;
import org.ow2.jonas.lib.util.I18n;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.naming.JNamingManager;
import org.ow2.jonas.service.ServiceException;
import org.ow2.jonas.web.JWebContainerService;
import org.ow2.jonas.web.JWebContainerServiceException;
import org.ow2.jonas.ws.jaxrpc.IJAXRPCService;
import org.ow2.jonas.ws.jaxrpc.WSException;
import org.ow2.jonas.ws.jaxrpc.factory.JServiceFactory;
import org.ow2.jonas.ws.jaxrpc.mbean.Handler;
import org.ow2.jonas.ws.jaxrpc.mbean.PortComponent;
import org.ow2.jonas.ws.jaxrpc.mbean.Service;
import org.ow2.jonas.ws.jaxrpc.mbean.WebServicesObjectName;
import org.ow2.jonas.ws.publish.PublicationInfo;
import org.ow2.jonas.ws.publish.PublishableDefinition;
import org.ow2.jonas.ws.publish.WSDLPublisherException;
import org.ow2.jonas.ws.publish.WSDLPublisherManager;
import org.ow2.util.ee.deploy.api.deployable.EARDeployable;
import org.ow2.util.url.URLUtils;

/**
 * implements deployment process and declares commons abstract methods for all
 * (used) Web services engine. Actually we consider Glue and AXIS.
 * @author Guillaume Sauthier
 * @author Xavier Delplanque
 * @author S. Ali Tokmen (URL escaping issues when folders contain spaces)
 */
public abstract class JAXRPCService extends AbsServiceImpl implements IJAXRPCService, JAXRPCServiceMBean {

    /**
     * Logger for this service.
     */
    private static final Logger logger = Log.getLogger("org.ow2.jonas.ws");

    /**
     * Internationalization tool.
     */
    private static final I18n i18n = I18n.getInstance(JAXRPCService.class);

    /**
     * Manager for WSDL files publishing.
     */
    private WSDLPublisherManager wsdlManager = null;

    /**
     * Reference to the web container service.
     */
    private JWebContainerService webService = null;

    /**
     * JMX Service reference.
     */
    private JmxService jmxService = null;

    /**
     * Map of deployments descriptor ( {@link ClassLoader} -> {@link Stack}).
     */
    private Map deployments = null;

    /**
     * Map of warURL -> WebServices.
     */
    private Map webservicesMBeans = null;

    /**
     * endpoint URL prefix to be used for ws endpoint.
     */
    private String endpointURLPrefix = null;

    /**
     * Auto WSGen ?
     */
    private boolean autoWSGen = true;

    /**
     * Naming Manager for URL binding.
     */
    private JNamingManager naming;

    /**
     * Validate parser or not ?
     */
    private boolean parsingWithValidation;

    /**
     * Init local fields at the very beginning.
     */
    public JAXRPCService() {
        // Init variables
        deployments = new HashMap();
        webservicesMBeans = new HashMap();
    }

    /**
     * Return JServiceFactory instance
     * @return  JServiceFactory instance
     */
    abstract public JServiceFactory createServiceFactory();

    /**
     * @param prefix URL prefixing all generated endpoint URL.
     */
    public void setUrlPrefix(final String prefix) {
        this.endpointURLPrefix = prefix;
    }

    /**
     * @param validate validating parser or not ?
     */
    public void setParsingWithValidation(final boolean validate) {
        this.parsingWithValidation = validate;
        WSManagerWrapper.setParsingWithValidation(validate);

        if (!validate) {
            logger.log(BasicLevel.DEBUG, "WebServices XML parsing without validation");
        } else {
            logger.log(BasicLevel.DEBUG, "WebServices XML parsing with validation");
        }

    }

    /**
     * @param engage Engage Automatic WsGen ?
     */
    public void setAutoWsGenEngaged(final boolean engage) {
        this.autoWSGen = engage;
    }

    /**
     * Get, update and publish WSDL documents contained in a set of jars and
     * wars.
     * @param ctx the context containing the configuration to deploy the wars.
     *        <BR>This context contains the following parameters :<BR>-
     *        jarUrls the list of the urls of the jars to deploy. <BR>- warUrls
     *        the list of the urls of the wars to deploy. <BR>- ejbClassLoader
     *        the classLoader of the ejbs(used for webapps too). <BR>-
     *        earClassLoader the ear classLoader of the j2ee app. <BR>-
     *        warCtxRootMapping the webapps contextRoots with associated war
     *        name. <BR>- unpackDir the directory where EAR has been unpacked
     *        (optionnal for pure webapp webservices).
     * @throws WSException if an error occurs during the deployment.
     */
    public void deployWebServices(final Context ctx) throws WSException {
        /**
         * For each Jar/WarUrls in the given ctx : - Get the jar/war deployment
         * desc - Get the WSDL definition - Update the WSDL location - Publish
         * the WSDL
         */

        // Get context parameters.
        URL[] jarUrls = null;
        URL[] warUrls = null;
        URL earURL = null;
        EARDeployable earDeployable = null;
        ClassLoader ejbClassLoader = null;
        ClassLoader earClassLoader = null;
        Map warCtxRootMapping = null;
        File unpackDirectory = null;

        // EAR & Webapp case common information
        try {
            jarUrls = (URL[]) ctx.lookup("jarUrls");
            warUrls = (URL[]) ctx.lookup("warUrls");
            unpackDirectory = (File) ctx.lookup(UNPACK_DIRECTORY_CTX_PARAM);
        } catch (NamingException e) {
            String err = i18n.getMessage("AbsWebServicesServiceImpl.deployWebServices.ctxParamProblem");
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new WSException(err, e);
        }

        // EAR case specific information
        try {
            earURL = (URL) ctx.lookup("earURL");
            earDeployable = (EARDeployable) ctx.lookup("earDeployable");
            earClassLoader = (ClassLoader) ctx.lookup("earClassLoader");
            warCtxRootMapping = (Map) ctx.lookup("warCtxRootMapping");
            ejbClassLoader = (ClassLoader) ctx.lookup("ejbClassLoader");
        } catch (NamingException ne) {
            // WebApp case
            earURL = null;
            earClassLoader = null;
            warCtxRootMapping = null;
            ejbClassLoader = null;
        }

        // Deploy wars Web services.
        URLClassLoader loaderForCls = null;
        String fileName = null;
        WSDeploymentDesc wsDD = null;

        for (int i = 0; i < warUrls.length; i++) {
            // Get the name of a war to deploy.
            fileName = URLUtils.urlToFile(warUrls[i]).getPath();
            logger.log(BasicLevel.DEBUG, "Analyzing war '" + fileName + "' for web services");

            // Get the class loader for the current war.
            loaderForCls = webService.getClassLoader(warUrls[i], earDeployable, ejbClassLoader);

            WebappClassLoader webLoader = (WebappClassLoader) loaderForCls;
            URL url = webLoader.getBaseURL();

            // Get the deployment descriptor from file.
            try {
                wsDD = WSManagerWrapper.getDeploymentDesc(warUrls[i], url, loaderForCls, earClassLoader);
            } catch (WSDeploymentDescException wsdde) {
                String err = i18n.getMessage("AbsWebServicesServiceImpl.deployWebServices.wsddEx", fileName);
                logger.log(BasicLevel.ERROR, err + ": " + wsdde);
                throw new WSException(err, wsdde);
            }

            if (wsDD != null) {
                // The current War contains Webservices
                try {
                    Context compCtx = new ComponentContext(fileName);
                    compCtx.rebind("wsDD", wsDD);
                    compCtx.rebind("cl", loaderForCls);

                    if (earClassLoader != null) {
                        compCtx.rebind("earCL", earClassLoader);
                    }
                    compCtx.rebind("warURL", warUrls[i]);

                    // ear case
                    if (warCtxRootMapping != null) {
                        compCtx.rebind("warContextRoot", warCtxRootMapping.get(warUrls[i]));

                        // non-ear case
                    } else {
                        compCtx.rebind("warContextRoot", "");
                    }

                    doDeployWebServices(compCtx);
                } catch (NamingException ne) {
                    String err = i18n.getMessage("AbsWebServicesServiceImpl.deployWebServices.bindError", fileName);
                    logger.log(BasicLevel.ERROR, err);
                    throw new WSException(err, ne);
                }
            }
        }
        // End deploy wars Web services.

        // Deploy jars Web services.
        // Check warCtxRootMapping presence
        if ((jarUrls.length != 0) && (warCtxRootMapping == null)) {
            String err = i18n.getMessage("AbsWebServicesServiceImpl.deployWebServices.ctxRootMappingMissing");
            logger.log(BasicLevel.ERROR, err);
            throw new WSException(err);
        }

        for (int i = 0; i < jarUrls.length; i++) {
            // Get the name of a war to deploy.
            fileName = URLUtils.urlToFile(jarUrls[i]).getPath();
            logger.log(BasicLevel.DEBUG, "Analyzing EjbJar '" + fileName + "' for web services");

            URLClassLoader webClassLoader = null;

            // Get the deployment descriptor from file.
            try {
                wsDD = WSManagerWrapper.getDeploymentDesc(jarUrls[i], ejbClassLoader, earClassLoader);
            } catch (WSDeploymentDescException wsdde) {
                String err = i18n.getMessage("AbsWebServicesServiceImpl.deployWebServices.wsddEx", fileName);
                logger.log(BasicLevel.ERROR, err + ": " + wsdde);
                throw new WSException(err, wsdde);
            }

            if (wsDD != null) {
                // The current EjbJar contains Webservices
                URL warURL = null;

                /**
                 * Default Naming Convention for Webapp linked to Ejb
                 * <ejb-name>.jar -> <ejb-name>.war
                 */
                File targetWebAppFile;
                if (wsDD.getWarFile() != null) {
                    targetWebAppFile = new File(unpackDirectory, wsDD.getWarFile());
                } else {
                    String ejb = new File(jarUrls[i].getFile()).getName();
                    String war = ejb.substring(0, ejb.length() - ".jar".length());

                    targetWebAppFile = new File(unpackDirectory, war.concat(".war"));
                }
                // Get an escaped URL from the File
                warURL = URLUtils.fileToURL(targetWebAppFile);

                logger.log(BasicLevel.DEBUG, "Unpack Dir : " + unpackDirectory);
                logger.log(BasicLevel.DEBUG, "Computed URL : " + warURL);

                // check if webapp is present
                boolean present = false;

                for (int w = 0; w < warUrls.length; w++) {
                    if (warUrls[w].equals(warURL)) {
                        present = true;
                    }
                    logger.log(BasicLevel.DEBUG, "warUrls[" + w + "]=" + warUrls[w]);
                }

                // get the linked web classloader
                webClassLoader = webService.getClassLoader(warURL, earDeployable, ejbClassLoader);

                if (!present) {
                    String err = i18n.getMessage("AbsWebServicesServiceImpl.deployWebServices.webappNotFound",
                                                 warURL);
                    logger.log(BasicLevel.ERROR, err);
                    throw new WSException(err);
                }

                try {
                    Context compCtx = new ComponentContext(fileName);
                    compCtx.rebind("wsDD", wsDD);

                    // Using WebClassLoader ()
                    compCtx.rebind("cl", webClassLoader);
                    compCtx.rebind("earCL", earClassLoader);

                    compCtx.rebind("warURL", warURL);

                    // warContextRoot cannot be null in ejb case
                    // because an ejb needs a webapp for webservices
                    Object warContextRoot = warCtxRootMapping.get(warURL);
                    if (warContextRoot == null) {
                        // TODO remove that, mapping should only contains escaped URL
                        // URL escaping issue ?
                        warContextRoot = warCtxRootMapping.get(URLUtils.fileToURL(new File(warURL.getFile())));
                    }
                    compCtx.rebind("warContextRoot", warContextRoot);
                    doDeployWebServices(compCtx);
                } catch (NamingException ne) {
                    String err = i18n.getMessage("AbsWebServicesServiceImpl.deployWebServices.bindError", fileName);
                    logger.log(BasicLevel.ERROR, err);
                    throw new WSException(err, ne);
                }
            }
        }

        // End deploy jars Web services.
    }

    /**
     * Deploy the given Web services.
     * @param ctx Context used for parameter passing.
     * @throws WSException when error occurs.
     */
    protected void doDeployWebServices(final Context ctx) throws WSException {
        /**
         * Context structure : - wsDD Web Services deployment descriptor. - cl
         * Module ClassLoader - earCL Application ClassLoader - warContextRoot
         * the context-root of the war (null in ejb case) - warURL the url of
         * war dispatching SOAP requests
         */
        WSDeploymentDesc wsDD = null;
        ClassLoader cl = null;
        ClassLoader earCL = null;
        String warContextRoot = null;
        URL warURL = null;

        try {
            wsDD = (WSDeploymentDesc) ctx.lookup("wsDD");
            cl = (ClassLoader) ctx.lookup("cl");
            warContextRoot = (String) ctx.lookup("warContextRoot");
            warURL = (URL) ctx.lookup("warURL");
        } catch (NamingException ne) {
            String err = i18n.getMessage("AbsWebServicesServiceImpl.doDeployWebServices.namingError");
            logger.log(BasicLevel.ERROR, err);
            throw new WSException(err, ne);
        }

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Deploying WebServices for '" + warURL + "'");
        }

        try {
            earCL = (ClassLoader) ctx.lookup("earCL");
        } catch (NamingException ne) {
            // Nothing to do : non EAR case
            earCL = null;
        }

        // store WSDeployInfo for later use (completeWSDeployment method)
        ClassLoader key = cl;
        if (earCL != null) {
            key = earCL;
        }
        Stack s = (Stack) deployments.get(key);
        if (s == null) {
            s = new Stack();
            deployments.put(key, s);
        }
        s.push(new WSDeployInfo(warURL, wsDD));


        List sds = wsDD.getServiceDescs();

        // for each WSDL contained in the component
        for (Iterator i = sds.iterator(); i.hasNext();) {
            ServiceDesc sd = (ServiceDesc) i.next();
            WSDLFile wsdl = sd.getWSDL();

            // get the endpoint URL for each port-component
            // update the WSDL Definition
            for (Iterator pc = sd.getPortComponents().iterator(); pc.hasNext();) {
                PortComponentDesc pcd = (PortComponentDesc) pc.next();
                QName portQName = pcd.getQName();
                URL endpoint = null;
                if (pcd.hasJaxRpcImpl()) {
                    endpoint = getEndpointURL(wsDD, (JaxRpcPortComponentDesc) pcd, warURL, warContextRoot);
                } else {
                    endpoint = getEndpointURL(wsDD, (SSBPortComponentDesc) pcd, warURL, cl, earCL, warContextRoot);
                }

                pcd.setEndpointURL(endpoint);
                wsdl.setLocation(portQName, endpoint);

                // add endpoint URL into JNDI namespace to allow application
                // clients (that are not running in the same JVM) to use
                // port-component-link
                final ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
                try {
                    Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());

                    Context registry = new InitialContext();
                    Reference urlRef = new Reference(URL.class.getName(), URLFactory.class.getName(), null);
                    urlRef.add(new StringRefAddr("url", endpoint.toString()));
                    registry.rebind(pcd.getName(), urlRef);
                    logger.log(BasicLevel.DEBUG, "Bind updated URL (" + endpoint + ") in " + pcd.getName());
                } catch (NamingException ne) {
                    throw new WSException("Cannot bind updated URL for port-component '" + pcd.getName()
                            + "'", ne);
                } finally {
                    Thread.currentThread().setContextClassLoader(oldCL);
                }

            }

            // publish WSDL
            try {
                // Create publication infos
                PublicationInfo info = new PublicationInfo();
                info.setOriginalWsdlFilename(sd.getWSDL().getName());

                // Handle case where there is no publication directory set
                File directory = sd.getPublicationDirectory();
                if (directory == null) {
                    // create a relative File using webservice-description/name
                    directory = new File(sd.getName());
                }
                info.setPublicationDirectory(directory);

                PublishableDefinition publishableDefinition = new PublishableDefinition();
                publishableDefinition.setDefinition(sd.getWSDL().getDefinition());
                publishableDefinition.setPublicationInfo(info);

                // And finally attempt to publish the WSDL
                wsdlManager.publish(publishableDefinition);
            } catch (WSDLPublisherException e) {
                throw new WSException("Cannot publish the WSDL for webservice-description '"
                                             + sd.getName() + "'", e);
            }
        }
    }

    /**
     * Creates and adds all nested Handlers in the parent PortComponent.
     * @param pcMBean parent PortComponent MBean
     * @param pcd PortComponent Descriptor
     */
    private void addHandlerMBeansToPortComponent(final PortComponent pcMBean, final PortComponentDesc pcd) {

        // iterates over the HandlerDesc list
        for (Iterator i = pcd.getHandlers().iterator(); i.hasNext();) {
            HandlerDesc hd = (HandlerDesc) i.next();

            // creates the Handler MBean, and automatically
            // add it to the PortComponent
            Handler hMBean = createHandlerMBean(hd.getName(), pcMBean);

            hMBean.setClassname(hd.getHandlerClassName());
            hMBean.setName(hd.getName());
            // soap-headers
            List sh = hd.getSOAPHeaders();
            // transform QName to String
            String[] headers = new String[sh.size()];
            int index = 0;
            for (Iterator j = sh.iterator(); j.hasNext();) {
                // Stringify the QName
                headers[index++] = ((QName) j.next()).toString();
            }
            hMBean.setSoapHeaders(headers);
            // soap-roles
            List sr = hd.getSOAPRoles();
            hMBean.setSoapRoles((String[]) sr.toArray(new String[sr.size()]));

            hMBean.setInitParams(hd.getInitParams());

        }

    }

    /**
     * @param name Handler name
     * @param parent parent PortComponent
     * @return Returns the creates Handler MBean
     */
    private static Handler createHandlerMBean(final String name, final PortComponent parent) {
        Handler h = null;
        try {
            h = new Handler(WebServicesObjectName.handler(name, parent.getRealObjectName()).toString());
        } catch (MalformedObjectNameException e) {
            // should never happen
            logger.log(BasicLevel.DEBUG, "Should never happen", e);
        }
        // add to the parent
        parent.addHandlerMBean(h);
        return h;
    }

    /**
     * @param name PortComponent name
     * @param parent parent Service
     * @return Returns the created PortComponent MBean
     */
    private static PortComponent createPortComponentMBean(final String name, final Service parent) {
        PortComponent pc = null;
        try {
            pc = new PortComponent(WebServicesObjectName.portComponent(name, parent.getRealObjectName()).toString());
        } catch (MalformedObjectNameException e) {
            // should never happen
            logger.log(BasicLevel.DEBUG, "Should never happen", e);
        }
        return pc;
    }

    /**
     * @param name Service name
     * @param parent parent WebServices
     * @return Returns the created Service MBean
     */
    private static Service createServiceMBean(final String name, final ObjectName parent) {
        Service service = null;
        try {
            service = new Service(WebServicesObjectName.service(name, parent).toString());
        } catch (MalformedObjectNameException e) {
            // should never happen
            logger.log(BasicLevel.DEBUG, "Should never happen", e);
        }
        return service;
    }

    /**
     * Creates the endpointURL of a given PortComponentDesc.
     * @param wsDD webservices.xml
     * @param jpcd JaxRpcPortComponentDesc to analyze
     * @param warURL URL of the war file containing the PortComponent
     * @param contextRoot context-root of the webapp
     * @return the endpointURL of a given PortComponentDesc.
     * @throws WSException When URL cannot be constructed.
     */
    private URL getEndpointURL(final WSDeploymentDesc wsDD, final JaxRpcPortComponentDesc jpcd, final URL warURL, final String contextRoot)
            throws WSException {

        logger.log(BasicLevel.DEBUG, "get endpoint for JaxRpc Port Component");

        return getEndpointURL(wsDD, jpcd.getWebDesc(), warURL, contextRoot, jpcd);

    }

    /**
     * Creates the endpointURL of a given PortComponentDesc.
     * @param wsDD webservices.xml
     * @param spcd SSBPortComponentDesc to analyze
     * @param warURL URL of the wabapp used to expose the Bean
     * @param cl ejbjar classloader
     * @param earCL application classloader
     * @param contextRoot webapp context name
     * @return the endpointURL of a given PortComponentDesc.
     * @throws WSException When URL cannot be created
     */
    private URL getEndpointURL(final WSDeploymentDesc wsDD, final SSBPortComponentDesc spcd, final URL warURL, final ClassLoader cl,
            final ClassLoader earCL, final String contextRoot) throws WSException {

        logger.log(BasicLevel.DEBUG, "get endpoint for StatelessSessionBean Port Component");

        WebContainerDeploymentDesc webDD = null;

        try {
            webDD = WebManagerWrapper.getDeploymentDesc(warURL, cl, earCL);
        } catch (WebContainerDeploymentDescException e) {
            String err = i18n.getMessage("AbsWebServicesServiceImpl.getEndpointURL.webDDException", warURL.getFile());
            logger.log(BasicLevel.ERROR, err);
            throw new WSException(err, e);
        }

        return getEndpointURL(wsDD, webDD, warURL, contextRoot, spcd);

    }

    /**
     * Returns the URL used to access the endpoint.
     * @param wsDD webservices.xml
     * @param webDD the WebContainer DD of the servlet
     * @param warURL the url of the war containing the servlet dispatching the
     *        SOAP request to the servant.
     * @param warContextRoot the application defined context-root for the webapp
     *        (can be null).
     * @param pcd PortComponentDesc Port descriptor
     * @return the URL used to access the endpoint.
     * @throws WSException When cannot create the endpoint URL.
     */
    private URL getEndpointURL(final WSDeploymentDesc wsDD, final WebContainerDeploymentDesc webDD, final URL warURL,
            final String warContextRoot, final PortComponentDesc pcd) throws WSException {

        // Resolve :
        // - hostname
        // - http/https port
        // Priority order : 1. the ones specified in jonas-web.xml
        //                  2. default web container values (if any)
        String servletName = pcd.getSibLink();
        String pcName = pcd.getQName().getLocalPart();

        logger.log(BasicLevel.DEBUG, "SOAP Servlet name '" + servletName + "'");

        String hostname = webDD.getHost();
        String port = webDD.getPort();
        String scheme = "http"; // default scheme
        String contextRoot = null;
        String mapping = null;

        if (wsDD.getContextRoot() != null && (pcd instanceof SSBPortComponentDesc)) {
            contextRoot = wsDD.getContextRoot();
        } else {
            contextRoot = getContextRoot(warContextRoot, webDD, warURL);
        }

        boolean needPortName = true;
        if (pcd.getEndpointURI() != null) {
            mapping = pcd.getEndpointURI();
            needPortName = false;
        } else {
            mapping = getServletMapping(servletName, webDD);
        }

        String prefix = endpointURLPrefix;

        // if the instance does not have a specified url-prefix
        // we ask the server "environment" for hostname, port, ...
        if (endpointURLPrefix == null || endpointURLPrefix.equals("")) {
            // get default host value
            if (hostname == null) {
                // get hostname
                try {
                    hostname = webService.getDefaultHost();
                } catch (JWebContainerServiceException e) {
                    String err = i18n
                            .getMessage("AbsWebServicesServiceImpl.getEndpointURL.noDefaultHost", warURL.getFile());
                    logger.log(BasicLevel.ERROR, err);
                    throw new WSException(err, e);
                }
            }

            // get default port and scheme value
            if (port == null) {
                // get Http informations
                try {
                    port = webService.getDefaultHttpPort();
                    scheme = "http";
                } catch (JWebContainerServiceException e) {
                    // Http Fails
                    // Try Https informations
                    try {
                        port = webService.getDefaultHttpsPort();
                        scheme = "https";
                    } catch (JWebContainerServiceException e2) {
                        String err = i18n.getMessage("AbsWebServicesServiceImpl.getEndpointURL.noDefaultHTTPPort", warURL
                                .getFile());
                        logger.log(BasicLevel.ERROR, err);
                        throw new WSException(err, e2);
                    }
                }
            }

            // construct URL
            if (port.equals("80")) {
                port = "";
            } else {
                port = ":" + port;
            }

            prefix = scheme + "://" + hostname + port;
        }

        String url = null;
        if (needPortName) {
            String encodedPortName = null;
            try {
                encodedPortName = URLEncoder.encode(pcName, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                // should never occurs since utf-8 is mandatory on all JVM
                encodedPortName = pcName;
            }
            url = prefix + "/" + contextRoot + "/" + mapping + "/" + encodedPortName;
        } else {
            url = prefix + "/" + contextRoot + mapping;
        }

        URL endpoint = null;

        try {
            endpoint = new URL(url);
        } catch (MalformedURLException mue) {
            String err = i18n.getMessage("AbsWebServicesServiceImpl.getEndpointURL.endpointURLError", pcName, url);
            logger.log(BasicLevel.ERROR, err);
            throw new WSException(err, mue);
        }

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Constructed URL for '" + pcName + "' : '" + endpoint + "'");
        }

        return endpoint;
    }

    /**
     * Returns the contextRoot used for webapp access.
     * @param ctxRoot The application specified context-root ("" if not).
     * @param webDD The web DeploymentDesc of the dispatching webapp.
     * @param warURL url of the webapp file.
     * @return The contextRoot used for webapp access.
     */
    private static String getContextRoot(final String ctxRoot, final WebContainerDeploymentDesc webDD, final URL warURL) {
        // Set the right context root for the web application, the priority is
        // the following :
        //    1 - context-root of application.xml
        //    2 - context-root of jonas-web.xml
        //    3 - context-root is the name of the file without .war.
        String contextRoot = null;

        if ("".equals(ctxRoot)) {
            String cRoot = webDD.getContextRoot();

            if (cRoot == null) {
                String file = new File(warURL.getFile()).getName();

                if (file.toLowerCase().endsWith(".war")) {
                    contextRoot = file.substring(0, file.length() - ".war".length());
                } else {
                    //It's a directory which is deployed
                    contextRoot = file.substring(0, file.length());
                }
            } else {
                contextRoot = cRoot;
            }
        } else {
            contextRoot = ctxRoot;
        }

        return contextRoot;
    }

    /**
     * Stops the JAX-RPC service.
     * @throws ServiceException If the service stop fails
     */
    @Override
    public void doStop() throws ServiceException {
        if (jmxService != null) {
            // Unregister JAXRPCService MBean
            try {
                jmxService.unregisterMBean(JonasObjectName.jaxrpcService(getDomainName()));
            } catch (Exception e) {
                logger.log(BasicLevel.DEBUG, "Cannot unregister MBean for JAX-RPC service", e);
            }
        }
    }

    /**
     * Starts the JAX-RPC service.
     * @throws ServiceException If the service startup fails
     */
    @Override
    public void doStart() throws ServiceException {
        WebServicesObjectName.setDomain(getDomainName());

        // Load the Descriptors
        jmxService.loadDescriptors(JAXRPCService.class.getPackage().getName() + ".mbean", JAXRPCService.class.getClassLoader());

        // Register JAXRPCService MBean
        try {
            jmxService.registerMBean(this, JonasObjectName.jaxrpcService(getDomainName()));
        } catch (Exception e) {
            logger.log(BasicLevel.WARN, "Cannot register MBean for JAX-RPC service", e);
        }
    }

    /**
     * Return the unique url servlet mapping.
     *
     * @param servlet
     *                the servlet name.
     * @param webDD
     *                the web DeploymentDesc where mappings are stored.
     * @return the unique url servlet mapping.
     * @throws WSException
     *                 when multiple (or 0) mappings are found.
     */
    private String getServletMapping(final String servlet, final WebContainerDeploymentDesc webDD) throws WSException {

        List mappings = webDD.getServletMappings(servlet);

        logger.log(BasicLevel.DEBUG, "mapping : " + mappings);

        if (mappings == null) {
            String err = i18n.getMessage("AbsWebServicesServiceImpl.getServletMapping.noMapping", servlet);
            logger.log(BasicLevel.ERROR, err);
            throw new WSException(err);
        }

        if (mappings.size() != 1) {
            String err = i18n.getMessage("AbsWebServicesServiceImpl.getServletMapping.1mappingOnly", servlet);
            logger.log(BasicLevel.ERROR, err);
            throw new WSException(err);
        }

        String mapping = (String) mappings.get(0);

        // keep only the first part of the mapping :
        // /services/* becomes services
        StringTokenizer st = new StringTokenizer(mapping, "/");
        mapping = st.nextToken();

        return mapping;

    }

    /**
     * Remove WebServices descriptors associated to the given ClassLoader
     * @param cl key ClassLoader
     */
    public void removeCache(final ClassLoader cl) {
        WSManagerWrapper.removeCache(cl);
        removeDeploymentInfoStack(cl);
    }

    /**
     * @return Returns the i18n.
     */
    protected static I18n getI18n() {
        return i18n;
    }

    /**
     * @return Returns the logger.
     */
    protected static Logger getLogger() {
        return logger;
    }

    /**
     * @see org.ow2.jonas.ws.jaxrpc.IJAXRPCService#completeWSDeployment(javax.naming.Context)
     */
    public void completeWSDeployment(final Context ctx) throws WSException {
        /**
         * 1. Get current deployer ClassLoader
         * 2. Get associated Stack
         * 3. For each DeployInfo element from the Stack
         *  a. Iterate through its ServiceDesc
         *   - create ServletEndpoints instance
         *   - fill it with PortComponentDesc info
         *  b. Bind ServletEndpoints instance
         */
        ClassLoader cl = null;
        ObjectName parent = null;
        Boolean isInEar = Boolean.FALSE;
        try {
            cl = (ClassLoader) ctx.lookup(IJAXRPCService.CLASSLOADER_CTX_PARAM);
            parent = (ObjectName) ctx.lookup(IJAXRPCService.PARENT_OBJECTNAME_CTX_PARAM);
            isInEar = (Boolean) ctx.lookup(IJAXRPCService.ISINEAR_CTX_PARAM);
        } catch (NamingException ne) {
            throw new WSException("Cannot retrieve '" + IJAXRPCService.CLASSLOADER_CTX_PARAM + "' from given Context", ne);
        }

        Stack s = getDeploymentInfoStack(cl);

        while ((s != null) && (!s.empty())) {

            WSDeployInfo di = (WSDeployInfo) s.pop();
            URL warURL = di.getWarURL();
            WSDeploymentDesc wsdd = di.getDescriptor();
            ObjectName parentON = parent;

            if (isInEar.booleanValue()) {
                parentON = getParentModuleObjectName(parent, wsdd);
            }

            // Store the root webservices MBeans
            List webservices = new ArrayList();
            webservicesMBeans.put(warURL, webservices);

            ClassLoader web = webService.getContextLinkedClassLoader(warURL);
            Context c = naming.getComponentContext(web);
            if (c == null) {
                throw new WSException("Cannot get Component Context from ClassLoader : " + web);
            }

            List sds = wsdd.getServiceDescs();

            // for each WSDL contained in the component
            for (Iterator i = sds.iterator(); i.hasNext();) {
                ServiceDesc sd = (ServiceDesc) i.next();

                // serviceMBean
                Service serviceMBean = createServiceMBean(sd.getName(), parentON);
                serviceMBean.setName(sd.getName());
                serviceMBean.setMappingFilename(sd.getMappingFilename());
                serviceMBean.setWsdlFilename(sd.getWsdlFilename());

                // add the MBeans to the registration list
                webservices.add(serviceMBean);

                // get the endpoint URL for each port-component
                // update the WSDL Definition
                for (Iterator pc = sd.getPortComponents().iterator(); pc.hasNext();) {
                    PortComponentDesc pcd = (PortComponentDesc) pc.next();
                    try {
                        c.rebind("comp/jonas/" + pcd.getSibLink() + "/dd", sd);
                    } catch (NamingException ne) {
                        throw new WSException("Cannot bind ServiceDesc instance for servlet '" + sd.getName()
                                + "'", ne);
                    }

                    // Filing up MBeans
                    PortComponent pcMBean = createPortComponentMBean(pcd.getName(), serviceMBean);
                    pcMBean.setEndpoint(pcd.getEndpointURL().toExternalForm());
                    pcMBean.setName(pcd.getName());
                    pcMBean.setServiceEndpointInterface(pcd.getServiceEndpointInterface().getName());
                    pcMBean.setWsdlPort(pcd.getQName().toString());

                    MBeanServer mbeanServer = jmxService.getJmxServer();
                    if (pcd.hasBeanImpl()) {
                        // first, we must find the StatelessSessionBean MBean
                        ObjectName ssbSearch = WebServicesObjectName.getStatelessSessionBeanQuery(parentON, pcd.getSibLink());
                        Iterator it = mbeanServer.queryNames(ssbSearch, null).iterator();

                        // should have only 1 element
                        if (it.hasNext()) {
                            ObjectName on = (ObjectName) it.next();
                            pcMBean.setImplementationBean(on.toString());
                            serviceMBean.addPortComponentMBean(pcMBean);
                        } else {
                            logger.log(BasicLevel.WARN, "Cannot find the MBean for Stateless '" + pcd.getSibLink() + "'");
                        }
                    } else {
                        // first, we must find the Servlet MBean
                        ObjectName sSearch = WebServicesObjectName.getServletQuery(parentON, pcd.getSibLink());
                        Iterator it = mbeanServer.queryNames(sSearch, null).iterator();

                        // should have only 1 element
                        if (it.hasNext()) {
                            ObjectName on = (ObjectName) it.next();
                            pcMBean.setImplementationBean(on.toString());
                            serviceMBean.addPortComponentMBean(pcMBean);
                        } else {
                            logger.log(BasicLevel.WARN, "Cannot find the MBean for Servlet '" + pcd.getSibLink() + "'");
                        }
                    }

                    addHandlerMBeansToPortComponent(pcMBean, pcd);

                    // WSDL URL
                    // The same WSDL is returned for each endpoint
                    serviceMBean.setWsdlURL(pcd.getEndpointURL().toExternalForm() + "?JWSDL");
                }

            }

            // register MBeans
            for (Iterator l = webservices.iterator(); l.hasNext();) {
                Service service = (Service) l.next();
                service.register(jmxService);
            }

        }
    }

    /**
     * @param parent EAR ObjectName
     * @param wsdd WebServices Deployment Descriptor
     * @return Returns the parent Module ObjectName (WebModule or EJBModule)
     */
    private ObjectName getParentModuleObjectName(final ObjectName parent, final WSDeploymentDesc wsdd) {
        // EAR case :
        // then the parent ObjectName is the EAR ObjectName
        // So we need to find either the WebModule or EJBModule
        // that is the real parent of WebServices

        // for this, we will take the first PortComponent and
        // if this is a JaxRpcPCDesc, we will search for
        // a WebModule ObjectName containing the servlet-link
        // in the other case, we will search for an EJBModule

        ObjectName result = null;
        ServiceDesc sd = (ServiceDesc) wsdd.getServiceDescs().iterator().next();
        PortComponentDesc pcd = (PortComponentDesc) sd.getPortComponents().iterator().next();
        String key = pcd.getSibLink();

        MBeanServer mbeanServer = jmxService.getJmxServer();
        if (pcd.hasBeanImpl()) {
            // search for an EJBModule
            // first, we must find the StatelessSessionBean MBean
            ObjectName ssbSearch = WebServicesObjectName.getStatelessSessionBeanQuery(parent, key);
            Iterator it = mbeanServer.queryNames(ssbSearch, null).iterator();

            // should have only 1 element
            if (it.hasNext()) {
                ObjectName ssb = (ObjectName) it.next();

                /// Once we have this ObjectName, we can easily get his parent
                // name via the EJBModule property :)
                ObjectName emSearch = WebServicesObjectName.getEJBModule(parent, ssb.getKeyProperty("EJBModule"));
                Iterator it1 = mbeanServer.queryNames(emSearch, null).iterator();

                // We take the first result and we set it as parent ON
                result = (ObjectName) it1.next();
            } else {
                // onList is empty, so just keep the J2EEApplication as parent
                logger.log(BasicLevel.WARN, "Cannot find EJBModule MBean containing SSB " + key);
                result = parent;
            }
        } else {
            // search for WebModule
            // first, we must find the Servlet MBean
            ObjectName sSearch = WebServicesObjectName.getServletQuery(parent, key);
            Iterator it = mbeanServer.queryNames(sSearch, null).iterator();

            // should have only 1 element
            if (it.hasNext()) {
                ObjectName servlet = (ObjectName) it.next();

                // / Once we have this ObjectName, we can easily get his parent
                // name via the WebModule property :)
                ObjectName wmSearch = WebServicesObjectName.getWebModule(parent, servlet.getKeyProperty("WebModule"));
                Iterator it1 = mbeanServer.queryNames(wmSearch, null).iterator();

                // We take the first result and we set it as parent ON
                result = (ObjectName) it1.next();
            } else {
                // onList is empty, so just keep the J2EEApplication as parent
                logger.log(BasicLevel.WARN, "Cannot find WebModule MBean containing Servlet " + key);
                result = parent;

            }
        }
        return result;
    }

    /**
     * buildServiceRef from WS ref descriptor
     * @param rd WS ref descriptor
     */
    public Reference buildServiceRef(final IServiceRefDesc rd, final ClassLoader loader) throws NamingException {
        Reference ref = null;
        // get the JServiceFactory
        JServiceFactory factory = createServiceFactory();
        try {
            ref = factory.getServiceReference(rd, loader);
        } catch (Exception e1) {
            NamingException ne = new NamingException("Cannot get service Reference for "
                                                     + rd.getServiceRefName());
            ne.initCause(e1);
            throw ne;
        }
        return ref;
    }

    /**
     * @param key
     *            ClassLoader
     * @return Returns a Stack of DeployInfo associated to the given ClassLoader
     */
    private Stack getDeploymentInfoStack(final ClassLoader key) {
        return (Stack) deployments.get(key);
    }

    /**
     * Removes the Stack associated to the given ClassLoader
     * @param key ClassLoader
     */
    private void removeDeploymentInfoStack(final ClassLoader key) {
        deployments.remove(key);
    }

    /**
     * Store information about a webservices descriptor.
     * @author Guillaume Sauthier
     */
    public class WSDeployInfo {

        /**
         * URL of the webapp
         */
        private URL war = null;

        /**
         * WebServices descriptor
         */
        private WSDeploymentDesc wsdd = null;

        /**
         * Creates a new WSDeployInfo instance with given parameters
         * @param war URL of the webapp
         * @param wsdd WebServices descriptor
         */
        public WSDeployInfo(final URL war, final WSDeploymentDesc wsdd) {
            this.war = war;
            this.wsdd = wsdd;
        }

        /**
         * @return Returns the Webapp URL
         */
        public URL getWarURL() {
            return war;
        }

        /**
         * @return Returns the WebServices Descriptor
         */
        public WSDeploymentDesc getDescriptor() {
            return wsdd;
        }
    }

    /**
     * @see org.ow2.jonas.ws.jaxrpc.IJAXRPCService#undeployWebServices(javax.naming.Context)
     */
    public void undeployWebServices(final Context ctx) throws WSException {

        URL url = null;
        try {
            url = (URL) ctx.lookup(IJAXRPCService.WARURL_CTX_PARAM);
        } catch (NamingException e) {
            // should never go here, but anyway ...
            throw new IllegalArgumentException(e.getMessage());
        }

        List ws = (List) webservicesMBeans.get(url);
        if (ws != null) {
            // unregister if a webservices MBean has been found
            for (Iterator l = ws.iterator(); l.hasNext();) {
                Service service = (Service) l.next();
                service.unregister(jmxService);
            }
        }

        // TODO unpublish WSDL ?
    }

    /**
     * Automatic WSGen is enabled ?
     * @return true if automatic WSGen needs to be Called.
     */
    public boolean isAutoWsGenEngaged() {
        return autoWSGen;
    }

    /**
     * Returns the URL prefixing all generated endpoint URL.
     * @return The URL prefixing all generated endpoint URL
     */
    public String getUrlPrefix() {
        return this.endpointURLPrefix;
    }

    /**
     * Validating parser or not ?
     * @return Validating parser or not
     */
    public boolean isParsingWithValidation() {
        return this.parsingWithValidation;
    }

    /**
     * @param webService the webService to set
     */
    public void setWebService(final JWebContainerService webService) {
        this.webService = webService;
    }

    /**
     * @param jmxService the jmxService to set
     */
    public void setJmxService(final JmxService jmxService) {
        this.jmxService = jmxService;
    }

    /**
     * @param naming the naming to set
     */
    public void setNaming(final JNamingManager naming) {
        this.naming = naming;
    }

    /**
     * Bind the WSDLPublisherManager into this service
     * @param wsdlManager the manager to use
     */
    public void bindWSDLPublisherManager(final WSDLPublisherManager wsdlManager) {
        this.wsdlManager = wsdlManager;
    }

}

