/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.deployment.ws;

import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.ow2.jonas.deployment.ws.lib.MappingFileManager;
import org.ow2.jonas.deployment.ws.wrapper.MappingFileManagerWrapper;
import org.ow2.jonas.deployment.ws.xml.JonasPortComponent;
import org.ow2.jonas.deployment.ws.xml.JonasWebserviceDescription;
import org.ow2.jonas.deployment.ws.xml.PortComponent;
import org.ow2.jonas.deployment.ws.xml.WebserviceDescription;
import org.ow2.jonas.lib.util.I18n;
import org.ow2.jonas.lib.util.Log;




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

/**
 * This class corresponds to 1 <code>webservices-description</code> XML
 * element. It's used to get a MappingFile instance if it's defined, idem for
 * WSDLFile instance and give access to the Web service Port component list.
 *
 * @author Guillaume Sauthier
 * @author Xavier Delplanque
 */
public class ServiceDesc {

    /**
     * Internationalization
     */
    private static I18n i18n = I18n.getInstance(ServiceDesc.class);

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

    /**
     * The webservice-description-name
     */
    private String name;

    /**
     * The list of PortComponentDesc
     */
    private Hashtable namePCDescBindings = new Hashtable();

    /**
     * The WSDL of the WebService
     */
    private WSDLFile wsdl = null;

    /**
     * The JaxRpc Mapping of the WebService
     */
    private MappingFile mapping = null;

    /**
     * Default endpoint URI for this group of WebServices
     */
    private String endpointURI = null;

    /**
     * Place where WSDL(s) will be published (may be null)
     */
    private File publicationDirectory = null;

    /**
     * URL of the mapping file
     */
    private URL mappingFileURL;

    /**
     * URL of the WSDL
     */
    private URL localWSDLURL;

    /**
     * JAX-RPC Mapping file name
     */
    private String mappingFilename = null;

    /**
     * WSDL file name
     */
    private String wsdlFilename = null;

    /**
     * Constructor : creates a ServiceDesc object.
     * @param jarCL ejbjar or war classLoader
     * @param wsd Zeus object containing WebserviceDescription informations
     * @param jwsd Zeus object containing JonasWebserviceDescription informations
     * @throws WSDeploymentDescException When contruction fails.
     */
    public ServiceDesc(ClassLoader jarCL, WebserviceDescription wsd, JonasWebserviceDescription jwsd) throws WSDeploymentDescException {

        // set name
        name = wsd.getWebserviceDescriptionName();
        if ("".equals(name)) { //$NON-NLS-1$
            throw new WSDeploymentDescException(getI18n().getMessage("ServiceDesc.noServiceName")); //$NON-NLS-1$
        }

        if (jwsd != null) {
            // set endpointURI
            String uri = jwsd.getDefaultEndpointURI();
            if (uri != null && !"".equals(uri)) {
                endpointURI = uri;
            }

            // Set wsdl-publication-directory
            String wsdlPubDir = jwsd.getWsdlPublishDirectory();
            if (wsdlPubDir != null) {
                URL pub;
                try {
                    pub = new URL(wsdlPubDir);
                } catch (MalformedURLException e) {
                    throw new WSDeploymentDescException("Cannot create URL : " + wsdlPubDir, e);
                }
                publicationDirectory = new File(pub.getPath());
            }
        }


        // get ServiceRef WSDLFile name
        wsdlFilename = wsd.getWsdlFile();

        // wsdl-file element must have a value
        if ("".equals(wsdlFilename)) { //$NON-NLS-1$
            String err = getI18n().getMessage("ServiceDesc.noWSDL", name); //$NON-NLS-1$
            throw new WSDeploymentDescException(err);
        }

        // build the WSDLFile
        wsdl = new WSDLFile(jarCL, wsdlFilename);
        localWSDLURL = jarCL.getResource(wsdlFilename);

        // set mapping file
        mappingFilename = wsd.getJaxrpcMappingFile();

        // jaxrpc-mapping-file element must have a value
        if (mappingFilename.equals("")) { //$NON-NLS-1$
            String err = getI18n().getMessage("ServiceDesc.noJAXRPCMapping", name); //$NON-NLS-1$
            throw new WSDeploymentDescException(err);
        }

        InputStream isMapping = jarCL.getResourceAsStream(mappingFilename);

        // isMapping must not be null (CL cannot find resource)
        if (isMapping == null) {
            String err = getI18n().getMessage("ServiceDesc.MappingNotFound", mappingFilename, name); //$NON-NLS-1$
            throw new WSDeploymentDescException(err);
        }

        mappingFileURL = jarCL.getResource(mappingFilename);

        // Build Mapping file
        if (isRunningInClientContainer()) {
            // running in Client Container
            mapping = MappingFileManager.getInstance(isMapping, mappingFilename);
        } else {
            // running in server
            mapping = MappingFileManagerWrapper.getMappingFile(isMapping, mappingFilename);
        }

        // links port-components and jonas-port-components
        Map links = associatePCAndJPC(wsd, jwsd);

        // set namePCDescBindings
        List pcl = wsd.getPortComponentList();

        for (int i = 0; i < pcl.size(); i++) {
            // for each port component, build and add a PortComponentDesc Object
            PortComponent pc = (PortComponent) pcl.get(i);
            JonasPortComponent jpc = (JonasPortComponent) links.get(pc.getPortComponentName());

            PortComponentDesc pcd = PortComponentDescFactory.newInstance(jarCL, pc, jpc, this);

            if (!wsdl.hasPort(pcd.getQName())) {
                throw new WSDeploymentDescException(getI18n().getMessage(
                        "ServiceDesc.unknownWSDLPort", pcd.getName(), pcd.getQName())); //$NON-NLS-1$
            }

            if (namePCDescBindings.put(pcd.getName(), pcd) != null) {
                throw new WSDeploymentDescException(getI18n().getMessage(
                        "ServiceDesc.portNameAlreadyUsed", pcd.getName())); //$NON-NLS-1$
            }
        }

        // validate informations :
        List ports = getPortComponents();

        // Ports use Soap bindings
        for (int i = 0; i < ports.size(); i++) {
            if (ports.get(i) != null) {
                PortComponentDesc pcd = (PortComponentDesc) ports.get(i);

                if (!wsdl.hasSOAPBinding(pcd.getQName())) {
                    throw new WSDeploymentDescException(getI18n().getMessage(
                            "ServiceDesc.noSOAPBinding", pcd.getName(), pcd.getQName())); //$NON-NLS-1$
                }
            }
        }
    }

    /**
     * @return Returns true if current execution takes place inside a
     *         ClientContainer
     */
    private boolean isRunningInClientContainer() {
        return (System.getProperty("jonas.base") == null);
    }

    /**
     * @param wsd WebservicesDescription element
     * @param jwsd JonasWebservicesDescription element
     * @return a Map of PortComponent.name to JonasPortComponent
     */
    private Map associatePCAndJPC(WebserviceDescription wsd, JonasWebserviceDescription jwsd) {
        Map res = new HashMap();
        // for each port-component
        for (Iterator i = wsd.getPortComponentList().iterator(); i.hasNext();) {
            PortComponent pc = (PortComponent) i.next();
            res.put(pc.getPortComponentName(), null);
        }
        // jonas-port-component(s)
        if (jwsd != null) {

            // get all port-component.name
            Set keys = res.keySet();

            // for each jonas-port-component
            for (Iterator i = jwsd.getJonasPortComponentList().iterator(); i.hasNext();) {
                JonasPortComponent jpc = (JonasPortComponent) i.next();
                String pcName = jpc.getPortComponentName();

                if (keys.contains(pcName)) {
                    // jonas-port-component linked to port-component
                    res.put(pcName, jpc);
                } else {
                    String err = "jonas-port-component '" + pcName + "' is not linked to any port-component. It will be ignored."; //getI18n().getMessage("WSDeploymentDesc.wsdlDeclareUnknownPort", wsdlf.getName());
                    logger.log(BasicLevel.DEBUG, err);
                }
            }
        }
        return res;
    }

    /**
     * Return the name of the WebService.
     * @return the name of the WebService.
     */
    public String getName() {
        return name;
    }

    /**
     * Return the Mapping File.
     * @return the MappingFile.
     */
    public MappingFile getMapping() {
        return mapping;
    }

    /**
     * Return the WSDL File.
     * @return the WSDL File.
     */
    public WSDLFile getWSDL() {
        return wsdl;
    }

    /**
     * @return Returns the endpointURI.
     */
    public String getEndpointURI() {
        return endpointURI;
    }

    /**
     * Return the port component list of this service desc.
     * @return the port component list of this service desc.
     */
    public List getPortComponents() {
        return new Vector(namePCDescBindings.values());
    }

    /**
     * Return the port component desc with the given name if it exists, null
     * else.
     * @param pcName the name of the portComponent to retrieve.
     * @return the port component desc with the given name if it exists, null
     *         else.
     */
    public PortComponentDesc getPortComponent(String pcName) {
        return (PortComponentDesc) namePCDescBindings.get(pcName);
    }

    /**
     * @return Returns a String representation of the ServiceDesc.
     */
    public String toString() {
        StringBuffer sb = new StringBuffer();

        sb.append("\n" + getClass().getName()); //$NON-NLS-1$
        sb.append("\ngetName()=" + getName()); //$NON-NLS-1$
        sb.append("\ngetMapping()=" + getMapping()); //$NON-NLS-1$
        sb.append("\ngetWSDL()=" + getWSDL()); //$NON-NLS-1$

        for (Iterator i = getPortComponents().iterator(); i.hasNext();) {
            sb.append("\ngetPortComponents()=" + ((PortComponentDesc) i.next()).toString()); //$NON-NLS-1$
        }

        return sb.toString();
    }

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

    /**
     * @return Returns the publicationDirectory.
     */
    public File getPublicationDirectory() {
        return publicationDirectory;
    }

    /**
     * @return Returns URL where mapping file can be loaded.
     */
    public URL getMappingFileURL() {
        return mappingFileURL;
    }

    /**
     * @return Returns URL where the WSDL can be loaded locally.
     */
    public URL getLocalWSDLURL() {
        return localWSDLURL;
    }

    /**
     * @return Returns the JAX-RPC Mapping filename
     */
    public String getMappingFilename() {
        return mappingFilename;
    }

    /**
     * @return Returns the WSDL filename
     */
    public String getWsdlFilename() {
        return wsdlFilename;
    }
}