/**
 * 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$
 * --------------------------------------------------------------------------
*/

package org.ow2.jonas.ws.publish.internal.registry;

import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.wsdl.Definition;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.xml.registry.BulkResponse;
import javax.xml.registry.BusinessLifeCycleManager;
import javax.xml.registry.BusinessQueryManager;
import javax.xml.registry.Connection;
import javax.xml.registry.ConnectionFactory;
import javax.xml.registry.JAXRException;
import javax.xml.registry.LifeCycleManager;
import javax.xml.registry.RegistryException;
import javax.xml.registry.RegistryService;
import javax.xml.registry.infomodel.Classification;
import javax.xml.registry.infomodel.ClassificationScheme;
import javax.xml.registry.infomodel.Concept;
import javax.xml.registry.infomodel.ExternalLink;
import javax.xml.registry.infomodel.InternationalString;
import javax.xml.registry.infomodel.Key;
import javax.xml.registry.infomodel.Organization;
import javax.xml.registry.infomodel.PersonName;
import javax.xml.registry.infomodel.Service;
import javax.xml.registry.infomodel.ServiceBinding;
import javax.xml.registry.infomodel.SpecificationLink;
import javax.xml.registry.infomodel.User;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.lib.util.I18n;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.ws.publish.AbsWSDLPublisher;
import org.ow2.jonas.ws.publish.PublicationInfo;
import org.ow2.jonas.ws.publish.PublishableDefinition;
import org.ow2.jonas.ws.publish.WSDLPublisher;
import org.ow2.jonas.ws.publish.WSDLPublisherException;
import org.w3c.dom.Element;

/**
 * Publish a given WSDL Definition into a specified WebServices registry (UDDI/ebXML).
 * Use JAXR (only UDDI At this time).
 * properties :<br/>
 * - <code>jonas.service.publish.uddi.username</code> : publisher username<br/>
 * - <code>jonas.service.publish.uddi.password</code> : publisher password<br/>
 * - <code>javax.xml.registry.lifeCycleManagerURL</code> : url of the Publish API for a registry<br/>
 * - <code>javax.xml.registry.queryManagerURL</code> : url of the Query API for a registry<br/>
 * - <code>jonas.service.publish.uddi.organization.name</code> : The organisation name<br/>
 * - <code>jonas.service.publish.uddi.organization.desc</code> : The organization description (optionnal)<br/>
 * - <code>jonas.service.publish.uddi.organization.person_name</code> : Primary Contact for the organization<br/>
 * The property file is given 'as is' to the ConnectionFactory, If you want to add some JAXR specific
 * properties, add them in this property file.
 *
 * @author Guillaume Sauthier
 */
public class RegistryWSDLPublisher extends AbsWSDLPublisher implements WSDLPublisher {

    /**
     * Standard Publish URL
     */
    private static final String PUBLISH_URL_PROP
        = "javax.xml.registry.lifeCycleManagerURL";

    /**
     * Standard Inquiry URL
     */
    private static final String INQUIRY_URL_PROP
        = "javax.xml.registry.queryManagerURL";

    /**
     * JOnAS Username
     */
    private static final String USERNAME
        = "jonas.service.publish.uddi.username";

    /**
     * JOnAS Password
     */
    private static final String PASSWORD
        = "jonas.service.publish.uddi.password";

    /**
     * JOnAS Organization name
     */
    private static final String ORGANIZATION_NAME
        = "jonas.service.publish.uddi.organization.name";

    /**
     * JOnAS Organization description
     */
    private static final String ORGANIZATION_DESC
        = "jonas.service.publish.uddi.organization.desc";

    /**
     * JOnAS Person under wich WSDL will be published
     */
    private static final String PERSON_NAME
        = "jonas.service.publish.uddi.organization.person_name";


    /** JAXR ConnectionFactory to use */
    private ConnectionFactory connFactory;

    /** username/password pair */
    private PasswordAuthentication passAuth;

    /** list of properties */
    private Properties props;

    /** Publication Logger (special file storing keys) */
    private static Logger logger = Log.getLogger("org.ow2.jonas.ws.wsdl");

    /** Internationalisation tool */
    private static I18n i18n = I18n.getInstance(RegistryWSDLPublisher.class);

    /**
     * Constructor : creates a RegistryWSDLHandler object.
     *
     * @param props uddi.properties file
     *
     * @throws WSDLPublisherException When some properties are missing
     */
    public RegistryWSDLPublisher(final Properties props) throws WSDLPublisherException {

        this.props = props;

        //Check minimal properties
        String publishURL = props.getProperty(PUBLISH_URL_PROP);
        String queryURL = props.getProperty(INQUIRY_URL_PROP);
        String username = props.getProperty(USERNAME);
        String password = props.getProperty(PASSWORD);

        // Check if publishURL is an URL
        try {
            new URL(publishURL);
        } catch (MalformedURLException mue) {
            String err = i18n.getMessage("RegistryWSDLHandler.constr.mue", publishURL);
            logger.log(BasicLevel.ERROR, err);
            throw new WSDLPublisherException(err, mue);
        }

        try {
            new URL(queryURL);
        } catch (MalformedURLException mue) {
            String err = i18n.getMessage("RegistryWSDLHandler.constr.mue", queryURL);
            logger.log(BasicLevel.ERROR, err);
            throw new WSDLPublisherException(err, mue);
        }

        try {
            // Create the ConnectionFactory
            connFactory = ConnectionFactory.newInstance();
            connFactory.setProperties(props);
        } catch (JAXRException jaxre) {
            String err = i18n.getMessage("RegistryWSDLHandler.constr.jaxrException");
            logger.log(BasicLevel.ERROR, err);
            throw new WSDLPublisherException(err, jaxre);
        }

        // Create the Authentication
        if (username != null && password != null) {
            passAuth = new PasswordAuthentication(username, password.toCharArray());
        } else {
            String err = i18n.getMessage("RegistryWSDLHandler.constr.noCreds");
            logger.log(BasicLevel.ERROR, err);
            throw new WSDLPublisherException(err);
        }

        // Check organization props
        String name = props.getProperty(ORGANIZATION_NAME);
        String person = props.getProperty(PERSON_NAME);
        if ((name == null) || (person == null)) {
            String err = i18n.getMessage("RegistryWSDLPublisher.constr.missingProp");
            logger.log(BasicLevel.ERROR, err);
            throw new WSDLPublisherException(err);
        }
    }

    /**
     * publish the given wsdl
     * @param publishableDefinition the data containing the WSDL file to be
     *        published and the publication info
     * @throws WSDLPublisherException if an error occurs at the publishing.
     */
    public void publish(final PublishableDefinition publishableDefinition) throws WSDLPublisherException {

        Definition definition = publishableDefinition.getDefinition();
        PublicationInfo info = publishableDefinition.getPublicationInfo();
        try {

            // Create the connection
            Connection connection = connFactory.createConnection();

            // Add Credentials
            Set creds = new HashSet();
            creds.add(passAuth);
            connection.setCredentials(creds);

            // Get the registry Service
            RegistryService rs = connection.getRegistryService();

            // get the Query API Manager
            BusinessQueryManager bqm = rs.getBusinessQueryManager();

            // get the Publish API Manager
            BusinessLifeCycleManager blcm = rs.getBusinessLifeCycleManager();

            // Reference a unique InternationalString
            InternationalString is = null;

            // Create the Organization with properties
            Organization org = blcm.createOrganization(props.getProperty(ORGANIZATION_NAME));
            String orgDesc = props.getProperty(ORGANIZATION_DESC);
            if (orgDesc != null) {
                is = blcm.createInternationalString(orgDesc);
                org.setDescription(is);
            }
            User contact = blcm.createUser();
            PersonName pName = blcm.createPersonName(props.getProperty(PERSON_NAME));
            contact.setPersonName(pName);
            org.setPrimaryContact(contact);

            Collection services = new ArrayList();
            // Create n Service (1 for each service)
            for (Iterator servicesIter = definition.getServices().values().iterator();
                 servicesIter.hasNext();) {

                javax.wsdl.Service s = (javax.wsdl.Service) servicesIter.next();
                Service service = blcm.createService(s.getQName().getLocalPart());

                Element desc = s.getDocumentationElement();
                if (desc != null) {
                    String description = getDescription(desc);
                    is = blcm.createInternationalString(description);
                    service.setDescription(is);
                }

                // Create n ServiceBinding (1 for each port)
                Collection serviceBindings = new ArrayList();
                for (Iterator portsIter = s.getPorts().values().iterator();
                     portsIter.hasNext();) {

                    javax.wsdl.Port port = (javax.wsdl.Port) portsIter.next();

                    ServiceBinding binding = blcm.createServiceBinding();

                    Element pDesc = port.getDocumentationElement();
                    String pDescription = port.getName();
                    if (pDesc != null) {
                        pDescription += " " + getDescription(pDesc);
                    }
                    is = blcm.createInternationalString(pDescription);
                    binding.setDescription(is);

                    // we do not check URL validity because webapps
                    // are not deployed at this point.
                    binding.setValidateURI(false);
                    String url = getEndpoint(port);
                    binding.setAccessURI(url);

                    // Create the Concep for WSDL
                    Concept concept = blcm.createConcept(null, port.getName() + " Concept", "");
                    is = blcm.createInternationalString(pDescription + " Concept");
                    concept.setDescription(is);

                    ExternalLink extlink = blcm.createExternalLink("file://",
                                                                   "WSDL");
                    extlink.setValidateURI(false);
                    // !!! Axis specific !!!
                    extlink.setExternalURI(url + "?JWSDL");

                    concept.addExternalLink(extlink);

                    // The Concept is a WSDL Document
                    ClassificationScheme uddiOrgTypes =
                        bqm.findClassificationSchemeByName(null, "uddi-org:types");
                    Classification wsdlSpecClass = blcm.createClassification(uddiOrgTypes,
                                                                             "wsdlSpec",
                                                                             "wsdlSpec");
                    concept.addClassification(wsdlSpecClass);

                    // save into registry and get key
                    Collection concepts = new ArrayList();
                    concepts.add(concept);
                    BulkResponse br = blcm.saveConcepts(concepts);
                    String key = getKey(br);

                    // i18n RegistryWSDLPublisher.publish.conceptKey
                    String msg = i18n.getMessage("RegistryWSDLHandler.publish.conceptKey",
                                                 port.getName(),
                                                 key);
                    logger.log(BasicLevel.INFO, msg);

                    // get the concept from registry
                    Concept c = (Concept) bqm.getRegistryObject(key, LifeCycleManager.CONCEPT);

                    SpecificationLink specLink = blcm.createSpecificationLink();
                    specLink.setSpecificationObject(c);
                    binding.addSpecificationLink(specLink);

                    serviceBindings.add(binding);
                }
                service.addServiceBindings(serviceBindings);
                services.add(service);
            }
            // Save into the Organisation
            org.addServices(services);

            Collection orgs = new ArrayList();
            orgs.add(org);

            BulkResponse br = blcm.saveOrganizations(orgs);
            String key = getKey(br);

            // i18n RegistryWSDLPublisher.publish.organisationKey
            String msg = i18n.getMessage("RegistryWSDLPublisher.publish.organisationKey",
                                         org,
                                         key);
            logger.log(BasicLevel.INFO, msg);


        } catch (JAXRException jaxre) {
            String err = i18n.getMessage("RegistryWSDLHandler.publish.jaxrException");
            logger.log(BasicLevel.ERROR, err);
            throw new WSDLPublisherException(err, jaxre);
        }
    }

    /**
     * Return the <code>soap:location</code> of the Port.
     *
     * @param port the <code>wsdl:port</code> we want the endpoint URL.
     *
     * @return the endpoint URL.
     */
    private String getEndpoint(final javax.wsdl.Port port) {
        String url = null;
        List ee = port.getExtensibilityElements();
        for (Iterator ext = ee.iterator(); ext.hasNext();) {
            ExtensibilityElement elem = (ExtensibilityElement) ext.next();
            if (elem instanceof SOAPAddress) {
                SOAPAddress soap = (SOAPAddress) elem;
                url = soap.getLocationURI();
            }
        }
        return url;
    }

    /**
     * @param e Element containing Description
     * @return Returns the desciption of the given Element
     */
    private String getDescription(final Element e) {
        return e.getFirstChild().getNodeValue();
    }

    /**
     * Return the Key contained in the Registry response.
     *
     * @param br the Registry response
     *
     * @return the key contained in the response
     *
     * @throws WSDLPublisherException when no key returned or response
     *         wraps server side exceptions.
     * @throws JAXRException If no Key was returned by the server
     */
    private String getKey(final BulkResponse br)
        throws WSDLPublisherException, JAXRException {
        String keyStr = null;
        Collection exceptions = br.getExceptions();
        Key key = null;
        if (exceptions == null) {
            // save OK
            Collection keys = br.getCollection();
            Iterator keyIter = keys.iterator();
            if (keyIter.hasNext()) {
                // we have 1 key
                key = (Key) keyIter.next();
                keyStr = key.getId();
            } else {
                // no key
                // i18n RegistryWSDLPublisher.getKey.noKeyReturned
                String err = i18n.getMessage("RegistryWSDLHandler.getKey.noKeyReturned");
                logger.log(BasicLevel.ERROR, err);
                throw new WSDLPublisherException(err);
            }
        } else {

            // Exceptions in Server Side

            // i18n RegistryWSDLPublisher.getKey.serverSideExceptions
            String err = i18n.getMessage("RegistryWSDLHandler.getKey.serverSideExceptions");

            // Get Server Side Cause
            Iterator exIter = exceptions.iterator();
            Exception cause = null;
            if (exIter.hasNext()) {
                // we have 1 Exception
                cause = (RegistryException) exIter.next();
            }
            logger.log(BasicLevel.ERROR, err);
            if (cause == null) {
                throw new WSDLPublisherException(err);
            } else {
                throw new WSDLPublisherException(err, cause);
            }
        }

        return keyStr;
    }
}
