/**
 * 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.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.wsdl.Definition;
import javax.wsdl.Message;
import javax.wsdl.Part;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.WSDLException;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.extensions.soap.SOAPBinding;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.wsdl.xml.WSDLWriter;
import javax.xml.namespace.QName;

import org.ow2.jonas.lib.util.I18n;


/**
 * Gives methods to get WSDL informations and to validate these one.
 * @author Xavier Delplanque
 * @author Guillaume Sauthier
 */
public class WSDLFile {


    /** list of each ports of each wsdl services */
    private String name;

    /** list of each ports of each wsdl services */
    private List wsdlPorts = null;

    /** wsdl definition */
    private Definition def = null;

    /** verbose mode */
    private static final boolean VERBOSE = false;

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

    /**
     * Creates a new WSDLFile object.
     * @param cl ClassLoader to use
     * @param name wsdl file name
     * @throws WSDeploymentDescException when a parse error occurs
     */
    public WSDLFile(ClassLoader cl, String name) throws WSDeploymentDescException {
            this(cl.getResource(name), name);
    }

    /**
     * Creates a new WSDLFile object.
     * @param url URL to load
     * @param name wsdl file name
     * @throws WSDeploymentDescException when a parse error occurs
     */
    public WSDLFile(URL url, String name) throws WSDeploymentDescException {
        this.name = name;

        try {
            WSDLFactory f = WSDLFactory.newInstance();
            WSDLReader r = f.newWSDLReader();

            r.setFeature("javax.wsdl.verbose", VERBOSE); //$NON-NLS-1$
            r.setFeature("javax.wsdl.importDocuments", true); //$NON-NLS-1$

            if (url == null) {
                throw new WSDeploymentDescException(getI18n().getMessage("WSDLFile.notFound", name)); //$NON-NLS-1$
            }

            def = r.readWSDL(url.toExternalForm(), url.toExternalForm());
        } catch (WSDLException e) {
            throw new WSDeploymentDescException(getI18n().getMessage("WSDLFile.WSDLParsingError", name), e); //$NON-NLS-1$
        }

        wsdlPorts = new Vector();
        fillWsdlPorts();
    }

    /**
     * return true if the port identified by portQname is defined in WSDL ports.
     * @param portQName the port to check.
     * @return if the gived Qname is contained in the
     *         WSDL.definition.service.port.
     */
    public boolean hasPort(QName portQName) {
        return hasPort(portQName.getLocalPart());
    }

    /**
     * return true if the port identified by portName is defined in WSDL ports.
     * @param portName the port to check.
     * @return if the gived port is contained in the
     *         WSDL.definition.service.port.
     */
    public boolean hasPort(String portName) {
        return getPort(portName) != null;
    }

    /**
     * return true if the service identified by srvQName is defined in WSDL
     * services.
     * @param srvQName the service to check.
     * @return true if the given Qname is contained in the
     *         WSDL.definition.service.
     */
    public boolean hasService(QName srvQName) {
        return def.getService(srvQName) != null;
    }

    /**
     * return true if shQName is defined in WSDL services.
     * @param shQName a soap header Qname that could be defined in the WSDL.
     * @return true if SOAP Header has been found in WSDL Definition.
     */
    public boolean hasSOAPHeader(QName shQName) {
        Map msgs = def.getMessages();

        for (Iterator m = msgs.values().iterator(); m.hasNext();) {
            Message msg = (Message) m.next();

            if (msg.getQName().getNamespaceURI() == shQName.getNamespaceURI()) {
                Part p = msg.getPart(shQName.getLocalPart());

                if (p != null) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * return true if all WSDL ports are defined in portList.
     * @param portList the ports List to check.
     * @return true if all WSDL ports are defined in
     *         WSDL.definition.service.port.
     */
    public boolean hasPortsIncludedIn(List portList) {
        return portList.containsAll(wsdlPorts);
    }

    /**
     * return true if the port identified by portQname use a SOAP binding.
     * @param portQName the port to check.
     * @return if the port identified by portQname use a SOAP binding. return
     *         false if the portQname is not defined in the WSDL file.
     */
    public boolean hasSOAPBinding(QName portQName) {
        boolean isSoapBinding = false;
        Port port = getPort(portQName.getLocalPart());

        // get Port
        if (port != null) {
            List ee = port.getBinding().getExtensibilityElements();
            Iterator eeIt = ee.iterator();

            while (eeIt.hasNext() && !isSoapBinding) {
                // verify that it uses a soap binding
                Object elem = eeIt.next();

                if (elem != null) {
                    isSoapBinding = elem instanceof SOAPBinding;
                }
            }
        }

        return isSoapBinding;
    }

    /**
     * return WSDL definition, null if it's undefined.
     * @return WSDL definition.
     */
    public Definition getDefinition() {
        return def;
    }

    /**
     * return the number of services defined inside the wsdl.
     * @return the number of services defined inside the wsdl.
     */
    public int getNbServices() {
        return def.getServices().size();
    }

    /**
     * return the QName of the first service, null if no service is defined.
     * @return the QName of the first service, null if no service is defined.
     */
    public QName getServiceQname() {
        Map srvs = def.getServices();
        Iterator svcIt = srvs.values().iterator();
        QName res = null;

        if (svcIt.hasNext()) {
            Service svc = (Service) svcIt.next();
            res = svc.getQName();
        }

        return res;
    }

    /**
     * return the given port location, null if the port is undefined.
     * @param portQName the port QName identifying the port searched.
     * @return portQname location.
     * @throws WSDeploymentDescException when port is not found
     */
    public URL getLocation(QName portQName) throws WSDeploymentDescException {
        Port port = getPort(portQName.getLocalPart());

        // get portQName port
        if (port != null) {
            List ee = port.getExtensibilityElements();
            Iterator eeIt = ee.iterator();

            while (eeIt.hasNext()) {
                // find the soap address element
                Object elem = eeIt.next();

                if ((elem != null) && elem instanceof SOAPAddress) {
                    try {
                        return new URL(((SOAPAddress) elem).getLocationURI());
                    } catch (MalformedURLException e) {
                        throw new WSDeploymentDescException(getI18n().getMessage("WSDLFile.MalformedPortLocation", portQName)); //$NON-NLS-1$
                    }
                }
            }
        }

        return null;
    }

    /**
     * set the given port location if it exists.
     * @param portQName the port to set.
     * @param loc the port location.
     */
    public void setLocation(QName portQName, URL loc) {
        Port port = getPort(portQName.getLocalPart());

        if (port != null) {
            List ee = port.getExtensibilityElements();
            Iterator eeIt = ee.iterator();

            while (eeIt.hasNext()) {
                // find the soap address element
                Object elem = eeIt.next();

                if ((elem != null) && elem instanceof SOAPAddress) {
                    ((SOAPAddress) elem).setLocationURI(loc.toString());
                }
            }
        }
    }

    /**
     * Init the wsdlPorts list.
     */
    private void fillWsdlPorts() {
        Map svcs = def.getServices();
        Iterator svcsIt = svcs.values().iterator();

        while (svcsIt.hasNext()) {
            Service svc = (Service) svcsIt.next();

            if (svc != null) {
                for (Iterator j = svc.getPorts().values().iterator(); j.hasNext();) {
                    Port p = (Port) j.next();
                    wsdlPorts.add(new QName(def.getTargetNamespace(), p.getName()));
                }
            }
        }
    }

    /**
     * Return the Port identified by portName in the WSDL, return null if it's
     * not defined inside the wsdl.
     * @param portName the port name.
     * @return The port object identified by the name portName
     */
    private Port getPort(String portName) {
        Map svcs = def.getServices();
        Iterator svcsIt = svcs.values().iterator();

        while (svcsIt.hasNext()) {
            Service svc = (Service) svcsIt.next();

            if (svc != null) {
                Port port = svc.getPort(portName);

                // get the port identified by portQname
                return port;
            }
        }

        return null;
    }

    /**
     * Return wsdl file name
     * @return wsdl file name
     */
    public String getName() {
        return name;
    }

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

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

        StringWriter sw = new StringWriter();
        // Write Definition
        try {
            WSDLFactory factory = WSDLFactory.newInstance();
            WSDLWriter writer = factory.newWSDLWriter();

            writer.writeWSDL(def, sw);

        } catch (WSDLException e) {
            sb.append(getI18n().getMessage("WSDLFile.writeDefError")); //$NON-NLS-1$
        }

        sb.append(sw.getBuffer().toString());

        return sb.toString();
    }

    /**
     * Return true if the 2 objects seems equals. Because the equals() method
     * doesn't exist on Definition, it's hard to known if 2 Definition are
     * equals in value. So we just test lists lengths. Use it ONLY in test/debug
     * case. !!!
     * @param other the object to compare.
     * @return true if the 2 objects seems equals.
     */
    public boolean equals(Object other) {
        if (other == null) {
            return false;
        }

        if (!(other instanceof WSDLFile)) {
            return false;
        }

        WSDLFile ref = (WSDLFile) other;
        Definition odef = ref.getDefinition();

        if (def.getServices().size() != odef.getServices().size()) {
            return false;
        }

        if (def.getPortTypes().size() != odef.getPortTypes().size()) {
            return false;
        }

        if (def.getMessages().size() != odef.getMessages().size()) {
            return false;
        }

        if (def.getBindings().size() != odef.getBindings().size()) {
            return false;
        }

        // After all theses tests, the 2 objects are equals in value
        return true;
    }

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