/*
 * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
 * Copyright (C) 2007 - Javolution (http://javolution.org/)
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and distribute this software is
 * freely granted, provided that this notice is preserved.
 */
package javolution.xml.ws;

import java.io.IOException;

import java.lang.CharSequence;
import java.lang.UnsupportedOperationException;
import javolution.io.AppendableWriter;
import javolution.io.UTF8StreamWriter;
import javolution.text.Text;
import javolution.text.TextBuilder;
import javolution.xml.XMLObjectReader;
import javolution.xml.XMLObjectWriter;
import javolution.xml.stream.XMLStreamException;
import javolution.xml.stream.XMLStreamReader;
import javolution.xml.stream.XMLStreamWriter;

/**
 * <p> This class provides a simple web service client capable of leveraging  
 *     Javolution {@link javolution.xml XML marshalling/unmarshalling}.</p>
 *     
 * <p> Sub-classes may work from WSDL files, {@link javolution.xml.XMLFormat
 *     XMLFormat} or directly with the XML streams (StAX). For example:[code]
 *     private static class HelloWorld extends WebServiceClient  {
 *         protected void writeRequest(XMLObjectWriter out) throws XMLStreamException {
 *             XMLStreamWriter xml = out.getStreamWriter();
 *             xml.writeDefaultNamespace("http://www.openuri.org/");
 *             xml.writeEmptyElement("helloWorld"); // Operation name.
 *         }
 *         protected void readResponse(XMLObjectReader in) throws XMLStreamException {
 *             XMLStreamReader xml = in.getStreamReader();
 *             xml.require(START_ELEMENT, "http://www.openuri.org/", "string");
 *             xml.next(); // Move to character content.
 *             System.out.println(xml.getText());
 *         }
 *     }
 *     WebServiceClient ws = new HelloWorld().setAddress("http://acme.com:80/HelloWorld.jws");
 *     ws.invoke();
 *     
 *      > Hello World!
 *     [/code]</p>
 *     
 * <p><b>Note:</b> At this moment, this class is supported only on the J2SE 
 *                 platform. Soon, it will also be supported on mobile devices
 *                 through the CLDC/MIDP Generic Connection framework.</p>
 *           
 * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
 * @version 5.2, September 16, 2007
 */
public abstract class WebServiceClient {

    /**
     * Holds standard SOAP envelope prefix.
     */
    public static final String ENVELOPE_PREFIX = "env";

    /**
     * Holds standard SOAP envelope namespace.
     */
    public static final String ENVELOPE_URI = "http://schemas.xmlsoap.org/soap/envelope/";

    /**
     * Holds the URL (J2SE).
     */
    Object _url;

    /**
     * Default constructor (address not set).
     */
    public WebServiceClient() {
        /**/
        if (true) return;
        /**/
        throw new UnsupportedOperationException("J2ME Not Supported Yet");
    }

    /**
     * Sets the address of this web service.
     * 
     * @param address the service full address. 
     */
    public WebServiceClient setAddress(String address) {
        /**/    
        try {
            _url = new java.net.URL(address);
        } catch (java.net.MalformedURLException e) {
            throw new IllegalArgumentException("Malformed URL: " + address);
        }
        /**/
        return this;
    }

    /**
     * Invokes the web service.
     */
    public void invoke() throws IOException, XMLStreamException {
        try {
            // Formats the request message (we cannot write directly to 
            // the output stream because the http request requires the length.
            _out.setOutput(_buffer);
            _writer.setOutput(_out);
            final XMLStreamWriter xmlOut = _writer.getStreamWriter();
            xmlOut.setPrefix(csq(ENVELOPE_PREFIX), csq(ENVELOPE_URI));
            xmlOut.writeStartElement(csq(ENVELOPE_URI), csq("Envelope"));
            xmlOut.writeNamespace(csq(ENVELOPE_PREFIX), csq(ENVELOPE_URI));
            xmlOut.writeStartElement(csq(ENVELOPE_URI), csq("Header"));
            xmlOut.writeEndElement();
            xmlOut.writeStartElement(csq(ENVELOPE_URI), csq("Body"));
            writeRequest(_writer);
            _writer.close();

            // Sends the request.
            if (_url == null)
                throw new IOException("URL not set");
            /**/    
            java.net.HttpURLConnection http = (java.net.HttpURLConnection) 
               ((java.net.URL)_url).openConnection();
            http.setRequestProperty("Content-Length", String.valueOf(_buffer
                    .length()));
            http.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
            // httpConn.setRequestProperty("SOAPAction", "");
            http.setRequestMethod("POST");
            http.setDoOutput(true);
            http.setDoInput(true);
            _utf8Writer.setOutput(http.getOutputStream());
            /**/
            _buffer.print(_utf8Writer);
            _utf8Writer.close();

            // Reads the response.
            /**/    
            _reader.setInput(http.getInputStream());
            /**/
            final XMLStreamReader xmlIn = _reader.getStreamReader();
            while (xmlIn.hasNext()) {
                if ((xmlIn.next() == XMLStreamReader.START_ELEMENT)
                        && xmlIn.getLocalName().equals("Body")
                        && xmlIn.getNamespaceURI().equals(ENVELOPE_URI)) {
                    // Found body, position reader to next element.
                    xmlIn.next();
                    readResponse(_reader);
                    break;
                }
            }

        } finally {
            _reader.close();
            _writer.reset();
            _out.reset();
            _buffer.reset();
            _utf8Writer.reset();
            _reader.reset();
        }
    }
    private final TextBuilder _buffer = new TextBuilder();
    private final AppendableWriter _out = new AppendableWriter();
    private final XMLObjectWriter _writer = new XMLObjectWriter();
    private final UTF8StreamWriter _utf8Writer = new UTF8StreamWriter();
    private  final XMLObjectReader _reader = new XMLObjectReader();
    /**/
    
    /**
     * Writes the web service request (SOAP body).
     * 
     * @param out the XML object writer.
     */
    protected abstract void writeRequest(XMLObjectWriter out)
            throws XMLStreamException;

    /**
     * Reads the web service response (SOAP body). The default implementation
     * writes the body XML events to <code>System.out</code>.
     * 
     * @param in the XML object reader.
     */
    protected void readResponse(XMLObjectReader in) throws XMLStreamException {
        final XMLStreamReader xml = in.getStreamReader();
        while (xml.hasNext()) {
            switch (xml.next()) {
            case XMLStreamReader.START_DOCUMENT:
                System.out.println("Start Document");
                break;
            case XMLStreamReader.END_DOCUMENT:
                System.out.println("End Document.");
                break;
            case XMLStreamReader.START_ELEMENT:
                System.out.println("Start Element: " + xml.getLocalName() + "("
                        + xml.getNamespaceURI() + ")");
                for (int i = 0, n = xml.getAttributeCount(); i < n; i++) {
                    System.out.println("   Attribute: "
                            + xml.getAttributeLocalName(i) + "("
                            + xml.getAttributeNamespace(i) + "), Value: "
                            + xml.getAttributeValue(i));
                }
                break;
            case XMLStreamReader.END_ELEMENT:
                if (xml.getLocalName().equals("Body")
                        && xml.getNamespaceURI().equals(ENVELOPE_URI))
                    return; // End body.
                System.out.println("End Element: " + xml.getLocalName() + "("
                        + xml.getNamespaceURI() + ")");
                break;
            case XMLStreamReader.CHARACTERS:
                System.out.println("Characters: " + xml.getText());
                break;
            case XMLStreamReader.CDATA:
                System.out.println("CDATA: " + xml.getText());
                break;
            case XMLStreamReader.COMMENT:
                System.out.println("Comment: " + xml.getText());
                break;
            case XMLStreamReader.SPACE:
                System.out.println("Space");
                break;
            default:
                System.out.println(xml);
            }
        }

    }  
    
    // For J2ME compatiblity.
    private static final CharSequence csq(Object string) {
        return (string instanceof CharSequence) ? (CharSequence) string:
            Text.valueOf(string);
        
    }
}
