/*
 * @(#)XmlRpcServlet.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.
 */

package Fournisseur;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.io.FileWriter;

import java.util.Dictionary;

import javax.servlet.SingleThreadModel;
import javax.servlet.http.*;

import org.w3c.dom.Element;

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

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 servlet facilitates development of HTTP/HTTPS based XML messaging
 * services, which add application-specific behaviours and handle specific
 * kinds of request and response documents.  It is only "RPC" in the sense
 * that it is a synchronous exchange of structured messages, directed at
 * a specific object (identified by the target of the POST).
 *
 * @version 1.9
 */
abstract public class XmlRpcServlet
    extends HttpServlet
    implements SingleThreadModel
{
    private transient boolean			checkTypes = true;
    private transient SimpleElementFactory	factory;
    private transient boolean			trustDocuments = false;
    private transient EntityResolver		resolver;


    /**
     * The default constructor does nothing.  Subclasses should override it
     * to do whatever generic initialization they need, and should use the
     * <em>Servlet.init(ServletConfig config)</em> method to perform any
     * initialization that is substantial, or that is dependent on the
     * Application environment in which the servlet runs (exposed through
     * the ServletConfig interface).
     *
     * <P> Since this servlet implements the single thread model, it won't
     * handle more than one request at a time.
     */
    protected XmlRpcServlet ()
    {
    }


    /**
     * This method must be overridden in a subclass in order to handle
     * the XML messaging request.
     *
     * <P> A more functional version would provide session context to
     * the message handling code.
     *
     * @param pathInfo identifes the object being addressed, within the
     *	scope of the URIs handled by this servlet.
     * @param request XML Document describing the request
     * @return XML document describing the response
     */
    protected abstract XmlDocument rpc (
	String		pathInfo,
	XmlDocument	request
    );


    /**
     * This method is used to control whether client messages are type
     * checked (<em>validated</em>).  By default, they are checked, since
     * weak type checking is a major source of errors in larger systems.
     * Even if validation isn't needed, errors in the well formedness of the
     * XML response document will always cause fatal errors in the POST.
     */
    public void setCheckTypes (boolean value)
	{ checkTypes = value; }


    /**
     * Returns true (the default) if client messages are validated to
     * ensure that certain basic content errors are not made.
     */
    public 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.  This method MUST be
     * re-invoked if the servlet is deserialized.
     *
     * @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; }


    /**
     * Returns the XML document sent in the request.
     */
    protected XmlDocument getRequestDocument (
	HttpServletRequest	request
    ) throws IOException, SAXException
    {
	Parser			parser;
	XmlDocumentBuilder	builder;
	InputSource		in;

	//
	// Get the request document provided by the client, optionally
	// doing type checking and permitting customized DOM node types 
	//
	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 (request.getContentType (),
		request.getInputStream (), true,
		request.getScheme ());
	parser.parse (in);
	return builder.getDocument ();
    }


    /**
     * Sends the given XML document as the response to this request.
     */
    protected void sendResponseDocument (
	XmlDocument		doc,
	HttpServletResponse	response
    ) throws IOException
    {
	response.setContentType ("text/xml;charset=ISO-8859-1");
//	response.setContentType ("text/xml;charset=UTF-8");
	Writer out = response.getWriter ();
	doc.write (out, "ISO-8859-1");
//	doc.write (out);
    }


    /**
     * The POST input and output are normally both XML documents.
     * However, a subclass might override this method if it needed
     * flexibility in this area:  perhaps dispatching on the request
     * type, or needing request or response to not be XML.
     */
    public void doPost (
	HttpServletRequest	request,
	HttpServletResponse	response
    ) throws IOException
    {
	XmlDocument		doc;
/*
    try {
    FileWriter f = new FileWriter("d:/temp/voir.txt", true) ;
    f.write("Data = ") ;
	int c ;
	while ((c = request.getInputStream().read()) != -1)
		f.write(c) ;
    f.write("\r\n") ;
    f.flush() ;
    f.close() ;
    } catch (IOException e) {}
*/
	try {
	    doc = getRequestDocument (request);
	    doc = rpc (request.getPathInfo (), doc);
	    sendResponseDocument (doc, response);

	} catch (SAXException e) {
	    Exception	x = e.getException ();
	    String	message = "Parse Error with Request:\n";

	    if (x == null) x = e;
	    if (e instanceof SAXParseException) {
		SAXParseException spe = (SAXParseException) e;
		message += "** URI: " + spe.getSystemId () + "\n";
		message += "** Line: " + spe.getLineNumber ()  + "\n";
	    }

	    sendError (response, x, message);

	} catch (IOException e) {
	    sendError (response, e, "I/O error");
	}
    }


    //
    // Error ocurred; report it
    //
    private void sendError (
	HttpServletResponse	response,
	Exception		e,
	String			message
    ) throws IOException
    {
	XmlDocument	doc = new XmlDocument ();
	Element		root = doc.createElement ("error");
	StringWriter	writer = new StringWriter ();
	PrintWriter	w = new PrintWriter (writer);

	w.println (message);
	w = new PrintWriter (writer);
	e.printStackTrace (w);
	w.println ("\n");
	w.flush ();

	doc.appendChild (root);
	root.appendChild (doc.createTextNode (writer.toString ()));

	// ensure that if client validates, they can take the message
	doc.setDoctype (null, null,
	      "<!ELEMENT error (#PCDATA)>\n"
	    + "<!ATTLIST error xml:space (default|preserve) 'preserve'>\n");

	sendResponseDocument (doc, response);
    }
}
