/////////////////////////////////////////////////////////////////////////
//
// © University of Southampton IT Innovation Centre, 2004
//
// Copyright in this library belongs to the IT Innovation Centre of
// 2 Venture Road, Chilworth Science Park, Southampton, SO16 7NP, UK.
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//      Created by:             Darren Marvin
//      Created date:           2004/04/30
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

package uk.ac.soton.itinnovation.gemss.transportmessaging.connection;

import eu.gemss.components.transport.servicedescription.*;
import eu.gemss.components.transport.servicedescription.endpointtypes.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.*;
import javax.wsdl.*;
import javax.wsdl.extensions.soap.*;
import javax.wsdl.factory.*;
import javax.xml.namespace.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.configuration.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.connection.file.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.connection.http.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.connection.https.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.messaging.context.*;

public class ConnectionFactory {

    private static Logger mLogger = Logger.getLogger("uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ConnectionFactory");
    private static ConnectionFactory mFactoryInstance;
    private static TransportMessagingConfiguration mConfig;

    private ConnectionFactory() {

    }

    private ConnectionFactory(TransportMessagingConfiguration config) {
        mConfig = config;
    }

    public static synchronized ConnectionFactory getInstance() {
        if(mFactoryInstance==null)
            mFactoryInstance = new ConnectionFactory();
        return mFactoryInstance;
    }

    public static synchronized ConnectionFactory getInstance(TransportMessagingConfiguration config) {
        if(mFactoryInstance==null)
            mFactoryInstance = new ConnectionFactory(config);
        return mFactoryInstance;
    }

    /*
    public void initialise(TransportMessagingConfiguration config) {
        if(mFactoryInstance==null)
            mFactoryInstance = new ConnectionFactory(config);
    }
    */
    public Connection createConnection(String URI,SecurityContext secCtx) throws ConnectionException {
        try{
            return createConnection(new URL(URI),secCtx);
        }
        catch(java.net.MalformedURLException ex) {
            mLogger.log(Level.SEVERE,"Endpoint for the WSDL '" + URI +"' is invalid",ex);
            throw new ConnectionException("Endpoint for the WSDL '" + URI +"' is invalid");
        }
    }

    public Connection createConnection(URL URL,SecurityContext secCtx) throws ConnectionException {
        //try to workout the protocol from the URI

        if(URL.toExternalForm().startsWith("https")) {
            if(mConfig==null)
                return new HTTPSConnection(URL,secCtx);
            else
                return new HTTPSConnection(mConfig,URL,secCtx);
        }
        else if(URL.toExternalForm().startsWith("http")) {
            //it is either a standard http connection or https connection
            if(mConfig==null)
                return new HTTPConnection(URL,secCtx);
            else
                return new HTTPConnection(mConfig,URL,secCtx);
        }
        else if(URL.toExternalForm().startsWith("file")) {
            return new FileConnection(URL);
        }
        else {
            mLogger.log(Level.SEVERE,"Unsupported protocol type for service endpoint with URI '" + URL.toExternalForm() + "'");
            throw new ConnectionException("Unsupported protocol type for service endpoint with URI '" + URL.toExternalForm() + "'");
        }
    }

    public Connection createConnection(ServiceEndpoint endpoint,SecurityContext secCtx) throws ConnectionException {
        try{
            if(endpoint==null || secCtx == null) {
                mLogger.log(Level.SEVERE,"One of either endpoint or security context is null");
                throw new ConnectionException("Serious failure, please send a bug report including all log files");
            }
            //checks to see what type of endpoint it is and then tries to create a suitable connection
            Connection connection = null;
            if(endpoint instanceof WSDLEndpoint) {
                //use its contents to create a standard connection
                WSDLEndpoint endp = (WSDLEndpoint)  endpoint;
                if(!endp.endpointSet()) {
                    //try to obtain the URI from the document in the uri
                    Document domDoc = null;
                    if(endp.WSDLDocCached()) {
                        domDoc = endp.getWSDLDocument();
                    }
                    else {
                        //grab the WSDL first
                        if(!endp.wsdlURISet()) {
                            mLogger.log(Level.SEVERE,"Service endpoint not supplied and cannot resolve it from other information given");
                            throw new ConnectionException("Service endpoint not supplied and cannot resolve it from other information given");
                        }
                        else {
                            Connection connectionTmp = createConnection(endp.getWSDLURI(),secCtx);
                            InputStream inStream = connectionTmp.getResourceInputStream();
                            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                            factory.setNamespaceAware(true);//this is extremely important for WSDL validation using WSDL4J
                            factory.setValidating(false);
                            DocumentBuilder builder = factory.newDocumentBuilder();
                            domDoc = builder.parse(inStream);
                            endp.setWSDLDocument(domDoc);
                        }
                    }
                    //get the service endpoint
                    String serviceEndpoint = getEndpointFromWSDL(endp);
                    if(serviceEndpoint!=null) {
                        endp.setEndpointURI(new URL(serviceEndpoint));
                    }
                    else {
                        mLogger.log(Level.SEVERE,"Unable to retrieve soap endpoint address from WSDL");
                        throw new ConnectionException("Unable to retrieve soap endpoint address from WSDL, more information in logs.");
                    }
                }
                connection = createConnection(endp.getEndpointURL(),secCtx);

            }
            else {
                mLogger.log(Level.SEVERE,"Unsupported service endpoint requested, supplied class '" + endpoint.getClass().getName() +"'");
                throw new ConnectionException("Unsupported service endpoint requested, more information in logs.");
            }
            return connection;
        }
        catch(java.io.IOException ex) {
            mLogger.log(Level.SEVERE,"Unable to create connection possibly due to network failure",ex);
            throw new ConnectionException("Unable to create connection possibly due to network failure, more information in logs.");
        }
        catch(javax.xml.parsers.ParserConfigurationException ex) {
            mLogger.log(Level.SEVERE,"Unable to create because could not read endpoint WSDL document",ex);
            throw new ConnectionException("Unable to create because could not read endpoint WSDL document, more information in logs.");
        }
        catch(org.xml.sax.SAXException ex) {
            mLogger.log(Level.SEVERE,"Unable to create because could not read endpoint WSDL document",ex);
            throw new ConnectionException("Unable to create because could not read endpoint WSDL document, more information in logs.");
        }
    }

    private String getEndpointFromWSDL(WSDLEndpoint endp) throws ConnectionException {
        try{
            String endPointURI = null;
            javax.wsdl.xml.WSDLReader wsdlReader = WSDLFactory.newInstance().newWSDLReader();
            Definition defn = wsdlReader.readWSDL(null,endp.getWSDLDocument());
            if(endp.serviceNameSet() && endp.portNameSet()) {

                Service service = defn.getService(new QName(defn.getTargetNamespace(),endp.getServiceName()));
                if(service==null) {
                    mLogger.log(Level.SEVERE,"Could not match the supplied endpoint service name '" + endp.getServiceName() + "' one in the WSDL definition");
                    throw new ConnectionException("Could not match the supplied endpoint service name with one in the WSDL definition");
                }
                Port port = service.getPort(endp.getPortName());
                if(port==null) {
                    mLogger.log(Level.SEVERE,"Could not match the supplied endpoint port name '" + endp.getPortName() + "' one in the WSDL definition");
                    throw new ConnectionException("Could not match the supplied endpoint port name with one in the WSDL definition");
                }
                List elements = port.getExtensibilityElements();
                Iterator iterator = elements.iterator();
                while(iterator.hasNext()) {
                    //looking for SOAP Address extension, should only be one
                    Object obj = iterator.next();
                    if(obj instanceof SOAPAddress) {
                        //use it
                        SOAPAddress address = (SOAPAddress) obj;
                        endPointURI = address.getLocationURI();
                    }
                }
            }
            return endPointURI;
        }
        catch(javax.wsdl.WSDLException ex) {
            mLogger.log(Level.SEVERE,"Unable to retrieve soap endpoint address from WSDL",ex);
            throw new ConnectionException("Unable to retrieve soap endpoint address from WSDL, more information in logs.");
        }
    }
}
