/**
 * Copyright (c) 2007-2012 EBM WebSourcing, 2012-2024 Linagora
 * 
 * This program/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 (at your
 * option) any later version.
 * 
 * This program/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 program/library; If not, see http://www.gnu.org/licenses/
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.binding.soap.util;

import static org.ow2.petals.binding.soap.SoapConstants.SOAP.FAULT_SERVER;

import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jbi.JBIException;
import javax.jbi.servicedesc.ServiceEndpoint;
import javax.servlet.ServletException;

import org.apache.axiom.om.OMElement;
import org.apache.axis2.AxisFault;
import org.apache.axis2.util.PolicyUtil;
import org.apache.axis2.util.XMLUtils;
import org.apache.neethi.PolicyComponent;
import org.ow2.easywsdl.extensions.wsdl4complexwsdl.WSDL4ComplexWsdlFactory;
import org.ow2.easywsdl.extensions.wsdl4complexwsdl.api.Description;
import org.ow2.easywsdl.extensions.wsdl4complexwsdl.api.WSDL4ComplexWsdlException;
import org.ow2.easywsdl.extensions.wsdl4complexwsdl.api.WSDL4ComplexWsdlReader;
import org.ow2.easywsdl.extensions.wsdl4complexwsdl.api.WSDL4ComplexWsdlWriter;
import org.ow2.easywsdl.wsdl.api.Endpoint;
import org.ow2.easywsdl.wsdl.api.Service;
import org.ow2.easywsdl.wsdl.api.WSDLReader.FeatureConstants;
import org.ow2.petals.binding.soap.listener.incoming.servlet.SoapServlet;
import org.ow2.petals.component.framework.AbstractComponent;
import org.ow2.petals.component.framework.jbidescriptor.generated.Consumes;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.ebmwebsourcing.easycommons.xml.XMLPrettyPrinter;

/***
 * @author Christophe Hamerling - EBM WebSourcing
 */
public final class WsdlHelper {

    private WsdlHelper() {
        // Utility class --> No constructor
    }

    /**
     * Replace the endpoint address in the WSDL
     * 
     * @return
     * @throws WSDL4ComplexWsdlException
     * @throws URISyntaxException
     */
    private static Document replaceServiceAddressInWSDL(final Document doc,
            final String address) throws WSDL4ComplexWsdlException, URISyntaxException {
        Document result = null;
        final WSDL4ComplexWsdlFactory wsdlFactory = WSDL4ComplexWsdlFactory.newInstance();
        final WSDL4ComplexWsdlWriter wsdlWriter = wsdlFactory.newWSDLWriter();
        final WSDL4ComplexWsdlReader reader = wsdlFactory.newWSDLReader();
        final Description desc = reader.read(doc);
        final List<Service> services = desc.getServices();
        for (final Service service : services) {
            final List<Endpoint> endpoints = service.getEndpoints();
            for (final Endpoint endpoint : endpoints) {
                endpoint.setAddress(address);
            }
        }
        result = wsdlWriter.getDocument(desc);
        return result;
    }

    /**
     * Get the service definition document (WSDL)
     * 
     * It is used by {@link AxisServicesHelper} during the Axis processing, it should only throw {@link AxisFault}.
     * 
     * @return never <code>null</code>!!
     * 
     */
    public static Description getDescription(final Consumes consumes, final AbstractComponent componentContext,
            final Logger logger) throws AxisFault {
        final Document doc;
        try {
            doc = getServiceDefinition(consumes, componentContext, logger);
        } catch (final JBIException e) {
            // everything should be said in the JBI Error
            throw new AxisFault(e.getMessage(), FAULT_SERVER, e);
        }

        if (doc != null) {
            try {
                return WSDL4ComplexWsdlFactory.newInstance()
                        .newWSDLReader(Collections.singletonMap(FeatureConstants.IMPORT_DOCUMENTS, (Object) true))
                        .read(doc);
            } catch (WSDL4ComplexWsdlException | URISyntaxException e) {
                throw new AxisFault("Can't parse the JBI endpoint WSDL description for " + doc, FAULT_SERVER, e);
            }
        } else {
            throw new AxisFault("No WSDL associated to the JBI endpoint", FAULT_SERVER);
        }
    }

    /**
     * Get the service definition document (WSDL) with the updated service
     * address
     */
    private static Document getServiceDefinition(final Consumes consumes, final AbstractComponent componentContext,
            final Logger logger, final String serviceAddress) throws JBIException {
        Document result = getServiceDefinition(consumes, componentContext, logger);

        if (result != null) {
            try {
                result = WsdlHelper.replaceServiceAddressInWSDL(result, serviceAddress);
            } catch (final WSDL4ComplexWsdlException | URISyntaxException e) {
                throw new JBIException("Can't replace service address", e);
            }
        }

        return result;
    }

    /**
     * Get the service definition document (WSDL)
     */
    private static Document getServiceDefinition(final Consumes consumes, final AbstractComponent componentContext,
            final Logger logger) throws JBIException {
        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "Get the service definition");
        }

        final ServiceEndpoint ep = getServiceEndpoint(consumes, componentContext);
        if (logger.isLoggable(Level.FINE)) {
            logger.fine("Found JBI Service endpoint: " + ep);
        }

        if (ep != null) {
            return componentContext.getContext().getEndpointDescriptor(ep);
        } else {
            throw new JBIException("The JBI Endpoint cannot be found");
        }
    }

    /**
     * Get the service endpoint. Try to retrieve if the endpoint name and
     * service name has been specified, else try to get it if only the interface
     * has been specified.
     */
    private static ServiceEndpoint getServiceEndpoint(final Consumes consumes,
            final AbstractComponent componentContext) {

        Iterator<ServiceEndpoint> endpoints = componentContext.getServiceUnitManager().getEndpointsForConsumes(consumes)
                .iterator();

        return endpoints.hasNext() ? endpoints.next() : null;

    }

    /**
     * This is used by the {@link SoapServlet}: it should only throw {@link ServletException}
     */
    public static void printWSDL(final Logger logger, final Consumes consumes, final AbstractComponent componentContext,
            final OutputStream out, final String serviceAddress, final Collection<PolicyComponent> wssPolicies)
            throws ServletException {

        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "Print WSDL");
        }

        try {

            // Note: WSS-Policy should be added to the WSDL through the function 'getServiceDefinition'. But as EasyWSDL
            // does not manage correctly extensions in WSDL, we must add WSS-Policy through the function 'printWSDL'.

            final Document wsdl = getServiceDefinition(consumes, componentContext, logger, serviceAddress);
            if (wsdl == null) {
                throw new ServletException("No WSDL associated to the JBI endpoint");
            } else {
                printWSDL(wsdl, serviceAddress, wssPolicies, out);
            }
        } catch (final Exception e) {
            // the message should be clear in the exception
            throw new ServletException(e);
        }
    }

    protected static void printWSDL(final Document wsdl, final String serviceAddress,
            final Collection<PolicyComponent> wssPolicies, final OutputStream out)
            throws Exception {
        assert wsdl != null;

        // create a easyWSDL description
        final Description desc = WSDL4ComplexWsdlFactory.newInstance().newWSDLReader().read(wsdl);

        // delete imported documents
        desc.deleteImportedDocumentsInWsdl(new URI(serviceAddress + "?wsdl="));

        // recreate a Document without imports
        final Document descWithoutImport = WSDL4ComplexWsdlFactory.newInstance().newWSDLWriter().getDocument(desc);

        // And add the WSS-Policy if exists
        for (final PolicyComponent wssPolicy : wssPolicies) {
            final OMElement policyElt = PolicyUtil.getPolicyComponentAsOMElement(wssPolicy);
            final Node policyNode = descWithoutImport.adoptNode(XMLUtils.toDOM(policyElt));

            // Remove Apache Rampart configuration from WS-Policy
            final NodeList rampartConfigElts = ((Element) policyNode)
                    .getElementsByTagNameNS("http://ws.apache.org/rampart/policy", "RampartConfig");
            for (int i = 0; i < rampartConfigElts.getLength(); i++) {
                final Node rampartConfigElt = rampartConfigElts.item(i);
                rampartConfigElt.getParentNode().removeChild(rampartConfigElt);
            }

            descWithoutImport.getDocumentElement().appendChild(policyNode);
        }

        XMLPrettyPrinter.prettify(descWithoutImport, out, XMLPrettyPrinter.getEncoding(descWithoutImport));
    }

    /**
     * Return the imported documents in the WSDL description of the services. As these documents are rebuilt, the
     * exposed web service URL is required to build them correctly
     * 
     * This is used by the {@link SoapServlet}, it should only throw {@link ServletException}.
     * 
     * @param logger
     *            the logger
     * @param consumes
     *            the consumes
     * @param componentContext
     *            the component context
     * @param requestURL
     *            the URL of the web service, typically http://..:8084/petals/services/MyWebService
     * @return
     */
    public static Map<URI, Document> getImportedDocuments(final Logger logger, final Consumes consumes,
            final AbstractComponent componentContext, final String requestURL) throws ServletException {


        // In the case an import is requested before the complexWSDL has been
        // generated...
        final Document doc;
        try {
            doc = getServiceDefinition(consumes, componentContext, logger);
        } catch (final JBIException e) {
            // the error message in the exception should be clear
            throw new ServletException(e);
        }

        if (doc == null) {
            throw new ServletException("WSDL description can not been retrieved from JBI endpoint");
        } else {
            final Description desc;
            try {
                // create a easyWSDL description
                desc = WSDL4ComplexWsdlFactory.newInstance().newWSDLReader().read(doc);
            } catch (final WSDL4ComplexWsdlException | URISyntaxException e) {
                throw new ServletException("Can't parse the JBI endpoint WSDL description for " + doc, e);
            }
            try {
                // remove imported docs
                return desc.deleteImportedDocumentsInWsdl(new URI(requestURL));
            } catch (final URISyntaxException e) {
                throw new ServletException("Can't parse the request URL: " + requestURL, e);
            } catch (final WSDL4ComplexWsdlException e) {
                throw new ServletException(e);
            }
        }
    }
}
