/*
* $Id: WSIncludeTransformer.java,v 1.36 2003/08/28 06:50:47 joe.latty Exp $
*/
package org.apache.cocoon.transformation;

//avalon
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.component.Component;
//cocoon
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.xml.XMLUtils;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.Session;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.components.source.SourceUtil;

//cocoon authentication
import org.apache.cocoon.webapps.authentication.AuthenticationManager;
import org.apache.cocoon.webapps.authentication.user.UserHandler;
import org.apache.cocoon.webapps.authentication.configuration.HandlerConfiguration;
import org.apache.cocoon.webapps.authentication.configuration.ApplicationConfiguration;

//excalibur
import org.apache.excalibur.xml.dom.DOMParser;
import org.apache.excalibur.source.SourceParameters;
import org.apache.excalibur.source.Source;


//w3c
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
//axis
import javax.xml.rpc.Call;
import javax.xml.rpc.Service;
import javax.xml.namespace.QName;

//java
import java.io.IOException;
import java.io.StringReader;
import java.util.Map;
import java.util.ArrayList;
import java.util.Iterator;
import java.net.URI;
//tias
import org.apache.cocoon.util.MemoryCache;
import org.apache.cocoon.util.HttpClientWrapper;
import org.apache.cocoon.util.HttpClientWrapperException;
import org.apache.cocoon.MessageConstants;

import org.apache.commons.httpclient.HttpClient;


/**
 * This transformer triggers for the element <code>wsinclude</code> in the
 * namespace "http://apache.org/cocoon/wsinclude/1.0".
 * The <code>service</code> elements contains the url which points to
 * a Web Service, the response of which will replace the wsinclude element.
 * The content of the <code>wsinclude</code> element is posted to the web service.
 * N.B. The XML is written directly to the Web Service InputStream and is not wrapped
 * in any request parameters. <p>
 * All config parameters for wsinclude can be passed as either sitemap parameters OR
 * child elements of the wsinclude element. <p>
 * Sitemap Example:
 * <pre>
 * &lt;map:transform type="wsinclude" &gt;
 *     &lt;!-- If server is specified then service is relative to the server,
 *     and the URI to the Web Service is contrcuted using both server and service.
 *     If server is not specified then service must be a full URI to the Web Service --&gt;
 *     &lt;map:parameter name="server" value="http://some.web.service/"/ &gt;
 *     &lt;map:parameter name="service" value="/myservice"/ &gt;
 *     &lt;map:parameter name="timeout" value="20000"/ &gt;
 *     &lt;!-- Optional defaults to errors --&gt;
 *     &lt;map:parameter name="errors" value="wsincludeMessages"/ &gt;
 *     &lt;!-- Optional, defaults to no wrapper element--&gt;
 *     &lt;map:parameter name="response" value="wsincludeResponse"/ &gt;
 *     &lt;!-- Optional defaults to false --&gt
 *     &lt;map:parameter name="relogin" value="true"/ &gt;
 *     &lt;!-- If relogin is true and the service returns the message indicated by
 *     this parameter, then an attempt is made to relogin to the service, and
 *     perform the original request again
 *     Defaults to: <code>&lt;message&gt;Not logged in&lt;/message&gt;</code>
 *     --&gt;
 *     &lt;map:parameter name="notLoggedIn" value="&amp;lt;message&amp;gt;Not logged in&amp;lt;/message&amp;gt;" /&gt;
 *     &lt;!-- Optional defaults to current application --&gt;
 *     &lt;map:parameter name="application" value="boris"/ &gt;
 *     &lt;!-- Optional defaults to 0, number of times to retry on fatal error --&gt;
 *     &lt;map:parameter name="retries" value="1"/ &gt;
 *     &lt;!-- SOAP specific, if present a SOAP call to the method is performed
 *     instead if a HTTP POST, the entire XML withing the wsinclude is sent as a
 *     single Sting parameter to the method--&gt;
 *     &lt;map:parameter name="soapMethod" value="login"/ &gt;
 * &lt;/map:transform>
 * </pre>
 * In the XML :
 * <pre>
 * &lt;?xml version="1.0" encoding="UTF-8"? &gt;
 * &lt;data&gt;
 * &lt;ws:wsinclude xmlns:ws="http://apache.org/cocoon/wsinclude/1.0" &gt;
 * &lt;Config_Request&gt;
 * &lt;Action ActionType="login"&gt;
 * &lt;ActionParams&gt;
 * &lt;ActionParam ParamName="name"&gt;tonyc&lt;/ActionParam&gt;
 * &lt;ActionParam ParamName="password"&gt;cabbage&lt;/ActionParam&gt;
 * &lt;ActionParam ParamName="product"&gt;TTOOLS&lt;/ActionParam&gt;
 * &lt;ActionParam ParamName="domain"/&gt;
 * &lt;/ActionParams&gt;
 * &lt;/Action&gt;
 * &lt;/Config_Request&gt;
 * &lt;/ws:wsinclude&gt;
 * &lt;/data&gt;
 * </pre>
 * The relogin parameter indicates that if a <code>&lt;message&gt;Not logged in&lt;/message&gt;</code> response is
 * received from the service, an attempt will be made to check the authentication
 * framework for the appropriate load resource uri for the CURRENT application and if found
 * call this uri.  If you wish to cause a login of a different application then you must
 * supply an 'application' parameter with the name of the application,  as defined in the
 * applications section of the top level sitemap
 *
 * @author  Stephen Burns
 */
public class WSIncludeTransformer extends AbstractSAXTransformer implements MessageConstants{

    //Constants
    /** Default error element name */
    public static final String DEFAULT_ERROR_ELEMENT="errors";
    /** Default tiemout */
    public static final int DEFAULT_TIMEOUT=10000;
    /** Default retries */
    public static final int DEFAULT_RETRIES=0;
    /** Default response element name */
    public static final String DEFAULT_RESPONSE_ELEMENT="response";
	/** Defines a default generic response that will be understood to mean the user has been logged out */
	public static String DEFAULT_NOT_LOGGED_IN_RESPONSE="<message>Not logged in</message>";

    //Date format
    private static java.text.SimpleDateFormat formatter=new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final int STATE_OUTSIDE   = 0;
    private static final int STATE_INSIDE    = 1;


    //Tags, used for both element names and/or sitemap parameter names
    public static final String WSINCLUDE_NAMESPACE_URI = "http://apache.org/cocoon/wsinclude/1.0";
    public static final String WSINCLUDE_INCLUDE_ELEMENT = "wsinclude";
    public static final String WSINCLUDE_SERVICE_ELEMENT = "service";
    public static final String WSINCLUDE_SERVER_ELEMENT = "server";
    public static final String WSINCLUDE_TIMEOUT_ELEMENT = "timeout";
    public static final String WSINCLUDE_CACHEID_ELEMENT = "cacheId";
    /**
     * Name of an element tag that will be used in the response to contain  any
     * error messages.  If no error messages then the tag is not included in the  response.
     */
    public static final String WSINCLUDE_ERROR_ELEMENT = DEFAULT_ERROR_ELEMENT;
    /**
     * Optional element, if specified the value is used as the name of an
     * element tag, in which the web service response is wrapped.
     * This tag contains attributes that have useful info such as the time
     * the request was made, and the time taken to process the request
     */
    public static final String WSINCLUDE_RESPONSE_ELEMENT = DEFAULT_RESPONSE_ELEMENT;
    /**
     * Optional element, if present, the contents of the wsinclude tag are sent as
     * a parameter to the soap method specified here
     */
    public static final String WSINCLUDE_SOAP_ELEMENT = "soapMethod";
    /**
    * Optional, defaults to false.  If supplied and set to true and the
    * WebService responds with a "Not logged in" response, an attempt
    * will made to relogin to the web service again, and then resubmit the original request.
    */
    public static final String WSINCLUDE_RELOGIN_ELEMENT = "relogin";
	/**
	* Optional, defaults to false. If supplied and set to true will reset the session in the
	* HttpClientWrapper in order to solve the problem with multiple host systems using the
	* same URI picking up forms out of session that belong to another host system.
	*/
	public static final String WSINCLUDE_RESET_ELEMENT = "reset";
    /**
    * Optional, defaults to 0.  If supplied and set to >0
    * the web service request will be retried up to this number of times.
    */
    public static final String WSINCLUDE_RETRIES_ELEMENT = "retries";
    /**
    * Optional,  Used in conjunction with relogin
    */
    public static final String WSINCLUDE_APPLICATION_ELEMENT = "application";
	/**
	* Optional.  Used in conjunction with relogin
	*/
	public static final String WSINCLUDE_HANDLER_ELEMENT = "handler";
    /**
     * Optional, the not logged in error message has a default value of
     * <code>&lt;message&gt;Not logged in&lt;/message&gt;</code>
     * but can be modified by either setting a sitemap parameter to the wsinclude
     * transform or within an element in the XML
     */
	public static final String WSINCLUDE_NOT_LOGGED_IN_ELEMENT="notLoggedIn";


    //instance variables

    /** The current state: STATE_ */
    protected int state;
    /** The current external web service */
    protected String server=null;
    protected String relService=null;
    /** Optional soapMethod */
    protected String soapMethod=null;
    /** Current timeout value allowed for web service, defaults to 10 seconds */
    protected int timeout=DEFAULT_TIMEOUT;
    /** Optional element, stored the name of a wrapper response element tag */
    protected String wsResponseElement=null;
    /** Contains the name of the errors tag, defaults to errors, e.g. messages */
    protected String wsErrorElement=DEFAULT_ERROR_ELEMENT;
    /** Stores any messages that need to be returned to the caller */
    protected ArrayList messages=new ArrayList();
    /** Optional cache id */
    protected String cacheId=null;
    /** Optional relogin */
    protected boolean relogin=false;
	/** Optional reset HttpClientWrapper session info */
	protected boolean reset=false;
    /** Optional retries */
    protected int retries=DEFAULT_RETRIES;
	/** Optional handler, used in conjunction with  relogin*/
	protected String handler=null;
    /** Optional application, used in conjunction with  relogin*/
    protected String application=null;
    /** Records the time the web service is called */
    protected java.util.Date startTime;
    /** Records the time the web service returns */
    protected java.util.Date endTime;
    /** Not logged in error message */
    protected String notLoggedInMsg=DEFAULT_NOT_LOGGED_IN_RESPONSE;


    /**
    * Constructor
    * Set the namespace for any tags that this transformer will recognise
    */
    public WSIncludeTransformer() {
        this.namespaceURI = WSINCLUDE_NAMESPACE_URI;
    }

    /**
     * Setup the component.
     */
    public void setup(SourceResolver resolver, Map objectModel,
                      String source, Parameters parameters)
    throws ProcessingException, SAXException, IOException
    {
        super.setup(resolver, objectModel, source, parameters);

        this.state = STATE_OUTSIDE;
        //parameters can be specified as parameter elements within the
        //transform element, (in the pipeline section of the sitemap)
        //or as elements within the wsinclude element
        // Web Service
        this.relService = parameters.getParameter( WSINCLUDE_SERVICE_ELEMENT,null );
        this.server = parameters.getParameter( WSINCLUDE_SERVER_ELEMENT,null );
        //timeout
        this.timeout = parameters.getParameterAsInteger(WSINCLUDE_TIMEOUT_ELEMENT,DEFAULT_TIMEOUT);
        //Response element
        this.wsResponseElement=parameters.getParameter( WSINCLUDE_RESPONSE_ELEMENT, null);
        //Error element
        this.wsErrorElement=parameters.getParameter( WSINCLUDE_ERROR_ELEMENT, DEFAULT_ERROR_ELEMENT);
        //cacheid
        this.cacheId=parameters.getParameter( WSINCLUDE_CACHEID_ELEMENT, null);
        //relogin
        this.relogin=parameters.getParameterAsBoolean( WSINCLUDE_RELOGIN_ELEMENT, false);
     	//reset
     	this.reset=parameters.getParameterAsBoolean( WSINCLUDE_RESET_ELEMENT, false);
        //application
        this.application=parameters.getParameter( WSINCLUDE_APPLICATION_ELEMENT, null);
		//handler
		this.handler=parameters.getParameter( WSINCLUDE_HANDLER_ELEMENT, null);
        //Optional retries
        this.retries=parameters.getParameterAsInteger( WSINCLUDE_RETRIES_ELEMENT, DEFAULT_RETRIES);
        //Optional SOAP method
        this.soapMethod=parameters.getParameter( WSINCLUDE_SOAP_ELEMENT, null);
        //Optional
        this.notLoggedInMsg=decode(parameters.getParameter( WSINCLUDE_NOT_LOGGED_IN_ELEMENT, DEFAULT_NOT_LOGGED_IN_RESPONSE));


    }

    /**
     * Recycle the component
     */
    public void recycle() {
        super.recycle();
        this.server=null;
        this.relService=null;
        this.timeout=DEFAULT_TIMEOUT;
        this.endTime=null;
        this.wsResponseElement=null;
        this.startTime=null;
        this.cacheId=null;
        this.messages=new ArrayList();
        this.wsErrorElement=DEFAULT_ERROR_ELEMENT;
        this.soapMethod=null;
        this.relogin=false;
        this.reset=false;
        this.handler=null;
        this.application=null;
        this.retries=DEFAULT_RETRIES;
        this.notLoggedInMsg=DEFAULT_NOT_LOGGED_IN_RESPONSE;
    }

    /**
     * Check for all tags and start recording if found
     */
    public void startTransformingElement(String uri, String name, String raw, Attributes attr)
    throws ProcessingException ,IOException, SAXException
    {

        if (name.equalsIgnoreCase( WSINCLUDE_INCLUDE_ELEMENT ) || name.equalsIgnoreCase( "include")) {
            this.state = STATE_INSIDE; //inside the main tag
            this.startSerializedXMLRecording(XMLUtils.defaultSerializeToXMLFormat(true));
        }
        //service (relative or absolute)
        else if (name.equalsIgnoreCase(WSINCLUDE_SERVICE_ELEMENT) && this.state == STATE_INSIDE) {
            this.startTextRecording();
        }
        //server
        else if (name.equalsIgnoreCase(WSINCLUDE_SERVER_ELEMENT) && this.state == STATE_INSIDE) {
            this.startTextRecording();
        }
        //timeout
        else if (name.equalsIgnoreCase(WSINCLUDE_TIMEOUT_ELEMENT) && this.state == STATE_INSIDE) {
            this.startTextRecording();
        }
        //error
        else if (name.equalsIgnoreCase(WSINCLUDE_ERROR_ELEMENT) && this.state == STATE_INSIDE) {
            this.startTextRecording();
        }
        //optional cache id element
        else if (name.equalsIgnoreCase(WSINCLUDE_CACHEID_ELEMENT) && this.state == STATE_INSIDE) {
            this.startTextRecording();
        }
        //optional relogin element
        else if (name.equalsIgnoreCase(WSINCLUDE_RELOGIN_ELEMENT) && this.state == STATE_INSIDE) {
            this.startTextRecording();
        }
        //optional reset element
        else if (name.equalsIgnoreCase(WSINCLUDE_RESET_ELEMENT) && this.state == STATE_INSIDE) {
			this.startTextRecording();
		}
        //optional response element
        else if (name.equalsIgnoreCase(WSINCLUDE_RESPONSE_ELEMENT) && this.state == STATE_INSIDE) {
            this.startTextRecording();
        }
        //optional soap method name element
        else if (name.equalsIgnoreCase(WSINCLUDE_SOAP_ELEMENT) && this.state == STATE_INSIDE) {
            this.startTextRecording();
        }
        //optional retries element
        else if (name.equalsIgnoreCase(WSINCLUDE_RETRIES_ELEMENT) && this.state == STATE_INSIDE) {
            this.startTextRecording();
        }
		//optional notLoggedIn element
		else if (name.equalsIgnoreCase(WSINCLUDE_NOT_LOGGED_IN_ELEMENT) && this.state == STATE_INSIDE) {
			this.startTextRecording();
		}
        //application, used in conjunction with relogin
        else if (name.equalsIgnoreCase(WSINCLUDE_APPLICATION_ELEMENT) && this.state == STATE_INSIDE) {
            this.startTextRecording();
        }
		//handler, used in conjunction with relogin
		else if (name.equalsIgnoreCase(WSINCLUDE_HANDLER_ELEMENT) && this.state == STATE_INSIDE) {
			this.startTextRecording();
		}
        //not for us
        else {
            super.startTransformingElement(uri, name, raw, attr);
        }
    }//startTransformingElement


    /**
     * On closing wsinclude tag the web service is invoked.
     * The response from the web service replaces the original wsinclude element
     */
    public void endTransformingElement(String uri, String name, String raw)
    throws ProcessingException, IOException, SAXException
    {

        if (this.state == STATE_INSIDE){
            if( name.equalsIgnoreCase( WSINCLUDE_INCLUDE_ELEMENT ) || name.equalsIgnoreCase( "include" ))
            {
                this.state = STATE_OUTSIDE;
                startTime=new java.util.Date();
                String wsRequest=endSerializedXMLRecording();

                String wsResponse=callService(wsRequest);
                //if logged out message in response try relogin
                if (relogin && wsResponse!=null && wsResponse.indexOf(notLoggedInMsg)!=-1){

                	if (login()){
                        //Throw away previous response, and use this one
                        wsResponse=callService(wsRequest);
                    }
                }
                endTime=new java.util.Date();
                //include web service response
                includeResponse(wsResponse);
            }
            //Service (relative or absolute)
            else if( name.equalsIgnoreCase( WSINCLUDE_SERVICE_ELEMENT ) ){
                relService=this.endTextRecording();
            }
            //Server
            else if( name.equalsIgnoreCase( WSINCLUDE_SERVER_ELEMENT ) ){
                server=this.endTextRecording();
            }
            //timeout
            else if( name.equalsIgnoreCase( WSINCLUDE_TIMEOUT_ELEMENT ) ){
                try{
                    timeout=Integer.parseInt(this.endTextRecording());
                }catch (NumberFormatException nfe){
                }
                if (this.getLogger().isDebugEnabled()) {
                  getLogger().debug("WSIncludeTransformer Service timeout set to " + timeout );
                }
            }
            //Optional retries
            else if( name.equalsIgnoreCase( WSINCLUDE_RETRIES_ELEMENT ) ){
                try{
                    retries=Integer.parseInt(this.endTextRecording());
                }catch (NumberFormatException nfe){
                }
                if (this.getLogger().isDebugEnabled()) {
                  getLogger().debug("WSIncludeTransformer Service retries set to " + retries );
                }
            }
			//Optional notLoggedInMsg
			else if( name.equalsIgnoreCase( WSINCLUDE_NOT_LOGGED_IN_ELEMENT ) ){
				notLoggedInMsg=decode(this.endTextRecording());
				if (this.getLogger().isDebugEnabled()) {
				  getLogger().debug("WSIncludeTransformer notLoggedInMsg set to " + notLoggedInMsg );
				}
			}
            //error
            else if (name.equalsIgnoreCase(WSINCLUDE_ERROR_ELEMENT)) {
                wsErrorElement=this.endTextRecording();
                if (this.getLogger().isDebugEnabled()) {
                  getLogger().debug("WSIncludeTransformer errors element set to " + wsErrorElement );
                }
            }
            //optional response element
            else if (name.equalsIgnoreCase(WSINCLUDE_RESPONSE_ELEMENT)) {
                wsResponseElement=this.endTextRecording();
                if (this.getLogger().isDebugEnabled()) {
                  getLogger().debug("WSIncludeTransformer response element set to " + wsResponseElement );
                }
            }
            //optional cache id
            else if (name.equalsIgnoreCase(WSINCLUDE_CACHEID_ELEMENT) ) {
                cacheId=this.endTextRecording();
                if (this.getLogger().isDebugEnabled()) {
                  getLogger().debug("WSIncludeTransformer cacheId set to " + cacheId);
                }
            }
            //optional relogin
            else if (name.equalsIgnoreCase(WSINCLUDE_RELOGIN_ELEMENT)) {
                String reloginStr=this.endTextRecording();
                if (reloginStr.trim().equalsIgnoreCase("true")){
                    relogin=true;
                }
            }
            //optional soap method
            else if (name.equalsIgnoreCase(WSINCLUDE_SOAP_ELEMENT) ) {
                soapMethod=this.endTextRecording();
                if (this.getLogger().isDebugEnabled()) {
                  getLogger().debug("WSIncludeTransformer soapMethod set to " + soapMethod );
                }
            }
            //application
            else if (name.equalsIgnoreCase(WSINCLUDE_APPLICATION_ELEMENT) ) {
                application=this.endTextRecording();
            }
			//handelr
			else if (name.equalsIgnoreCase(WSINCLUDE_HANDLER_ELEMENT) ) {
				handler=this.endTextRecording();
			}
        }//end if inside
        //not for us
        else{
            super.endTransformingElement(uri, name, raw);
        }
    }//end endTransformingElement


    /**
     * Calls the relevant WebService and perform required retry attempts
     * if fatal error has occurred
     */
    protected String callService(String wsRequest){
        String wsResponse=null;
        int attempt=1;

        while (wsResponse==null && attempt<=(retries+1)) {
            if (getLogger().isDebugEnabled())
            	 getLogger().debug("WSInclude Attempt="+attempt);

            URI serviceURI=createServiceURI(server,relService);
            if (soapMethod!=null){
                wsResponse=soapCall(serviceURI, soapMethod, wsRequest, timeout, cacheId);
            }else {
                wsResponse=httpPostToWebService(serviceURI, wsRequest, timeout, cacheId, reset);
            }
            attempt++;
        }

		if (wsResponse == null) {
			wsResponse = "<Error Code=\"Error\" Description=\"WSInclude was unable to connect to " + createServiceURI(server,relService).toString() + "\"/>";
		}

        return wsResponse;
    }//callService



    /**
     * Login to the application
     * @return true on success
     */
    private boolean login(){
    	if (getLogger().isDebugEnabled()){
    		getLogger().debug("**WSInclude Forcing relogin...");
    	}
		//authenticate
		AuthenticationManager authManager = null;
		try {
			SourceParameters authenticationParameters = new SourceParameters();
			//Get Authentication Manager
			authManager = (AuthenticationManager) this.manager.lookup(AuthenticationManager.ROLE);
			//Check for handler
			UserHandler userHandler = authManager.isAuthenticated(handler);
			if (userHandler==null) return false;
			HandlerConfiguration handlerConfig=userHandler.getHandlerConfiguration();
			if (handlerConfig==null) return false;
			ApplicationConfiguration appConfig=(ApplicationConfiguration)handlerConfig.getApplications().get(application);
			String loadURI=appConfig.getLoadResource();
			if (loadURI!=null){
				callURI(loadURI);
				return true;
			}
		} catch(Exception e){
			if (getLogger().isErrorEnabled()){
				getLogger().error("login",e);
			}
		}finally {
			this.manager.release( (Component)authManager);
		}
    	return false;
    }//login

    /**
     * Call the given sitemap URI
     * @param uri sitemap uri
     */
    private void callURI(String uri){
		Source source = null;
		Document doc=null;
		try {
			if (getLogger().isDebugEnabled()){
				getLogger().debug("WSInclude Calling URI "+uri);
			}
			source = SourceUtil.getSource(uri,
										  null,
										  null,
										  resolver);
			doc = SourceUtil.toDOM(source);
		} catch(Exception e){
			e.printStackTrace();
		}finally {
			try {
				resolver.release(source);
			}catch (Exception e){
			}
		}
    }//callURI


    /**
     * Create the URI to the service based on the optional server and
     * relService values
     */
    private URI createServiceURI(String server, String relService){
        String uri=null;

        if (server!=null){
            if (server.endsWith("/")  && relService.startsWith("/")){
                uri=server+relService.substring(1);
            }else {
                uri=server+"/"+relService;
            }
        }else {
            uri=relService;
        }
        try {
            if (uri!=null) {
                URI u=new URI(uri);
                return u;
            }
        }catch (Exception e){
        }
        return null;
    }

    /**
    * Sends the value of the wsinclude tag to the specified web service,
    * and returns response
    * @param service Web Service URL
    * @param xml  XML data to post to web service
    * @param timeout Timeout in milliseconds to wait for response
    * @param cacheId Optional cacheId, used to cache certain responses
    * @param reset Optional, used to reset HttpClientWrapper session
    */
    private String httpPostToWebService(URI serviceURI, String xml, int timeout, String cacheId, boolean reset){
        if (this.getLogger().isDebugEnabled()) {
          this.getLogger().debug("httpPostToWebService url:"+serviceURI.toString()
                               + " timeout:"+timeout
                               + " cacheId:"+cacheId
                               +" xml:\n" + xml
                               +" reset: " + reset);

        }

        //If no host then fill out error message and return
        if (serviceURI==null) {
          messages.add(MSG_POST_NO_WEB_SERVICE);
          return null;
        }
        //Make sure we are always posting something
        if (xml==null) {
          messages.add(MSG_POST_NO_DATA);
          return null;
        }
        //If cache id has been supplied then check cached responses
        if (cacheId!=null && !"".equals(cacheId.trim())){
          String cachedResponse=checkCache(serviceURI.toString(),cacheId);
          if (cachedResponse!=null){
              if (this.getLogger().isDebugEnabled()) {
                  this.getLogger().debug("httpPostToWebService returning cached response");
              }
              return cachedResponse;
          }
        }

        //POST xml to web service
        try{
            HttpClient client=HttpClientWrapper.getHttpClient(objectModel, serviceURI.toString());
			if (reset!=false){
				HttpClientWrapper.resetSessionAttribute(objectModel, serviceURI.toString());
			}
            String resp=HttpClientWrapper.post(client, serviceURI.toString(),xml,timeout);

            if (cacheId!=null){
                addToCache(serviceURI.toString(),cacheId,resp);
            }
			if (this.getLogger().isDebugEnabled()) {
			  this.getLogger().debug("Respsonse from WSInclude: "+resp );
			}

            return resp;
        }catch (HttpClientWrapperException hcwe){
            hcwe.printStackTrace();
            if (this.getLogger().isDebugEnabled()) {
                this.getLogger().error("A HttpClientWrapperException error was thrown in httpPostToWebService FAILED in call to service: '" + serviceURI.toString() + "' xml... " + xml +" "+hcwe.getCause());
            }
            messages.add(MSG_POST_FATAL_ERROR+":"+hcwe.getMessage());
            return null;
        }catch (Exception e) {
			if (this.getLogger().isDebugEnabled()) {
				this.getLogger().error("An exception was thrown in httpPostToWebService FAILED in call to service: '" + serviceURI.toString() + "' xml... " + xml +" "+e.getCause());
			}
			messages.add(MSG_POST_FATAL_ERROR+":"+e.getMessage());
			return null;
        }
    }//end httpPostToWebService


    /**
    * Checks memory cache for previous call to this service
    */
    private String checkCache(String cache, String key){
        return (String)MemoryCache.get(cache,key);
    }

    /**
    * Adds to memory cache
    */
    private void addToCache(String cache, String key, String value ){
        MemoryCache.put(cache,key,value);
    }


    /**
    * Call a web service using SOAP
    */
    public String soapCall(URI serviceURI,
                            String method,
                            String param,
                            int timeout,
                            String cacheId)
    {
        if (this.getLogger().isDebugEnabled()) {
          this.getLogger().debug("soapCall endpoint '" + serviceURI + "' method... " + method );
        }

        //If no host then fill out message and return
        if (serviceURI==null) {
          messages.add(MSG_SOAP_NO_WEB_SERVICE);
          return null;
        }
        if (method==null) {
          messages.add(MSG_SOAP_NO_METHOD);
          return null;
        }
        String sessionKey="SOAP::" +  serviceURI.getHost();

        //If cache id has been supplied then check cached responses
        if (cacheId!=null && !"".equals(cacheId.trim())){
          String cachedResponse=checkCache(serviceURI.toString(),cacheId);
          if (cachedResponse!=null){
              if (this.getLogger().isDebugEnabled()) {
                  this.getLogger().debug("soapCall returning cached response");
              }
              return cachedResponse;
          }
        }

        Request request = ObjectModelHelper.getRequest( objectModel );
        Session session = request.getSession( true );
        Service service = null;
        Call call=null;

        if (session != null){
          call = (Call) session.getAttribute( sessionKey );
        }

        if (call==null){
            try{
                service = new org.apache.axis.client.Service();
                ((org.apache.axis.client.Service)service).setMaintainSession(true);
                call=(Call)service.createCall();
                session.setAttribute( sessionKey, call );
            }catch (Exception e){
                if (getLogger().isErrorEnabled()){
                    getLogger().error("Failed to create SOAP Call to "+serviceURI.toString(),e);
                }
            }
        }

        try{
            ((org.apache.axis.client.Call)call).setTimeout(new Integer(timeout));
            call.setTargetEndpointAddress(serviceURI.toString());
            call.setOperationName( new QName(method) );

            if (getLogger().isDebugEnabled()){
                getLogger().debug("SOAP request to "+serviceURI+"\n"
                        +"method:"+method+"\n"
                        +"data:\n"+param+"\n");
            }
            //call.addParameter("loginRequest", XMLType.XSD_STRING, ParameterMode.IN);
            //call.setReturnType(XMLType.XSD_STRING);

            String ret = (String) call.invoke( new Object [] { param });

            if (getLogger().isDebugEnabled()){
                getLogger().debug("SOAP response from "+serviceURI+"\n"
                    +"method:"+method+"\n"
                    +"data:\n"+ret+"\n");

            }
            if (cacheId!=null){
              addToCache(serviceURI.toString(),cacheId,ret);
            }

            //call.removeAllParameters();

            return ret;
        }catch (java.rmi.RemoteException re){
            messages.add(MSG_SOAP_FATAL_ERROR+":"+re.getMessage());
            if (getLogger().isErrorEnabled()){
            	 getLogger().error("Soap call failed to "+serviceURI.toString(),re);
            }
        }catch (Exception e){
            messages.add(MSG_SOAP_FATAL_ERROR+":"+e.getMessage());
			if (getLogger().isErrorEnabled()){
				 getLogger().error("Soap call failed to "+serviceURI.toString(),e);
			}
        }
        //call.removeAllParameters();

        return null;
    }//end soapCall


    /**
     * Add the XML response from the web service, which replaces
     * the original wsinclude element
     */
    private void includeResponse(String wsResponse)
        throws ProcessingException, IOException, SAXException
    {
        if (wsResponse==null || messages.size()>0){
            //build an error message
            StringBuffer buffer=new StringBuffer();
            buffer.append("<").append(wsErrorElement).append(">");
            //now add each error message
            if (messages.size()>0){
                Iterator iterator=messages.iterator();
                while (iterator.hasNext()){
                    buffer.append("<message>").append((String)iterator.next()).append("</message>");
                }
            }
            buffer.append("</").append(wsErrorElement).append(">");
            wsResponse=buffer.toString();
        }

        DOMParser parser = null;
        try {
            //add start response element
            if (wsResponseElement!=null){
                AttributesImpl attrs=new AttributesImpl();
                attrs.addAttribute("","startTime","startTime","PCDATA",formatter.format(startTime));
                attrs.addAttribute("","timeTaken","timeTaken","PCDATA",""+(endTime.getTime()-startTime.getTime()));
                super.startElement("", wsResponseElement, wsResponseElement, attrs);
            }
            //add response as XML
            parser = (DOMParser)this.manager.lookup(DOMParser.ROLE);
            InputSource inputSource = new InputSource(new StringReader( wsResponse ));
            Document doc = parser.parseDocument( inputSource );
            Node root = (Node) doc.getDocumentElement();
            super.sendEvents( root );

            //add end response element
            if (wsResponseElement!=null){
                super.endElement("", wsResponseElement, wsResponseElement);
            }
        }
        catch (Exception e){
            if (getLogger().isErrorEnabled()) getLogger().error( "IncludeResponse Fatal exception!", e );
        }
        finally {
            this.manager.release((Component)parser);
        }
    }//end includeResponse

	/**
	 * HTML decode
	 */
    public static String decode(String str){
    	if (str==null) return "";
    	try {
	    	str=java.net.URLDecoder.decode(str,"UTF8");
    	}catch (Exception e){
    	}
	    str=replace(str,"&lt;","<");
		str=replace(str,"&gt;",">");
    	return str;
    }

    public static String replace(String str, String oldStr, String newStr){
    	if (str==null) return "";
    	int idx=str.indexOf(oldStr);
    	if (idx==-1) return str;
    	return str.substring(0,idx)+newStr+replace(str.substring(idx+oldStr.length()),oldStr,newStr);
    }

    public static void main(String args[]) throws Exception{
    	String test="&lt;test&gt;";
    	System.out.println(decode(test));
    }

}// end WSIncludeTransformer
