/*
 * @(#)XmlRpcClient.java	1.9 99/01/06
 *
 * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;

import java.net.URL;
import java.net.HttpURLConnection;
import java.net.URLConnection;

import java.util.Dictionary;

import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sun.xml.parser.Parser;
import com.sun.xml.parser.Resolver;
import com.sun.xml.parser.ValidatingParser;

import com.sun.xml.tree.SimpleElementFactory;
import com.sun.xml.tree.XmlDocumentBuilder;
import com.sun.xml.tree.XmlDocument;


/**
 * This class facilitates development of XML messaging clients which use
 * HTTP(S) POSTing to synchronously exchange XML documents.  This may
 * be used directly, or be subclassed to add application-specific behaviours
 * such as specialized processing for some element vocabularies found in
 * those documents.
 *
 * @version 1.9
 */
public class XmlRpcClient
{
    private URL				url;

    private boolean			checkTypes = true;
    private SimpleElementFactory	factory = null;
    private boolean			trustDocuments = false;
    private EntityResolver		resolver;

    /**
     * Constructs a client; its URL must be set later.
     */
    public XmlRpcClient ()
	{ }

    /**
     * Constructs a client, and sets its URL.
     */
    public XmlRpcClient (URL url)
	{ setUrl (url); }

    /**
     * Sets the URL for which this client is a proxy (stand-in); this is
     * a write-once attribute;
     *
     * @param url the URL to which requests will be sent.
     * @exception IllegalStateException if the URL is already assigned
     * @exception IllegalArgumentException if the URL scheme is not
     *	"http" or "https".
     */
    public void setUrl (URL url)
    {
	if (this.url != null)
	    throw new IllegalStateException ("URL is already set");

	String	scheme = url.getProtocol ();

	if (!("http".equals (scheme) || "https".equals (scheme)))
	    throw new IllegalArgumentException (
		"not an HTTP/HTTPS URL: " + url);
	this.url = url;
    }


    /**
     * Returns the URL for which this client is a proxy.
     */
    public URL getUrl () { return url; }


    /**
     * This method sends the HTTP/HTTPS POST request to the object
     * identified by the URL passed to the constructor.  Subclasses
     * (or wrapper classes) may choose to provide convenience methods
     * layered over this method, perhaps automatically constructing
     * request documents or performing partial response processing. 
     *
     * @param request the document sent to the object for processing
     * @returns response returned as the result of processing
     * @exception IllegalStateException if the URL is not assigned
     */
    public XmlDocument call (XmlDocument request)
    throws IOException
    {
	if (url == null)
	    throw new IllegalStateException ("URL is not set");

	//
	// Write the XML document as the POST request data.
	//
//	HttpURLConnection	conn;
	URLConnection		conn;
	Writer			out;

//	conn = (HttpURLConnection) url.openConnection ();
	conn = url.openConnection ();
	conn.setDoOutput (true);
	conn.setUseCaches (false);
//	conn.setRequestProperty ("Content-Type", "text/xml;charset=UTF-8");
	conn.setRequestProperty ("Content-Type", "text/xml;charset=ISO-8859-1");

//	out = new OutputStreamWriter (conn.getOutputStream (), "UTF-8");
	out = new OutputStreamWriter (conn.getOutputStream (), "ISO-8859-1");
	request.write (out);
	out.flush ();
	out.close ();	// ESSENTIAL!

	//
	// Parse the XML document in the POST response.
	//
	Parser			parser;
	XmlDocumentBuilder	builder;
	InputSource		in;

	if (checkTypes) {
	    parser = new ValidatingParser ();
	    parser.setErrorHandler (Errors.instance);
	} else {
	    parser = new Parser ();
	    parser.setFastStandalone (true);
	}

	builder = new XmlDocumentBuilder ();
	builder.setElementFactory (factory);
	parser.setDocumentHandler (builder);
	if (resolver == null)
	    resolver = new Resolver ();
	parser.setEntityResolver (resolver);

	in = Resolver.createInputSource (conn.getContentType (),
		conn.getInputStream (), true,
		url.getProtocol ());

	try {
	    parser.parse (in);

	} catch (SAXException e) {
	    Exception	x = e.getException ();

	    System.err.println ("Parse error on response:");
	    if (x == null) x = e;
	    e.printStackTrace ();

	}

	return builder.getDocument ();
    }


    /**
     * This method is used to indicate whether servers are controlled well
     * enough that the data they provide doesn't need the XML analogue of
     * static type checking: <em>validation</em>.  This is not the default,
     * since it's hard to establish such a high level of control in large
     * open systems, and weak type checking is a major source of errors in
     * all systems.  Even if validation isn't needed, errors in the well
     * formedness of the XML response document will always cause fatal errors
     * in the RPC invocation.
     */
    protected void setCheckTypes (boolean value)
	{ checkTypes = value; }

    /**
     * Returns true if all documents must be validated (the default),
     * or false if the servers are trusted to provide correct data
     */
    protected boolean getCheckTypes ()
	{ return checkTypes; }
    
    /**
     * The request document that's built from the request can be customized,
     * so that the generic DOM functionality can be augmented with behaviour
     * specific to each element type.  This method lets you describe the
     * basic customizations to be performed, and optionally enable documents
     * themselves to specify further customizations.
     *
     * @param factory used to acquire customized element nodes
     * @param trustDocuments if true, the document can add its own
     *	mappings from element names to Java element node classes
     */
    protected void customizeDocument (
	SimpleElementFactory	factory,
	boolean			trustDocuments
    ) {
	this.factory = factory;
	this.trustDocuments = trustDocuments;
    }

    /**
     * Subclasses can provide a customized entity resolver to be used when
     * resolving external entities such as DTDs.  Typically, DTDs will be
     * cached locally (perhaps as Java resources or files).  In some cases,
     * this handler may know how to handle other sorts of URI; for example,
     * URIs which indicate the XML-formatted results of a database query.
     */
    protected void customizeResolver (EntityResolver r)
	{ resolver = r; }
}
