/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 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$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.ws.cxf.jaxws;

import static org.ow2.jonas.lib.util.Log.setComponentLogLevel;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.wsdl.WSDLException;

import org.apache.cxf.Bus;
import org.apache.cxf.service.model.ServiceInfo;
import org.apache.cxf.wsdl.WSDLManager;
import org.apache.cxf.wsdl11.ServiceWSDLBuilder;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Level;
import org.ow2.jonas.ws.cxf.BusCreator;
import org.ow2.jonas.ws.cxf.http.JOnASDestination;
import org.ow2.jonas.ws.cxf.util.WSDLUtils;
import org.ow2.jonas.ws.jaxws.IWebServiceEndpoint;
import org.ow2.jonas.ws.jaxws.PortIdentifier;
import org.ow2.jonas.ws.jaxws.base.JAXWSWebservicesContainer;
import org.ow2.jonas.ws.publish.PublicationInfo;
import org.ow2.jonas.ws.publish.PublishableDefinition;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

/**
 * The WebservicesContainer represents a group of endpoints sharing the same WSDL Definition.
 * Grouping is based on the WSDL location value. <br/>
 * For example, if 2 endpoints (from the same archive) are declaring the same WSDL location
 * ('META-INF/wsdl/definition.wsdl' for example), they will be grouped in the same container.
 *
 * @author Guillaume Sauthier
 */
public class WebservicesContainer extends JAXWSWebservicesContainer {

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(WebservicesContainer.class);

    /**
     * Endpoints of the group share the same Bus instance.
     */
    private Bus bus;

    /**
     * This represents the WSDL Location [META-INF/wsdl/definition.wsdl].
     */
    private String name;

    /**
     * The list of endpoints sharing the WSDL.
     */
    private Map<PortIdentifier, IWebServiceEndpoint> endpoints;

    /**
     * Where do the user wants to publish the WSDL ?
     */
    private String wsdlPublicationDirectory;

    /**
     * Construct the group.
     * @param name Shared WSDL Location
     */
    public WebservicesContainer(final String name) {
        super(name);
        this.name = name;
        endpoints = new HashMap<PortIdentifier, IWebServiceEndpoint>();

        // Init the Bus
        BusCreator creator = new BusCreator();
        bus = creator.initBus();

    }

    /**
     * Add a new endpoint in the group.
     * @param endpoint newly added endpoint.
     */
    @Override
    public void addEndpoint(final IWebServiceEndpoint endpoint) {
        endpoints.put(endpoint.getIdentifier(), endpoint);
    }

    /**
     * Remove an endpoint from the group.
     * @param endpoint removed endpoint.
     */
    @Override
    public void removeEndpoint(final IWebServiceEndpoint endpoint) {
        endpoints.remove(endpoint.getIdentifier());
    }

    /**
     * @return the shared Bus instance.
     */
    public Bus getBus() {
        return this.bus;
    }

    /**
     * Given a service/port pair, try to find a matching endpoint URL.
     * @param identifier Port identifier
     * @return an endpoint URL or null if no match was found.
     */
    @Override
    public String findUpdatedURL(final PortIdentifier identifier) {

        IWebServiceEndpoint matching = endpoints.get(identifier);
        if (matching != null) {
            return matching.getPortMetaData().getEndpointURL();
        }
        return null;
    }

    /**
     * @return the name of this container (WSDL location)
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * @return All the inner endpoints.
     */
    @Override
    public Collection<IWebServiceEndpoint> getEndpoints() {
        return endpoints.values();
    }

    /**
     * Starts the container.
     * Recursively starts inner endpoints.
     */
    @Override
    public void start() {

        logger.debug("Start WebservicesContainer[{0}]", name);
        for (IWebServiceEndpoint endpoint : endpoints.values()) {
            endpoint.start();
        }

    }

    /**
     * Stops the container.
     * Recursively stops inner endpoints.
     */
    @Override
    public void stop() {

        logger.debug("Stop WebservicesContainer[{0}]", name);
        for (IWebServiceEndpoint endpoint : endpoints.values()) {
            endpoint.stop();
        }

        // Stop the Bus of the container
        // Bus start/stop operations are extra verbose
        Level old = setComponentLogLevel("org.apache.cxf", BasicLevel.LEVEL_ERROR);
        Level old2 = setComponentLogLevel("org.springframework", BasicLevel.LEVEL_ERROR);

        this.bus.shutdown(true);

        // Reset log levels
        setComponentLogLevel("org.apache.cxf", old);
        setComponentLogLevel("org.springframework", old2);

    }

    /**
     * Set the WSDL publication directory for this WSDL.
     * @param wsdlPublicationDirectory directory
     */
    @Override
    public void setWsdlPublicationDirectory(final String wsdlPublicationDirectory) {
        this.wsdlPublicationDirectory = wsdlPublicationDirectory;
    }

    /**
     * @return the WSDL publication directory for this WSDL (may be null).
     */
    @Override
    public String getWsdlPublicationDirectory() {
        return wsdlPublicationDirectory;
    }


    /**
     * @return list of publishable definition that will be used by the publisher
     */
    public List<PublishableDefinition> getPublishableDefinitions() {
        // Get WSDL location
        String wsdlLocation = getName();

        // Only 1 definition if there is a WSDL location, else 1 definition by endpoint
        List<PublishableDefinition> publishableDefinitionList = new ArrayList<PublishableDefinition>();

        // Two cases : with or without a WSDL
        if (wsdlLocation != null && !"".equals(wsdlLocation)) {
            // 1st case : With a WSDL location
            PublishableDefinition publishableDefinition = new PublishableDefinition();
            publishableDefinitionList.add(publishableDefinition);

            // There is a WSDL, only one definition and publication info
            PublicationInfo publicationInfo = new PublicationInfo();
            publicationInfo.setOriginalWsdlFilename(wsdlLocation);
            publishableDefinition.setPublicationInfo(publicationInfo);

            // Get the WSDL manager
            WSDLManager wsdlManager = bus.getExtension(WSDLManager.class);
            try {
                publishableDefinition.setDefinition(wsdlManager.getDefinition(wsdlLocation));
            } catch (WSDLException e) {
                logger.error("Unable to load WSDL ''{0}''", wsdlLocation, e);
            }

        } else {
            // No WSDL, needs to publish a WSDL for each endpoint
            Collection<IWebServiceEndpoint> endPoints = getEndpoints();
            if (endPoints != null) {
                for (IWebServiceEndpoint endpoint : endPoints) {
                    if (endpoint instanceof CXFWSEndpoint) {
                        CXFWSEndpoint cxfEndPoint = (CXFWSEndpoint) endpoint;

                        PublishableDefinition publishableDefinition = new PublishableDefinition();
                        JOnASDestination destination = cxfEndPoint.getDestination();
                        ServiceInfo serviceInfo = destination.getEndpointInfo().getService();

                        // Publication info
                        PublicationInfo publicationInfo = new PublicationInfo();
                        publishableDefinition.setPublicationInfo(publicationInfo);
                        // Set a name based on the service name
                        publicationInfo.setOriginalWsdlFilename(serviceInfo.getName().getLocalPart() + ".wsdl");



                        // Definition
                        try {
                            ServiceWSDLBuilder builder = new ServiceWSDLBuilder(bus, serviceInfo);
                            publishableDefinition.setDefinition(builder.build());
                        } catch (WSDLException e) {
                            logger.error("Unable to load WSDL ''{0}''", wsdlLocation, e);
                        }
                    }
                }
            }
        }

        // Update publication directory if container contains one
        String directory = getWsdlPublicationDirectory();
        if (directory != null) {
            File dir = new File(directory);
            for (PublishableDefinition publishableDefinition : publishableDefinitionList) {
                publishableDefinition.getPublicationInfo().setPublicationDirectory(dir);

            }
        }

        // Update definition
        for (PublishableDefinition publishableDefinition : publishableDefinitionList) {
            WSDLUtils.updateEndpointAddresses(publishableDefinition.getDefinition(), this);
        }

        return publishableDefinitionList;
    }
}
