package org.apache.cocoon.transformation;

import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.component.*;
import org.apache.avalon.framework.logger.Logger;
import org.apache.cocoon.xml.EmbeddedXMLPipe;
import org.apache.cocoon.xml.XMLUtils;
import org.apache.excalibur.xml.sax.SAXParser;

import org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.xml.dom.DOMBuilder;


import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.Locator;
import org.xml.sax.Attributes;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.*;
import java.util.*;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;


/**
 *A post client is a class that send an incoming xml document to
 *a URL, waits for the result and send it back down the pipe
 *
 *based on AbstractDOMTransformer
 * @author <a href="mailto:rossb@apache.org">Ross Burton</a>
 * @author <a href="mailto:brobertson@mta.ca">Bruce G. Robertson</a>
 * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
 * @version CVS $Id: AbstractDOMTransformer.java 30932 2004-07-29 17:35:38Z vgritsenko $
 *
 * Eric Boisvert
 * eboisvert@oricom.ca
 *
 */
public  class PostClient extends AbstractTransformer
        implements org.apache.cocoon.transformation.Transformer, DOMBuilder.Listener, Composable, Disposable, Recyclable {

    /**
     *  The SAX entity resolver
     */
    protected SourceResolver resolver;

    /**
     *  The request object model
     */
    protected Map objectModel;

    /**
     *  The URI requested
     */
    protected String source;
    
    protected String ns = "";

    /**
     *  Parameters in the sitemap
     */
    protected Parameters parameters;

	public static final String SERVER_PARAM = "server";
	public static final String wfsUri ="http://www.opengis.net/wfs";
	public static final String CONTENT_PARAM = "content-type";
	public String rootNode = null;
	
//	 Instance variables
	private String server;
	private String contentType;
	
	private InputStream WfsResponse;

	public class WfsXMLPipe extends EmbeddedXMLPipe
	{
		private Logger l = null;
		
		public void setLogger(Logger log)
		{
			l = log;
		}
		

		WfsXMLPipe(org.xml.sax.ContentHandler handler)
		{
			super(handler);
		}
		
		public void processingInstruction(int piTarget,int piData)
		throws java.lang.Exception
		{
			debug("remove PI");
		}
		
		private void debug(String message)
		{
			l.debug(message);
		}


		public void startElement(String uri, String loc, String raw, Attributes a) throws SAXException {
			// TODO Auto-generated method stub
			l.debug("push "+raw);
			super.startElement(uri, loc, raw, a);
		}


		public void endDocument() throws SAXException {
			// TODO Auto-generated method stub
			l.debug("end document");
			super.endDocument();
		}


		public void startDocument() throws SAXException {
			// TODO Auto-generated method stub
			l.debug("start document");
			super.startDocument();
		}


		public void endElement(String uri, String loc, String raw) throws SAXException {
			// TODO Auto-generated method stub
			l.debug("end push: "+ raw);
			super.endElement(uri, loc, raw);
		}
				
		
		
	}
    /**
     * A <code>ComponentManager</code> which is available for use.
     */
    protected ComponentManager manager;

    /**
     * The <code>DOMBuilder</code> used to build DOM tree out of
     *incoming SAX events.
     */
    protected DOMBuilder builder;


    public PostClient() {
        super();
        this.builder = new DOMBuilder(this);
    }

    /**
     * Set the component manager.
     */
    public void compose(ComponentManager manager) {
        this.manager = manager;
    }

    /**
     * Set the <code>SourceResolver</code>, objectModel <code>Map</code>,
     * the source and sitemap <code>Parameters</code> used to process the request.
     *
     * If you wish to process the parameters, override this method, call
     * <code>super()</code> and then add your code.
     */
    public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
            throws ProcessingException, SAXException, IOException {

        this.resolver = resolver;
        this.objectModel = objectModel;
        this.source = src;
        this.parameters = par;
        this.getSiteMapParameters(parameters);
    }

    /**
     * Recycle the component.
     */
    public void recycle() {
        this.resolver = null;
        this.source = null;
        this.objectModel = null;
        this.parameters = null;
    	WfsResponse=null;
        this.builder.recycle();
    }

    /**
     * dispose
     */
    public void dispose() {
        this.builder = null;
        this.manager = null;
    }

    /**
     * This method is called when the Document is finished.
     * @param doc The DOM Document object representing this SAX stream
     * @see org.apache.cocoon.xml.dom.DOMBuilder.Listener
     */
    public void notify(Document doc) throws SAXException {
//    	 TODO Auto-generated method stub
		//getLogger().debug(doctoString(doc));
		//getLogger().error("handleExtractedDocument");
		try
		{
			getLogger().debug("got the document");
			
			// call the remote server 
			this.executeRequest(doc);
			// WfsResponse is the connections inputStream
			
			if (WfsResponse != null)
			{
			getLogger().debug("Response is not null");
			Reader xmlReader = new InputStreamReader(WfsResponse);
			InputSource i = new InputSource(xmlReader);
			getLogger().debug("got the input source");
	           	SAXParser parser = (SAXParser)this.manager.lookup(SAXParser.ROLE);
	            	parser.parse(i, super.xmlConsumer);
			WfsResponse.close();
			this.manager.release((Component)parser);
			}
			else
			{
			// connection failed, send error message into pipe
			getLogger().debug("no response");
			String err = "<Error>Error connecting to WfsClient</Error>";
			InputSource src = new InputSource(new StringReader(err));
			SAXParser parser = (SAXParser)this.manager.lookup(SAXParser.ROLE);
		        parser.parse(src, super.xmlConsumer);
			this.manager.release((Component)parser);
			}
			
		}
		catch(Exception e)
		{
			getLogger().error("failed to connect to Wfs server ",e);
		
		}
		connection.disconnect();

    }
    
    
    private void getSiteMapParameters(Parameters params)
	{
//		 TODO Auto-generated method stub
		getLogger().debug("parameters");
		try
		{
		server = params.getParameter(PostClient.SERVER_PARAM);
		getLogger().debug(server);
		}
		catch(Exception e)
		{
			server="http://localhost";
			getLogger().error("failed to get server param",e);
		}
		try
		{
		contentType = params.getParameter(PostClient.CONTENT_PARAM);
		if (contentType == null || contentType.equals(""))
			contentType = "application/xml";
		getLogger().debug(server);
		}
		catch(Exception e)
		{
			contentType="application/xml";
		}
		
	}
    
    private String DomToString(Document doc)
    {
    	try
    	{
  
    	return XMLUtils.serializeNode(doc, XMLUtils.createPropertiesForXML(false));
    	}
    	 catch (ProcessingException e)
    	 {
    	 return "";
    	 }

    }
    
    /**
     * Turn the incoming XML document into a string a fix the namespace issue
     * The XMLUtils.serializeNode does not transfer the namespace declarations
     * , so during the DOM building process, the list of namespaces has been
     * collected in a local variable called ns. 
     */
    private String GetXmlRequest(Document doc)
    {
    	String req = this.DomToString(doc);
    	// now, I must figure out the name of the first node
    	Node n = doc.getFirstChild();
    	// insert the ns declaration into the first element
    	return req.replaceFirst(n.getLocalName(),n.getLocalName()+" "+ns);
    }
    
    private HttpURLConnection connection;
	private URL imsUrl;
	public void executeRequest(Document doc)
	{
		// establish the connection and send the XML over
		try
		{
		getLogger().debug(server);
		imsUrl = new URL(server);
		connection = (HttpURLConnection)imsUrl.openConnection();
		connection.setRequestMethod("POST");
		// this can be defined in the transformer's param
		connection.setRequestProperty("Content-Type",contentType);
		connection.setDoInput(true);
		connection.setDoOutput(true);
		
		// send the xml in the outputstream
		PrintStream ps = new PrintStream(connection.getOutputStream() );
		String req = GetXmlRequest(doc);
		// does not like CR apparently. -- but maybe it's 
		// just this particular server who is a**** retentive
		ps.print(req.trim().replaceAll("\n",""));
		// send to debug
		getLogger().debug(req);
		ps.close();
		connection.getOutputStream().close();
		
		// get the response
		WfsResponse = connection.getInputStream();
		//debug info 
		getLogger().debug("Response message:"+((HttpURLConnection)connection).getResponseMessage());
		getLogger().debug("Response method:"+((HttpURLConnection)connection).getRequestMethod());
		getLogger().debug("Response code:"+String.valueOf(((HttpURLConnection)connection).getResponseCode()));
		getLogger().debug("content length:"+String.valueOf(connection.getContentLength()));
		getLogger().debug("Content type:" + connection.getContentType());
		// debug info
		
        
		}
		catch(Exception e)
		{
			getLogger().error("exception in request",e);
			WfsResponse = null;
		}
		
		
	}

    
   


    //
    // SAX Methods. Send incoming SAX events to the DOMBuilder.
    //

    public void setDocumentLocator(Locator locator) {
        builder.setDocumentLocator(locator);
    }

    public void startDocument() throws SAXException {
        builder.startDocument();
    }

    public void endDocument() throws SAXException {
        builder.endDocument();
    }

    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        getLogger().debug(prefix+":"+uri);
        //keeps a collection of namespace declaration
        // because Dom serializer does not write back ns declarations
        
        if (prefix == null || prefix.equals(""))
        	ns += "xmlns"+"=\""+uri+"\" ";
        else
        ns += "xmlns:"+prefix+"=\""+uri+"\" ";
    	builder.startPrefixMapping(prefix, uri);
    }

    public void endPrefixMapping(String prefix) throws SAXException {
        getLogger().debug(prefix);
    	builder.endPrefixMapping(prefix);
    }

    public void startElement(String uri, String loc, String raw, Attributes a)
            throws SAXException {
        builder.startElement(uri, loc, raw, a);
    }

    public void endElement(String uri, String loc, String raw)
            throws SAXException {
        builder.endElement(uri, loc, raw);
    }

    public void characters(char c[], int start, int len)
            throws SAXException {
        builder.characters(c, start, len);
    }

    public void ignorableWhitespace(char c[], int start, int len)
            throws SAXException {
        builder.ignorableWhitespace(c, start, len);
    }

    public void processingInstruction(String target, String data)
            throws SAXException {
        builder.processingInstruction(target, data);
    }

    public void skippedEntity(String name)
            throws SAXException {
        builder.skippedEntity(name);
    }

    public void startDTD(String name, String publicId, String systemId)
            throws SAXException {
        builder.startDTD(name, publicId, systemId);
    }

    public void endDTD()
            throws SAXException {
        builder.endDTD();
    }

    public void startEntity(String name)
            throws SAXException {
        builder.startEntity(name);
    }

    public void endEntity(String name)
            throws SAXException {
        builder.endEntity(name);
    }

    public void startCDATA()
            throws SAXException {
        builder.startCDATA();
    }

    public void endCDATA()
            throws SAXException {
        builder.endCDATA();
    }

    public void comment(char ch[], int start, int len)
            throws SAXException {
        builder.comment(ch, start, len);
    }
}
