/*
 * $Id: XmlServlet.java,v 1.1.1.1 2002/09/30 15:08:52 smartine Exp $
 * Copyright (C) 1999-2000 David Brownell
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package xml.pipeline;

import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;

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

import org.xml.sax.*;
import org.w3c.dom.*;
import xml.*;


// $Id: XmlServlet.java,v 1.1.1.1 2002/09/30 15:08:52 smartine Exp $

/**
 * This base class provides utility functions making it easy for servlets to
 * send and receive XML data structures through an XML processing pipeline.
 *
 * <p> This means that either the request to the servlet, or its response,
 * or both of these, may be viewed as a stage in an XML processing pipeline.
 * The request will always be an input; the response, an output.  It's easy
 * to convert to DOM or text representations at any point.  Other types
 * of event producers and consumers can work with specialized representations
 * that have been tuned to application requirements.
 *
 * <p> Perhaps the simpest subclass would just add a method something like
 * the following one: <pre>
 *
 *    public void doPost (
 *        HttpServletRequest  request,
 *        HttpServletResponse response
 *    ) throws IOException, ServletException
 *    {
 *        EventProducer       producer = getXmlInput (request);
 *        EventConsumer       consumer = getXmlOutput (response);
 *
 *        consumer = PipelineFactory.createPipeline (
 *            " ... filter pipeline descriptor goes here ...",
 *            consumer);
 *        producer.produce (consumer);
 *    }
 * </pre>
 *
 * <p> Used in conjuction with a CallFilter, such a servlet could be used
 * by clients to pass data through processing components which need any
 * facilities (such as application resources, or mediated interactions)
 * which are not available to that client.
 *
 * @see CallFilter
 * @see PipelineFactory
 *
 * @author David Brownell
 * @version $Date: 2002/09/30 15:08:52 $
 */
abstract public class XmlServlet extends HttpServlet
{
    // XXX need to have a way to offer parsing options for inputs;
    // e.g. validation, entity resolution, etc.  Provide a parser!

    
    // servlet holds task-wide defaults for these
    // ... won't rely on serialization for servlets!
    private transient EntityResolver	resolver;
    private transient ErrorHandler	errHandler;


    /**
     * The default constructor does nothing.
     */
    public XmlServlet () { }


    /**
     * Returns the XML input to this servlet in the form of an event
     * producer, which will fire a stream of events to a event consumer.
     * This assumes that the body of the request is an XML document;
     * do not invoke this method if that is not the case.
     *
     * <p><em>NOTE:</em>  No multipart mime support at this time.</p>
     */
    public EventProducer	getXmlInput (HttpServletRequest req)
    throws IOException, SAXException
    {
	InputSource 	in = new InputSource (req.getInputStream ());
	EventProducer	producer;

	in.setEncoding (Resolver.getEncoding (req.getContentType ()));
	producer = new EventProducer (in);
	producer.setEntityResolver (resolver);
	producer.setErrorHandler (errHandler);
	return producer;
    }

    private static final String HANDLER = "http://xml.org/sax/properties/";

    /**
     * Returns an event consumer which will treat all of its input
     * callbacks as requests to write that portion of an XML document
     * to the specified response.
     */
    public TextConsumer	getXmlOutput (HttpServletResponse res)
    throws IOException
    {
	//
	// NOTE:  some older JVMs have a bug where "UTF-8" is rejected.
	//
	res.setContentType ("application/xml;charset=UTF-8");
	return new TextConsumer (res.getWriter ());
    }

    /**
     * Returns an event consumer which will treat all of its input
     * callbacks as requests to write that portion of an XHTML document
     * to the specified response.
     */
    public TextConsumer	getXhtmlOutput (HttpServletResponse res)
    throws IOException
    {
	//
	// NOTE:  some older JVMs have a bug where "US-ASCII" is rejected.
	//
	res.setContentType ("text/html;charset=US-ASCII");
	return new TextConsumer (res.getWriter (), true);
    }

    /**
     * Returns the input document provided in this request in the form of
     * a single DOM Document.
     */
    public Document	getDocument (HttpServletRequest req)
    throws IOException, SAXException
    {
	EventProducer	producer = getXmlInput (req);
	DomConsumer		consumer = new DomConsumer ();

	producer.setEntityResolver (resolver);
	producer.setErrorHandler (errHandler);
	producer.produce (consumer);
	return consumer.getDocument ();
    }

    /**
     * Writes the response of this servlet as an XML document,
     * encoded in UTF-8 and with the correct MIME type.
     */
    public void	writeOutput (HttpServletResponse res, Document document)
    throws IOException, SAXException
    {
	EventConsumer	consumer = getXmlOutput (res);
	EventProducer	producer;

	producer = new EventProducer (
	    new DomParser2 (document),
	    new InputSource ("this is ignored")
	    );
	producer.setEntityResolver (resolver);
	producer.setErrorHandler (errHandler);
	producer.produce (consumer);
    }

    /**
     * Returns an entity resolver that is set up with some locally
     * established mappings from public identifiers to files.
     *
     * <p> This uses the <b>xhtml1.dtd-dir</b> application parameter
     * (context attribute) to identify the name of a directory with
     * a local copy of the XHTML1 DTDs.  (Subject to change.)
     */
    public EntityResolver getEntityResolver ()
    {
	if (resolver == null) {
	    ServletContext	context = getServletContext ();
	    String		xhtmlDir;
	    
	    //
	    // get a resolver that's preloaded for XHTML DTD stuff, if
	    // possible, using application-wide parameters.
	    //
	    xhtmlDir = (String) context.getAttribute ("xhtml1.dtd-dir");

	    if (xhtmlDir != null) {
		try {
		    resolver = Resolver.createXhtmlResolver (xhtmlDir);
		} catch (IOException e) {
		    // whoops -- log?
		}
	    }

	    // XXX build with a copy of a hashtable that we maintain,
	    // loading it with various mappings ... xhtml, docbk, etc
	}
	return resolver;
    }


    /**
     * Assigns an error handler that will, by default, be provided to
     * event producers.  If this is not done, or if a null value
     * is assigned, a default value may be established with mappings from
     * public identifiers to local files.
     */
    public void setEntityResolver (EntityResolver r)
    {
	resolver = r;
    }


    /**
     * Returns any error handler that will, by default, be provided to
     * event producers.
     */
    public ErrorHandler getErrorHandler ()
    {
	return errHandler;
    }
    

    /**
     * Assigns an error handler that will, by default, be provided to
     * event producers.  Since this will be shared among all pipelines
     * and threads, use caution when accessing state from such a handler.
     */
    public void setErrorHandler (ErrorHandler e)
    {
	errHandler = e;
    }
    

    // XXX fixURI ...
    // XXX safe way to add a URI param for embedding?
}
