org.exist.http.SOAPServer.java Source code

Java tutorial

Introduction

Here is the source code for org.exist.http.SOAPServer.java

Source

/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2001-2006 The eXist team
 *  http://exist-db.org
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software Foundation
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *  $Id$
 */
package org.exist.http;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TemplatesHandler;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

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

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.exist.Namespaces;
import org.exist.dom.BinaryDocument;
import org.exist.dom.DocumentImpl;
import org.exist.dom.QName;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.exist.http.servlets.HttpRequestWrapper;
import org.exist.http.servlets.HttpResponseWrapper;
import org.exist.http.servlets.RequestWrapper;
import org.exist.http.servlets.ResponseWrapper;
import org.exist.memtree.DocumentBuilderReceiver;
import org.exist.memtree.ElementImpl;
import org.exist.memtree.MemTreeBuilder;
import org.exist.memtree.SAXAdapter;
import org.exist.security.PermissionDeniedException;
import org.exist.security.xacml.AccessContext;
import org.exist.source.Source;
import org.exist.source.StringSource;
import org.exist.storage.DBBroker;
import org.exist.storage.XQueryPool;
import org.exist.storage.lock.Lock;
import org.exist.storage.serializers.Serializer;
import org.exist.storage.serializers.WSDLFilter;
import org.exist.util.MimeType;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.Cardinality;
import org.exist.xquery.CompiledXQuery;
import org.exist.xquery.Constants;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.Module;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQuery;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.functions.request.RequestModule;
import org.exist.xquery.functions.response.ResponseModule;
import org.exist.xquery.functions.session.SessionModule;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;
import org.exist.xslt.TransformerFactoryAllocator;

/**
 * @author Adam Retter <adam.retter@devon.gov.uk>
 * @author Jose Maria Fernandez
 * 
 * @serial 20070531T12:18:00
 * 
 * The SOAPServer allows Web Services to be written in XQuery; it translates a 
 * SOAP Request to an XQuery function call and then translates the result of the
 * XQuery function to a SOAP Response.
 * 
 * This is done by managing an internal representation of an XQWS (XQuery Web Service),
 * through this it is able to provide enough information to an XSLT proccessor to
 * generate WSDL and human readable descriptions of the web service and individual
 * functions.
 * 
 * XSLT's are provided for both document literal and RPC style Web Service's and are
 * located in $EXIST_HOME/tools/SOAPServer
 */
public class SOAPServer {
    protected final static Logger LOG = Logger.getLogger(SOAPServer.class);

    private String formEncoding; //TODO: we may be able to remove this eventually, in favour of HttpServletRequestWrapper being setup in EXistServlet, currently used for doPost() but perhaps could be used for other Request Methods? - deliriumsky
    private String containerEncoding;

    private final static String ENCODING = "UTF-8";
    private final static String SEPERATOR = System.getProperty("line.separator");
    private final static String XSLT_WEBSERVICE_WSDL = "/db/system/webservice/wsdl.xslt";
    private final static String XSLT_WEBSERVICE_HUMAN_DESCRIPTION = "/db/system/webservice/human.description.xslt";
    private final static String XSLT_WEBSERVICE_FUNCTION_DESCRIPTION = "/db/system/webservice/function.description.xslt";
    private final static String XSLT_WEBSERVICE_SOAP_RESPONSE = "/db/system/webservice/soap.response.xslt";
    public final static String WEBSERVICE_MODULE_EXTENSION = ".xqws";

    private HashMap<String, XQWSDescription> XQWSDescriptionsCache = new HashMap<String, XQWSDescription>();

    //TODO: SHARE THIS FUNCTION WITH RESTServer (copied at the moment)
    private final static String QUERY_ERROR_HEAD = "<html>" + "<head>" + "<title>Query Error</title>"
            + "<style type=\"text/css\">" + ".errmsg {" + "  border: 1px solid black;" + "  padding: 15px;"
            + "  margin-left: 20px;" + "  margin-right: 20px;" + "}" + "h1 { color: #C0C0C0; }" + ".path {"
            + "  padding-bottom: 10px;" + "}" + ".high { " + "  color: #666699; " + "  font-weight: bold;" + "}"
            + "</style>" + "</head>" + "<body>" + "<h1>XQuery Error</h1>";

    /**
     * Constructor
     * 
     * @param formEncoding   The character encoding method to be used for form data
     * @param containerEncoding   The character encoding method to be used for the container  
     */
    public SOAPServer(String formEncoding, String containerEncoding) {
        this.formEncoding = formEncoding;
        this.containerEncoding = containerEncoding;
    }

    /**
     * Compiles an XQuery or returns a cached version if one exists
     * 
     * @param broker   The Database Broker to use
     * @param xqSource   The XQuery source
     * @param staticallyKnownDocuments   An array of XmldbURI's for documents that should be considered statically known by the XQuery
     * @param xqwsCollectionUri   The XmldbUri of the collection where the XQWS resides
     * @param request   The HttpServletRequest for the XQWS
     * @param response   The HttpServletResponse for the XQWS
     * 
     * @return The compiled XQuery
     * @throws PermissionDeniedException 
     */
    private CompiledXQuery compileXQuery(DBBroker broker, Source xqSource, XmldbURI[] staticallyKnownDocuments,
            XmldbURI xqwsCollectionUri, HttpServletRequest request, HttpServletResponse response)
            throws XPathException, PermissionDeniedException {
        //Get the xquery service
        final XQuery xquery = broker.getXQueryService();
        final XQueryPool pool = xquery.getXQueryPool();
        XQueryContext context;

        //try and get pre-compiled XQuery from the cache
        CompiledXQuery compiled = pool.borrowCompiledXQuery(broker, xqSource);

        //Create the context and set a header to indicate cache status
        if (compiled == null) {
            context = xquery.newContext(AccessContext.REST);
            //response.setHeader("X-XQuery-Cached", "false");
        } else {
            context = compiled.getContext();
            //response.setHeader("X-XQuery-Cached", "true");
        }

        //Setup the context
        declareVariables(context, request, response);
        context.setModuleLoadPath(XmldbURI.EMBEDDED_SERVER_URI.append(xqwsCollectionUri).toString());
        context.setStaticallyKnownDocuments(staticallyKnownDocuments);

        //no pre-compiled XQuery, so compile, it
        if (compiled == null) {
            try {
                compiled = xquery.compile(context, xqSource);
            } catch (final IOException e) {
                LOG.debug(e.getMessage());
                throw new XPathException("Failed to compile query: " + xqSource.toString(), e);
            }
        }

        //store the compiled xqws for use later 
        pool.returnCompiledXQuery(xqSource, compiled);

        return compiled;
    }

    /**
     * Creates an XQuery to call an XQWS function from a SOAP Request
     * 
     * @param broker   The Database Broker to use
     * @param xqwsFileUri   The XmldbURI of the XQWS file
     * @param xqwsNamespace   The namespace of the xqws
     * @param xqwsCollectionUri   The XmldbUri of the collection where the XQWS resides
     * @param xqwsSOAPFunction   The Node from the SOAP request for the Function call from the Http Request
     * @param xqwsDescription   The internal description of the XQWS
     * @param request   The Http Servlet Request
     * @param response The Http Servlet Response
     * 
     * @return The compiled XQuery
     * @throws PermissionDeniedException 
     */
    private CompiledXQuery XQueryExecuteXQWSFunction(DBBroker broker, Node xqwsSOAPFunction,
            XQWSDescription xqwsDescription, HttpServletRequest request, HttpServletResponse response)
            throws XPathException, PermissionDeniedException {
        final StringBuilder query = new StringBuilder();
        query.append("xquery version \"1.0\";").append(SEPERATOR);
        query.append(SEPERATOR);
        query.append("import module namespace ").append(xqwsDescription.getNamespace().getLocalName()).append("=\"")
                .append(xqwsDescription.getNamespace().getNamespaceURI()).append("\" at \"")
                .append(xqwsDescription.getFileURI().toString()).append("\";").append(SEPERATOR);
        query.append(SEPERATOR);

        //add the function call to the xquery
        String functionName = xqwsSOAPFunction.getLocalName();
        if (functionName == null) {
            functionName = xqwsSOAPFunction.getNodeName();
        }
        query.append(xqwsDescription.getNamespace().getLocalName()).append(":").append(functionName).append("(");

        //add the arguments for the function call if any
        final NodeList xqwsSOAPFunctionParams = xqwsSOAPFunction.getChildNodes();
        final Node nInternalFunction = xqwsDescription.getFunction(functionName);
        final NodeList nlInternalFunctionParams = xqwsDescription.getFunctionParameters(nInternalFunction);

        int j = 0;
        for (int i = 0; i < xqwsSOAPFunctionParams.getLength(); i++) {
            final Node nSOAPFunctionParam = xqwsSOAPFunctionParams.item(i);
            if (nSOAPFunctionParam.getNodeType() == Node.ELEMENT_NODE) {
                // Did we reached the length?
                if (j == nlInternalFunctionParams.getLength()) {
                    throw new XPathException("Too many input parameters for " + functionName + ": expected="
                            + xqwsSOAPFunctionParams.getLength());
                }
                query.append(writeXQueryFunctionParameter(
                        xqwsDescription.getFunctionParameterType(nlInternalFunctionParams.item(j)),
                        xqwsDescription.getFunctionParameterCardinality(nlInternalFunctionParams.item(j)),
                        nSOAPFunctionParam));
                query.append(","); //add function seperator

                j++;
            }
        }

        /*
        if(j!=xqwsSOAPFunctionParams.getLength()) {
           throw new XPathException("Input parameters number mismatch for "+functionName+": expected="+xqwsSOAPFunctionParams.getLength()+" got="+j);
        }
        */

        //remove last superflurous seperator
        if (query.charAt(query.length() - 1) == ',') {
            query.deleteCharAt(query.length() - 1);
        }

        query.append(")");

        //compile the query
        return compileXQuery(broker, new StringSource(query.toString()),
                new XmldbURI[] { xqwsDescription.getCollectionURI() }, xqwsDescription.getCollectionURI(), request,
                response);
    }

    /**
     * Writes the value of a parameter for an XQuery function call
     * 
     * @param param   This StringBuffer contains the serialization of the value for XQuery
     * @param nParamSeqItem   The parameter value node from the SOAP Message
     * @param prefix   The prefix for the value (casting syntax)
     * @param postfix   The postfix for the value (casting syntax)
     * @param isAtomic   Whether the value of this type should be atomic or not (or even both)
     */
    private void processParameterValue(StringBuffer param, Node nParamSeqItem, String prefix, String postfix,
            int isAtomic) throws XPathException {
        boolean justOnce = false;
        final StringBuilder whiteContent = new StringBuilder();

        try {
            final Transformer tr = TransformerFactory.newInstance().newTransformer();
            tr.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

            Node n = nParamSeqItem.getFirstChild();
            final StringWriter sw = new StringWriter();
            final StreamResult result = new StreamResult(sw);
            final StringBuffer psw = sw.getBuffer();
            while (n != null) {
                switch (n.getNodeType()) {
                case Node.ELEMENT_NODE:
                    if (isAtomic > 0) {
                        throw new Exception(
                                "Content of " + nParamSeqItem.getNodeName() + " must be an atomic value");
                    }
                    isAtomic = -1;
                    if (justOnce) {
                        throw new Exception(nParamSeqItem.getNodeName() + " must have ONLY ONE element child");
                    }
                    final DOMSource source = new DOMSource(n);
                    tr.transform(source, result);
                    // Only once!
                    justOnce = true;
                    break;
                case Node.TEXT_NODE:
                case Node.CDATA_SECTION_NODE:
                    final String nodeValue = n.getNodeValue();
                    final boolean isNotWhite = !nodeValue.matches("[ \n\r\t]+");
                    if (isAtomic >= 0) {
                        if (isNotWhite || isAtomic > 0) {
                            if (isAtomic == 0) {
                                isAtomic = 1;
                            }
                            psw.append(nodeValue);
                        } else if (isAtomic == 0) {
                            whiteContent.append(nodeValue);
                        }
                    } else if (isNotWhite) {
                        throw new Exception(nParamSeqItem.getNodeName()
                                + " has mixed content, but it must have only one element child");
                    }
                    break;
                }
                n = n.getNextSibling();
            }
            if (isAtomic >= 0) {
                param.append(prefix);
            }
            if (isAtomic == 0) {
                param.append(whiteContent);
            } else {
                param.append(psw);
            }
            if (isAtomic >= 0) {
                param.append(postfix);
            }
        } catch (final Exception e) {
            LOG.debug(e.getMessage());
            throw new XPathException(e.getMessage());
        }
    }

    /**
     * Writes a parameter for an XQuery function call
     * 
     * @param paramType   The type of the Parameter (from the internal description of the XQWS)
     * @param paramCardinality The cardinality of the Parameter (from the internal description of the XQWS)
     * @param SOAPParam   The Node from the SOAP request for the Paremeter of the Function call from the Http Request 
     * 
     * @return A String representation of the parameter, suitable for use in the function call 
     */
    private StringBuffer writeXQueryFunctionParameter(String paramType, int paramCardinality, Node nSOAPParam)
            throws XPathException {
        String prefix = new String();
        String postfix = prefix;

        //determine the type of the parameter
        final int type = Type.getType(paramType);
        final int isAtomic = (Type.subTypeOf(type, Type.ATOMIC)) ? 1 : ((Type.subTypeOf(type, Type.NODE)) ? -1 : 0);

        if (isAtomic >= 0) {
            if (isAtomic > 0 && type != Type.STRING) {
                final String typeName = Type.getTypeName(type);
                if (typeName != null) {
                    prefix = typeName + "(\"";
                    postfix = "\")";
                }
            } else {
                prefix = "\"";
                postfix = prefix;
            }
        }

        final StringBuffer param = new StringBuffer();

        //determine the cardinality of the parameter
        if (paramCardinality >= Cardinality.MANY) {
            //sequence
            param.append("(");

            final NodeList nlParamSequenceItems = nSOAPParam.getChildNodes();
            for (int i = 0; i < nlParamSequenceItems.getLength(); i++) {
                final Node nParamSeqItem = nlParamSequenceItems.item(i);
                if (nParamSeqItem.getNodeType() == Node.ELEMENT_NODE) {
                    processParameterValue(param, nParamSeqItem, prefix, postfix, isAtomic);

                    param.append(","); //seperator for next item in sequence
                }
            }

            //remove last superflurous seperator
            if (param.charAt(param.length() - 1) == ',') {
                param.deleteCharAt(param.length() - 1);
            }

            param.append(")");
        } else {
            processParameterValue(param, nSOAPParam, prefix, postfix, isAtomic);
        }

        return param;
    }

    /**
     * Get's an XQWS Description from the cache.
     * If the description in the cache is out of date it will be refreshed.
     * If there is no cached description a new one is created and added
     * to the cache.
     * 
     * @param broker   The Database Broker to use
     * @param path   The path of the http request
     * @param request   The HttpServletRequest for the XQWS
     * 
     * @return An object describing the XQWS
     */
    private XQWSDescription getXQWSDescription(DBBroker broker, String path, HttpServletRequest request)
            throws PermissionDeniedException, XPathException, SAXException, NotFoundException {
        XQWSDescription description;

        //is there a description for this path
        if (XQWSDescriptionsCache.containsKey(path)) {
            //get the description from the cache
            description = XQWSDescriptionsCache.get(path);

            //is the description is invalid, refresh it
            if (!description.isValid()) {
                description.refresh(request);
            }
        } else {
            //create a new description
            description = new XQWSDescription(broker, path, request);
        }

        //store description in the cache
        XQWSDescriptionsCache.put(path, description);

        //return the description
        return description;
    }

    /**
     * HTTP GET
     * Processes requests for description documents - WSDL, Human Readable and Human Readable for a specific function
     * 
     * TODO: I think simple webservices can also be called using GET, so we may need to cater for that as well
     * but first it would be best to write the doPost() method, split the code out into functions and also use it for this.
     */
    public void doGet(DBBroker broker, HttpServletRequest request, HttpServletResponse response, String path)
            throws BadRequestException, PermissionDeniedException, NotFoundException, IOException {
        //set the encoding
        if (request.getCharacterEncoding() == null) {
            request.setCharacterEncoding(formEncoding);
        }

        /* Process the request */
        try {
            //Get a Description of the XQWS
            final XQWSDescription description = getXQWSDescription(broker, path, request);

            //Get the approriate description for the user
            byte[] result = null;
            if (request.getParameter("WSDL") != null || request.getParameter("wsdl") != null) {
                //WSDL document literal
                result = description.getWSDL();

                //set output content type for wsdl
                response.setContentType(MimeType.XML_TYPE.getName());
            } else if (request.getParameter("WSDLRPC") != null || request.getParameter("wsdlrpc") != null) {
                //WSDL RPC
                result = description.getWSDL(false);

                //set output content type for wsdl
                response.setContentType(MimeType.XML_TYPE.getName());
            } else if (request.getParameter("function") != null) {
                //Specific Function Description
                result = description.getFunctionDescription(request.getParameter("function"));
            } else {
                //Human Readable Description
                result = description.getHumanDescription();
            }

            //send the description to the http servlet response
            final ServletOutputStream os = response.getOutputStream();
            final BufferedOutputStream bos = new BufferedOutputStream(os);
            bos.write(result);
            bos.close();
            os.close();
        } catch (final XPathException xpe) {
            LOG.debug(xpe.getMessage());
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            writeResponse(response, formatXPathException(null, path, xpe), "text/html", ENCODING);
        } catch (final SAXException saxe) {
            LOG.debug(saxe.getMessage());
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            writeResponse(response,
                    formatXPathException(null, path, new XPathException(
                            "SAX exception while transforming node: " + saxe.getMessage(), saxe)),
                    "text/html", ENCODING);
        } catch (final TransformerConfigurationException tce) {
            LOG.debug(tce.getMessage());
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            writeResponse(response,
                    formatXPathException(null, path,
                            new XPathException("SAX exception while transforming node: " + tce.getMessage(), tce)),
                    "text/html", ENCODING);
        }
    }

    //process incomoing SOAP requests
    public void doPost(DBBroker broker, HttpServletRequest request, HttpServletResponse response, String path)
            throws BadRequestException, PermissionDeniedException, NotFoundException, IOException {
        /*
         * Example incoming SOAP Request
         * 
           <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
         <SOAP-ENV:Header/>
         <SOAP-ENV:Body>
             <echo xmlns="http://localhost:8080/exist/servlet/db/echo.xqws">
                 <arg1>adam</arg1>
             </echo>
         </SOAP-ENV:Body>
           </SOAP-ENV:Envelope>
         */

        // 1) Read the incoming SOAP request
        final InputStream is = request.getInputStream();
        final byte[] buf = new byte[request.getContentLength()];
        int bytes = 0;
        int offset = 0;
        final int max = 4096;
        while ((bytes = is.read(buf, offset, max)) != -1) {
            offset += bytes;
        }

        // 2) Create an XML Document from the SOAP Request
        Document soapRequest = null;
        try {
            soapRequest = BuildXMLDocument(buf);
        } catch (final Exception e) {
            LOG.debug(e.getMessage());
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            writeResponse(response, formatXPathException(null, path, new XPathException(
                    "Unable to construct an XML document from the SOAP Request, probably an invalid request: "
                            + e.getMessage(),
                    e)), "text/html", ENCODING);
            return;
        }

        try {
            final StringWriter out = new StringWriter();
            broker.getSerializer().serialize((ElementImpl) soapRequest.getDocumentElement(), out);
            //System.out.println(out.toString());

        } catch (final SAXException e) {
            LOG.error("Error during serialization.", e);
        }

        // 3) Validate the SOAP Request 
        //TODO: validate the SOAP Request

        // 4) Extract the function call from the SOAP Request
        final NodeList nlBody = soapRequest.getDocumentElement().getElementsByTagNameNS(Namespaces.SOAP_ENVELOPE,
                "Body");
        if (nlBody == null) {
            LOG.error("Style Parameter wrapped not supported yet");
        }

        final Node nSOAPBody = nlBody.item(0); // DW: can return NULL ! case: style ParameterWrapped
        final NodeList nlBodyChildren = nSOAPBody.getChildNodes();
        Node nSOAPFunction = null;
        for (int i = 0; i < nlBodyChildren.getLength(); i++) {
            Node bodyChild = nlBodyChildren.item(i);
            if (bodyChild.getNodeType() == Node.ELEMENT_NODE) {
                nSOAPFunction = bodyChild;
                break;
            }
        }

        // Check the namespace for the function in the SOAP document is the same as the request path?
        final String funcNamespace = nSOAPFunction.getNamespaceURI();

        if (funcNamespace != null) {
            if (!funcNamespace.equals(request.getRequestURL().toString())) {
                //function in SOAP request has an invalid namespace
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                writeResponse(response, "SOAP Function call has invalid namespace, got: " + funcNamespace
                        + " but expected: " + request.getRequestURL().toString(), "text/html", ENCODING);
                return;
            }
        } else {
            //function in SOAP request has no namespace
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            writeResponse(response,
                    "SOAP Function call has no namespace, expected: " + request.getRequestURL().toString(),
                    "text/html", ENCODING);
            return;
        }

        // 4.5) Detemine encoding style
        final String encodingStyle = ((org.w3c.dom.Element) nSOAPFunction).getAttributeNS(Namespaces.SOAP_ENVELOPE,
                "encodingStyle");
        boolean isRpcEncoded = (encodingStyle != null
                && "http://schemas.xmlsoap.org/soap/encoding/".equals(encodingStyle));

        // As this detection is a "quirk" which is not always available, let's use a better one...
        if (!isRpcEncoded) {
            final NodeList nlSOAPFunction = nSOAPFunction.getChildNodes();
            for (int i = 0; i < nlSOAPFunction.getLength(); i++) {
                final Node functionChild = nlSOAPFunction.item(i);
                if (functionChild.getNodeType() == Node.ELEMENT_NODE) {
                    if (((org.w3c.dom.Element) functionChild).hasAttributeNS(Namespaces.SCHEMA_INSTANCE_NS,
                            "type")) {
                        isRpcEncoded = true;
                        break;
                    }
                }
            }
        }

        // 5) Execute the XQWS function indicated by the SOAP request  
        try {
            //Get the internal description for the function requested by SOAP (should be in the cache)
            final XQWSDescription description = getXQWSDescription(broker, path, request);

            //Create an XQuery to call the XQWS function
            final CompiledXQuery xqCallXQWS = XQueryExecuteXQWSFunction(broker, nSOAPFunction, description, request,
                    response);

            //xqCallXQWS
            final XQuery xqueryService = broker.getXQueryService();
            final Sequence xqwsResult = xqueryService.execute(xqCallXQWS, null);

            // 6) Create a SOAP Response describing the Result
            String funcName = nSOAPFunction.getLocalName();
            if (funcName == null) {
                funcName = nSOAPFunction.getNodeName();
            }
            final byte[] result = description.getSOAPResponse(funcName, xqwsResult, request, isRpcEncoded);

            // 7) Send the SOAP Response to the http servlet response
            response.setContentType(MimeType.XML_LEGACY_TYPE.getName());
            final ServletOutputStream os = response.getOutputStream();
            final BufferedOutputStream bos = new BufferedOutputStream(os);
            bos.write(result);
            bos.close();
            os.close();
        } catch (final XPathException xpe) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            writeResponse(response, formatXPathException(null, path, xpe), "text/html", ENCODING);
        } catch (final SAXException saxe) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            writeResponse(response,
                    formatXPathException(null, path, new XPathException(
                            "SAX exception while transforming node: " + saxe.getMessage(), saxe)),
                    "text/html", ENCODING);
        } catch (final TransformerConfigurationException tce) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            writeResponse(response,
                    formatXPathException(null, path,
                            new XPathException("SAX exception while transforming node: " + tce.getMessage(), tce)),
                    "text/html", ENCODING);
        }
    }

    /**
     * Builds an XML Document from a string representation
     * 
     * @param buf   The XML Document content
     * 
     * @return   DOM XML Document
     */
    private Document BuildXMLDocument(byte[] buf) throws SAXException, ParserConfigurationException, IOException {
        //try and construct xml document from input stream, we use eXist's in-memory DOM implementation
        final SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setNamespaceAware(true);
        //TODO we should be able to cope with context.getBaseURI()            
        final InputSource src = new InputSource(new ByteArrayInputStream(buf));
        final SAXParser parser = factory.newSAXParser();
        final XMLReader reader = parser.getXMLReader();
        final SAXAdapter adapter = new SAXAdapter();
        reader.setContentHandler(adapter);
        reader.setContentHandler(adapter);
        reader.parse(src);

        //return receiver.getDocument();
        return adapter.getDocument();
    }

    /**
     * Pass the request, response and session objects to the XQuery
     * context.
     *
     * @param context
     * @param request
     * @param response
     * @throws XPathException
     */
    private void declareVariables(XQueryContext context, HttpServletRequest request, HttpServletResponse response)
            throws XPathException {
        if (request != null) {
            final RequestWrapper reqw = new HttpRequestWrapper(request, formEncoding, containerEncoding);
            context.declareVariable(RequestModule.PREFIX + ":request", reqw);
            context.declareVariable(SessionModule.PREFIX + ":session", reqw.getSession(false));
        }

        if (response != null) {
            final ResponseWrapper respw = new HttpResponseWrapper(response);
            context.declareVariable(ResponseModule.PREFIX + ":response", respw);
        }

    }

    //TODO: SHARE THIS FUNCTION WITH RESTServer (copied at the moment)
    /**
     * @param query
     * @param e
     */
    private String formatXPathException(String query, String path, XPathException e) {
        final StringWriter writer = new StringWriter();
        writer.write(QUERY_ERROR_HEAD);
        writer.write("<p class=\"path\"><span class=\"high\">Path</span>: ");
        writer.write("<a href=\"");
        writer.write(path);
        writer.write("\">");
        writer.write(path);
        writer.write("</a></p>");

        writer.write("<p class=\"errmsg\">");
        writer.write(e.getMessage());
        writer.write("</p>");
        if (query != null) {
            writer.write("<p><span class=\"high\">Query</span>:</p><pre>");
            writer.write(query);
            writer.write("</pre>");
        }
        writer.write("</body></html>");
        return writer.toString();
    }

    //TODO: SHARE THIS FUNCTION WITH RESTServer (copied at the moment)
    private void writeResponse(HttpServletResponse response, String data, String contentType, String encoding)
            throws IOException {
        // possible format contentType: application/xml; charset=UTF-8
        if (contentType != null && !response.isCommitted()) {

            final int semicolon = contentType.indexOf(';');
            if (semicolon != Constants.STRING_NOT_FOUND) {
                contentType = contentType.substring(0, semicolon);
            }

            response.setContentType(contentType + "; charset=" + encoding);
        }

        final OutputStream is = response.getOutputStream();
        is.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".getBytes());
        is.write(data.getBytes(encoding));
    }

    private class XQWSDescription {
        /**
         * Class describes an XQWS using an Internal XML Representation
         * 
         * @author Adam Retter <adam.retter@devon.gov.uk>
         * @serial 20061023T19:23:00
         */

        private DBBroker broker = null;
        private String HttpServletRequestURL = null;
        private String XQWSPath = null;
        private XmldbURI xqwsFileURI = null;
        private XmldbURI xqwsCollectionURI = null;
        private QName xqwsNamespace = null;

        //cache for internal Description of an XQWS
        private long lastModifiedXQWS = 0;
        private Module modXQWS = null;
        private org.exist.memtree.DocumentImpl docXQWSDescription = null;

        //cache for XQWS WSDL
        private long lastModifiedWSDL = 0;
        private byte[][] descriptionWSDL = { null, null };

        //cache for XQWS Human Readable description
        private long lastModifiedHuman = 0;
        private byte[] descriptionHuman = null;

        //Cache for XQWS (Human Readable) Function description
        private long lastModifiedFunction = 0;
        private HashMap<String, byte[]> descriptionFunction = new HashMap<String, byte[]>(); //key: functionName as String, value: byte[]

        /**
         * Constructor
         * 
         * @param broker   The Database Broker to use
         * @param XQWSPath   The path to the XQWS
         * @param request   The Http Request for the XQWS
         */
        public XQWSDescription(DBBroker broker, String XQWSPath, HttpServletRequest request)
                throws XPathException, SAXException, PermissionDeniedException, NotFoundException {
            this.broker = broker;
            this.HttpServletRequestURL = request.getRequestURL().toString();
            this.XQWSPath = XQWSPath;

            //create an initial description of the XQWS
            createInternalDescription(request);
        }

        /**
         * Returns the URI of the XQWS file
         * 
         * @return The XmldbURI of the XQWS file
         */
        public XmldbURI getFileURI() {
            return xqwsFileURI;
        }

        /**
         * Returns the URI of the Collection containing the XQWS file
         * 
         * @return The XmldbURI of the Collection containing the XQWS file
         */
        public XmldbURI getCollectionURI() {
            return xqwsCollectionURI;
        }

        /**
         * Returns the Namespace of the XQWS
         * 
         * @return The QName for the Namespace of the XQWS
         */
        public QName getNamespace() {
            return xqwsNamespace;
        }

        /**
         * Determines if this description of the XQWS is valid
         * 
         * @return true if the description is valid, false otherwise
         */
        public boolean isValid() {
            BinaryDocument docXQWS = null;

            try {
                docXQWS = getXQWS(broker, XQWSPath);
                return (docXQWS.getMetadata().getLastModified() == lastModifiedXQWS);
            } catch (final PermissionDeniedException e) {
                LOG.debug(e.getMessage());
                return false;
            } finally {
                if (docXQWS != null) {
                    docXQWS.getUpdateLock().release(Lock.READ_LOCK);
                }
            }
        }

        /**
         * Refreshes an XQWS Description by re-reading the XQWS
         * Should be called if isValid() returns false and an XQWS description is needed further 
         * 
         * @param request   The HttpServletRequest to update for
         */
        public void refresh(HttpServletRequest request)
                throws XPathException, SAXException, PermissionDeniedException, NotFoundException {
            createInternalDescription(request);
        }

        /**
         * Returns the WSDL for the XQWS Description
         * Caches the result, however the cache is regenerated if
         * the StyleSheet used for the transformation changes
         * 
         * @return byte array containing the WSDL
         */
        public byte[] getWSDL() throws PermissionDeniedException, TransformerConfigurationException, SAXException {
            return getWSDL(true);
        }

        /**
         * Returns the WSDL for the XQWS Description
         * Caches the result, however the cache is regenerated if
         * the StyleSheet used for the transformation changes
         * 
         * @return byte array containing the WSDL
         */
        public byte[] getWSDL(boolean isDocumentLiteral)
                throws PermissionDeniedException, TransformerConfigurationException, SAXException {
            DocumentImpl docStyleSheet = null;
            final int wsdlIndex = isDocumentLiteral ? 0 : 1;
            try {
                //get the WSDL StyleSheet
                docStyleSheet = broker.getXMLResource(XmldbURI.create(XSLT_WEBSERVICE_WSDL), Lock.READ_LOCK);

                //has the stylesheet changed, or is this the first call for this version
                if (docStyleSheet.getMetadata().getLastModified() != lastModifiedWSDL
                        || descriptionWSDL[wsdlIndex] == null) {
                    //TODO: validate the WSDL

                    final Properties params = new Properties();
                    params.put("isDocumentLiteral", isDocumentLiteral ? "true" : "false");

                    //yes, so re-run the transformation
                    descriptionWSDL[wsdlIndex] = Transform(docXQWSDescription, docStyleSheet, params);
                    lastModifiedWSDL = docStyleSheet.getMetadata().getLastModified();
                }

                //return the result of the transformation
                return descriptionWSDL[wsdlIndex];
            } finally {
                if (docStyleSheet != null) {
                    //close the Stylesheet Document and release the read lock
                    docStyleSheet.getUpdateLock().release(Lock.READ_LOCK);
                }
            }
        }

        /**
         * Returns the Human Readable description for the XQWS Description
         * Caches the result, however the cache is regenerated if
         * the StyleSheet used for the transformation changes
         * 
         * @return byte array containing the WSDL
         */
        public byte[] getHumanDescription()
                throws PermissionDeniedException, TransformerConfigurationException, SAXException {
            DocumentImpl docStyleSheet = null;
            try {
                //get the Human Description StyleSheet
                docStyleSheet = broker.getXMLResource(XmldbURI.create(XSLT_WEBSERVICE_HUMAN_DESCRIPTION),
                        Lock.READ_LOCK);

                //has the stylesheet changed, or is this the first call for this version
                if (docStyleSheet.getMetadata().getLastModified() != lastModifiedHuman
                        || descriptionHuman == null) {
                    //yes, so re-run the transformation
                    descriptionHuman = Transform(docXQWSDescription, docStyleSheet, null);
                    lastModifiedHuman = docStyleSheet.getMetadata().getLastModified();
                }

                //return the result of the transformation
                return descriptionHuman;
            } finally {
                if (docStyleSheet != null) {
                    //close the Stylesheet Document and release the read lock
                    docStyleSheet.getUpdateLock().release(Lock.READ_LOCK);
                }
            }
        }

        /**
         * Returns the (Human Readable) description of a Function for the XQWS Description
         * Caches the result, however the cache is regenerated if
         * the StyleSheet used for the transformation changes
         * 
         * @param functionName The name of the function to describe
         * 
         * @return byte array containing the Function Description
         */
        public byte[] getFunctionDescription(String functionName)
                throws PermissionDeniedException, TransformerConfigurationException, SAXException {
            DocumentImpl docStyleSheet = null;
            try {
                //get the Function Description StyleSheet
                docStyleSheet = broker.getXMLResource(XmldbURI.create(XSLT_WEBSERVICE_FUNCTION_DESCRIPTION),
                        Lock.READ_LOCK);

                //has the stylesheet changed?
                if (docStyleSheet.getMetadata().getLastModified() != lastModifiedFunction) {
                    //yes, so empty the cache
                    descriptionFunction.clear();

                    //change the last modified date
                    lastModifiedFunction = docStyleSheet.getMetadata().getLastModified();
                }

                //if there is not a pre-trasformed description in the cache
                if (!descriptionFunction.containsKey(functionName)) {
                    //do the transformation and store in the cache
                    final Properties params = new Properties();
                    params.put("function", functionName);
                    descriptionFunction.put(functionName, Transform(docXQWSDescription, docStyleSheet, params));
                }

                //return the result of the transformation from the cache
                return descriptionFunction.get(functionName);
            } finally {
                if (docStyleSheet != null) {
                    //close the Stylesheet Document and release the read lock
                    docStyleSheet.getUpdateLock().release(Lock.READ_LOCK);
                }
            }
        }

        /**
         * Returns the function node from the internal description
         * 
         * @param functionName   The name of the function to return
         * 
         * @return the node from the internal description
         */
        public Node getFunction(String functionName) {
            //iterate through all the function nodes
            final NodeList nlFunctions = docXQWSDescription.getElementsByTagName("function");
            for (int i = 0; i < nlFunctions.getLength(); i++) {
                //get the function node
                final Node nFunction = nlFunctions.item(i);

                //iterate through children of function, get value of <name> element
                final NodeList nlFunctionChildren = nFunction.getChildNodes();
                for (int j = 0; j < nlFunctionChildren.getLength(); j++) {
                    final Node nFunctionChild = nlFunctionChildren.item(j);
                    if (nFunctionChild.getNodeType() == Node.ELEMENT_NODE) {
                        //is this the function node we are looking for?
                        if ("name".equals(nFunctionChild.getNodeName())
                                && nFunctionChild.getFirstChild().getNodeValue().equals(functionName)) {
                            //yes so return it
                            return nFunction;
                        }
                    }
                }
            }

            return null;
        }

        /**
         * Returns the parameters for a function from the internal description
         * 
         * @param functionName   The name of the function to return parameters for
         * 
         * @return NodeList of parameter's
         */
        @SuppressWarnings("unused")
        public NodeList getFunctionParameters(String functionName) {
            final Node internalFunction = getFunction(functionName);
            if (internalFunction != null) {
                return getFunctionParameters(internalFunction);
            }
            return null;
        }

        /**
         * Returns the parameters for a function from the internal description
         * 
         * @param internalFunction The internal function to return parameters for
         * 
         * @return NodeList of parameter's
         */
        public NodeList getFunctionParameters(Node internalFunction) {
            final NodeList nlChildren = internalFunction.getChildNodes();
            for (int i = 0; i < nlChildren.getLength(); i++) {
                final Node child = nlChildren.item(i);
                if ("parameters".equals(child.getNodeName())) {
                    return child.getChildNodes();
                }
            }

            return null;
        }

        /**
         * Returns the Name for the function parameter
         * 
         * @param internalFunctionParameter The internal function parameter to return the Name for
         * 
         * @return The Name of the parameter
         */
        @SuppressWarnings("unused")
        public String getFunctionParameterName(Node internalFunctionParameter) {
            //first element child of <parameter> is <name>
            final NodeList nlParamArgs = internalFunctionParameter.getChildNodes();
            for (int i = 0; i < nlParamArgs.getLength(); i++) {
                final Node nArg = nlParamArgs.item(i);
                if (nArg.getNodeType() == Node.ELEMENT_NODE) {
                    if ("name".equals(nArg.getNodeName())) {
                        return nArg.getFirstChild().getNodeValue();
                    }
                }
            }

            return null;
        }

        /**
         * Returns the Type for the function parameter
         * 
         * @param internalFunctionParameter The internal function parameter to return the Type for
         * 
         * @return The Type of the parameter
         */
        public String getFunctionParameterType(Node internalFunctionParameter) {
            //second element child of <parameter> is <type>
            final NodeList nlParamArgs = internalFunctionParameter.getChildNodes();
            for (int i = 0; i < nlParamArgs.getLength(); i++) {
                final Node nArg = nlParamArgs.item(i);
                if (nArg.getNodeType() == Node.ELEMENT_NODE) {
                    if ("type".equals(nArg.getNodeName())) {
                        return nArg.getFirstChild().getNodeValue();
                    }
                }
            }

            return null;
        }

        /**
         * Returns the Cardinality for the function parameter
         * 
         * @param internalFunctionParameter The internal function parameter to return the Cardinality for
         * 
         * @return The Cardinality as defined by org.exist.xquery.Cardinality
         */
        public int getFunctionParameterCardinality(Node internalFunctionParameter) {
            //third element child of <parameter> is <cardinality>
            final NodeList nlParamArgs = internalFunctionParameter.getChildNodes();
            for (int i = 0; i < nlParamArgs.getLength(); i++) {
                final Node nArg = nlParamArgs.item(i);
                if (nArg.getNodeType() == Node.ELEMENT_NODE) {
                    if ("cardinality".equals(nArg.getNodeName())) {
                        return Integer.valueOf(nArg.getFirstChild().getNodeValue()).intValue();
                    }
                }
            }

            //default cardinality
            return Cardinality.EXACTLY_ONE;
        }

        /**
         * Returns the SOAP Response for the XQWS Function
         * named with the result provided.
         * 
         * @param functionName   The name of the XQWS function that was called
         * @param functionResult   The Result of the XQWS function that was called
         * @param request   The Http Request for the XQWS
         * 
         * @return byte array containing the SOAP Response
         */
        public byte[] getSOAPResponse(String functionName, Sequence functionResult, HttpServletRequest request,
                boolean isRpcEncoded)
                throws XPathException, PermissionDeniedException, TransformerConfigurationException, SAXException {
            //get the Result StyleSheet for the SOAP Response
            final DocumentImpl docStyleSheet = broker.getXMLResource(XmldbURI.create(XSLT_WEBSERVICE_SOAP_RESPONSE),
                    Lock.READ_LOCK);

            //Get an internal description, containg just a single function with its result
            final org.exist.memtree.DocumentImpl docResult = describeWebService(modXQWS, xqwsFileURI, request,
                    XQWSPath, functionName, functionResult);

            //return the SOAP Response
            final Properties params = new Properties();
            params.put("isDocumentLiteral", isRpcEncoded ? "false" : "true");
            return Transform(docResult, docStyleSheet, params);
        }

        /**
         * Creates the internal Description of the XQWS
         * 
         * @param request The HttpServletRequest for which the description should be created
         */
        private void createInternalDescription(HttpServletRequest request)
                throws XPathException, SAXException, PermissionDeniedException, NotFoundException {
            // 1) Get the XQWS
            final BinaryDocument docXQWS = getXQWS(broker, XQWSPath);

            if (docXQWS == null) {
                throw new NotFoundException("Resource " + request.getRequestURL().toString() + " not found");
            }

            xqwsFileURI = docXQWS.getFileURI();
            xqwsCollectionURI = docXQWS.getCollection().getURI();
            final byte[] xqwsData = getXQWSData(broker, docXQWS);

            // 2) Store last modified date
            lastModifiedXQWS = docXQWS.getMetadata().getLastModified();

            // 3) Get the XQWS Namespace
            xqwsNamespace = getXQWSNamespace(xqwsData);

            // 4) Compile a Simple XQuery to access the module
            final CompiledXQuery compiled = XQueryIncludeXQWS(broker, docXQWS.getFileURI(), xqwsNamespace,
                    docXQWS.getCollection().getURI());

            // 5) Inspect the XQWS and its function signatures and create a small XML document to represent it
            modXQWS = compiled.getContext().getModule(xqwsNamespace.getNamespaceURI());
            docXQWSDescription = describeWebService(modXQWS, xqwsFileURI, request, XQWSPath, null, null);
        }

        /**
          * Gets XQWS file from the db
          * 
          * @param broker    The Database Broker to use
          * @param path      The Path to the XQWS
          * 
          * @return   The XQWS BinaryDocument
          */
        private BinaryDocument getXQWS(DBBroker broker, String path) throws PermissionDeniedException {
            BinaryDocument docXQWS = null;
            try {
                final XmldbURI pathUri = XmldbURI.create(path);
                docXQWS = (BinaryDocument) broker.getXMLResource(pathUri, Lock.READ_LOCK);
                return docXQWS;
            } finally {
                //close the XQWS Document and release the read lock
                if (docXQWS != null) {
                    docXQWS.getUpdateLock().release(Lock.READ_LOCK);
                }
            }
        }

        /**
         * Gets the data from an XQWS Binary Document
         * 
         * @param broker   The Database Broker to use
         * @param docXQWS   The XQWS Binary Document
         * 
         * @return   byte array containing the content of the XQWS Binary document
         */
        private byte[] getXQWSData(DBBroker broker, BinaryDocument docXQWS) {

            try {
                final InputStream is = broker.getBinaryResource(docXQWS);
                final byte[] data = new byte[(int) broker.getBinaryResourceSize(docXQWS)];
                is.read(data);
                is.close();
                return data;

            } catch (final IOException ex) {
                LOG.error(ex);
            }

            return null;
        }

        /**
         * Get's the namespace of the XQWS form the content of an XQWS
         *
         * @param xqwsData   The content of an XQWS file
         * 
         * @return The namespace QName
         */
        private QName getXQWSNamespace(byte[] xqwsData) {
            //move through the xqws char by char checking if a line contains the module namespace declaration     
            final StringBuilder sbNamespace = new StringBuilder();
            final ByteArrayInputStream bis = new ByteArrayInputStream(xqwsData);
            while (bis.available() > 0) {
                final char c = (char) bis.read(); //TODO: do we need encoding here?
                sbNamespace.append(c);
                if (c == SEPERATOR.charAt(SEPERATOR.length() - 1)) {
                    if (sbNamespace.toString().startsWith("module namespace")) {
                        //break out of the while loop, sbNamespace should now contain our namespace
                        break;
                    } else {
                        //empty the namespace buffer
                        sbNamespace.delete(0, sbNamespace.length());
                    }
                }
            }

            //seperate the name and url
            final String namespaceName = sbNamespace
                    .substring("module namespace".length(), sbNamespace.indexOf("=")).trim();
            final String namespaceURL = sbNamespace.substring(sbNamespace.indexOf("\"") + 1,
                    sbNamespace.lastIndexOf("\""));

            //return the XQWS namespace
            return new QName(namespaceName, namespaceURL);
        }

        /**
         * Creates a simple XQuery to include an XQWS
         * 
         * @param broker   The Database Broker to use
         * @param xqwsFileUri   The XmldbURI of the XQWS file
         * @param xqwsNamespace   The namespace of the xqws
         * @param xqwsCollectionUri   The XmldbUri of the collection where the XQWS resides
         * 
         * @return The compiled XQuery
         * @throws PermissionDeniedException 
         */
        private CompiledXQuery XQueryIncludeXQWS(DBBroker broker, XmldbURI xqwsFileUri, QName xqwsNamespace,
                XmldbURI xqwsCollectionUri) throws XPathException, PermissionDeniedException {
            //Create a simple XQuery wrapper to access the module
            String query = "xquery version \"1.0\";" + SEPERATOR;
            query += SEPERATOR;
            query += "import module namespace " + xqwsNamespace.getLocalName() + "=\""
                    + xqwsNamespace.getNamespaceURI() + "\" at \"" + xqwsFileUri.toString() + "\";" + SEPERATOR;
            query += SEPERATOR;
            query += "()";

            //compile the query
            return compileXQuery(broker, new StringSource(query), new XmldbURI[] { xqwsCollectionUri },
                    xqwsCollectionUri, null, null);
        }

        /**
        * Describes an XQWS by building an XML node representation of the XQWS module
        * 
        * <webservice>
        *    <name/>
        *    <description/>
        *    <host/>
        *    <path/>
        *    <URL/>
        *    <functions>
        *       <function/> { unbounded } { @see org.exist.http.SOAPServer#describeWebServiceFunction(org.exist.xquery.FunctionSignature, org.exist.memtree.MemTreeBuilder) }
        *    </functions>
        * </webservice>
        *
        * @param modXQWS   The XQWS XQuery module
        * @param xqwsFileUri   The File URI of the XQWS
        * @param request   The Http Servlet request for this webservice
        * @param path   The request path
        * @param functionName   Used when only a single function should be described, linked to functionResult
        * @param functionResult For writting out the results of a function call, should be used with functionName 
        * @return   An in-memory document describing the webservice
        */
        private org.exist.memtree.DocumentImpl describeWebService(Module modXQWS, XmldbURI xqwsFileUri,
                HttpServletRequest request, String path, String functionName, Sequence functionResult)
                throws XPathException, SAXException {
            final FunctionSignature[] xqwsFunctions = modXQWS.listFunctions();
            final MemTreeBuilder builderWebserviceDoc = new MemTreeBuilder(
                    broker.getXQueryService().newContext(AccessContext.REST));
            builderWebserviceDoc.startDocument();
            builderWebserviceDoc.startElement(new QName("webservice", null, null), null);
            builderWebserviceDoc.startElement(new QName("name", null, null), null);
            builderWebserviceDoc.characters(xqwsFileUri.toString().substring(0,
                    xqwsFileUri.toString().indexOf(WEBSERVICE_MODULE_EXTENSION)));
            builderWebserviceDoc.endElement();
            builderWebserviceDoc.startElement(new QName("description", null, null), null);
            builderWebserviceDoc.characters(modXQWS.getDescription());
            builderWebserviceDoc.endElement();
            builderWebserviceDoc.startElement(new QName("host", null, null), null);
            builderWebserviceDoc.characters(request.getServerName() + ":" + request.getServerPort());
            builderWebserviceDoc.endElement();
            builderWebserviceDoc.startElement(new QName("path", null, null), null);
            builderWebserviceDoc.characters(path);
            builderWebserviceDoc.endElement();
            builderWebserviceDoc.startElement(new QName("URL", null, null), null);
            builderWebserviceDoc.characters(request.getRequestURL());
            builderWebserviceDoc.endElement();
            builderWebserviceDoc.startElement(new QName("functions", null, null), null);
            for (int f = 0; f < xqwsFunctions.length; f++) {
                if (functionName == null) {
                    //All Function Descriptions
                    describeWebServiceFunction(xqwsFunctions[f], builderWebserviceDoc, null);
                } else {
                    //Only a Single Function Description for showing function call results
                    if (xqwsFunctions[f].getName().getLocalName().equals(functionName)) {
                        describeWebServiceFunction(xqwsFunctions[f], builderWebserviceDoc, functionResult);
                        break;
                    }
                }
            }
            builderWebserviceDoc.endElement();
            builderWebserviceDoc.endElement();
            builderWebserviceDoc.endDocument();

            return builderWebserviceDoc.getDocument();
        }

        /**
         * Describes an XQWS function by building an XML node representation of the function signature
         * 
         *    <function>
         *       <name/>
         *       <description/>
         *       <parameters>
         *          <parameter>   { unbounded }
         *             <name/>
         *             <type/>
         *             <cardinality/>
         *          </parameter>
         *       </parameters>
         *       <return>
         *          <type/>
         *          <cardinality/>
         *          <result>      { Only displayed if this is after the function has been executed }
         *             either {
         *                <value/> or
         *                <sequence>
         *                   <value/> { unbounded }
         *                </sequence>
         *             }
         *          </result>
         *       </return>
         *    </function>
         * 
         * @param signature   The function signature to describe
         * @param builderFunction   The MemTreeBuilder to write the description to
         * @param functionResult   A Sequence containing the function results or null if the function has not yet been executed
         */
        private void describeWebServiceFunction(FunctionSignature signature, MemTreeBuilder builderFunction,
                Sequence functionResult) throws XPathException, SAXException {
            //Generate an XML snippet for each function
            builderFunction.startElement(new QName("function", null, null), null);
            builderFunction.startElement(new QName("name", null, null), null);
            builderFunction.characters(signature.getName().getLocalName());
            builderFunction.endElement();
            if (signature.getDescription() != null) {
                builderFunction.startElement(new QName("description", null, null), null);
                builderFunction.characters(signature.getDescription());
                builderFunction.endElement();
            }
            final SequenceType[] xqwsArguments = signature.getArgumentTypes();
            builderFunction.startElement(new QName("parameters", null, null), null);
            for (int a = 0; a < xqwsArguments.length; a++) {
                builderFunction.startElement(new QName("parameter", null, null), null);
                builderFunction.startElement(new QName("name", null, null), null);
                //builderFunction.characters(xqwsArguments[a].getNodeName().getLocalName()); //TODO: how to get parameter name?
                builderFunction.endElement();
                builderFunction.startElement(new QName("type", null, null), null);
                builderFunction.characters(Type.getTypeName(xqwsArguments[a].getPrimaryType()));
                builderFunction.endElement();
                builderFunction.startElement(new QName("cardinality", null, null), null);
                builderFunction.characters(Integer.toString(xqwsArguments[a].getCardinality()));
                builderFunction.endElement();
                builderFunction.endElement();
            }
            builderFunction.endElement();
            builderFunction.startElement(new QName("return", null, null), null);
            builderFunction.startElement(new QName("type", null, null), null);
            builderFunction.characters(Type.getTypeName(signature.getReturnType().getPrimaryType()));
            builderFunction.endElement();
            final int iReturnCardinality = signature.getReturnType().getCardinality();
            builderFunction.startElement(new QName("cardinality", null, null), null);
            builderFunction.characters(Integer.toString(iReturnCardinality));
            builderFunction.endElement();
            if (functionResult != null) {
                builderFunction.startElement(new QName("result", null, null), null);

                //determine result cardinality
                final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(builderFunction);
                if (iReturnCardinality >= Cardinality.MANY) {
                    //sequence of values
                    builderFunction.startElement(new QName("sequence", null, null), null);

                    for (int i = 0; i < functionResult.getItemCount(); i++) {
                        builderFunction.startElement(new QName("value", null, null), null);
                        functionResult.itemAt(i).copyTo(broker, receiver);
                        //builderFunction.characters(functionResult.itemAt(i).getStringValue());
                        builderFunction.endElement();
                    }

                    builderFunction.endElement();
                } else {
                    //atomic value
                    builderFunction.startElement(new QName("value", null, null), null);
                    functionResult.itemAt(0).copyTo(broker, receiver);
                    //builderFunction.characters(functionResult.itemAt(0).getStringValue());
                    builderFunction.endElement();
                }

                builderFunction.endElement();
            }
            builderFunction.endElement();
            builderFunction.endElement();
        }

        /**
          * Transforms a document with a stylesheet
          * 
          * @param docStyleSheet   A stylesheet document from the db
          * @param parameters   Any parameters to be passed to the stylesheet
          * 
          * @return byte array containing the result of the transformation
          */
        private byte[] Transform(org.exist.memtree.DocumentImpl srcDoc, DocumentImpl docStyleSheet,
                Properties parameters) throws TransformerConfigurationException, SAXException {
            //Transform docXQWSDescription with the stylesheet

            /*
             * TODO: the code in this try statement (apart from the WSDLFilter use) was mostly extracted from
             * transform:stream-transform(), it would be better to be able to share that code somehow
             */
            final SAXTransformerFactory factory = TransformerFactoryAllocator
                    .getTransformerFactory(broker.getBrokerPool());
            final TemplatesHandler templatesHandler = factory.newTemplatesHandler();
            templatesHandler.startDocument();
            final Serializer serializer = broker.getSerializer();
            serializer.reset();
            final WSDLFilter wsdlfilter = new WSDLFilter(templatesHandler, HttpServletRequestURL);
            serializer.setSAXHandlers(wsdlfilter, null);
            serializer.toSAX(docStyleSheet);
            templatesHandler.endDocument();

            final TransformerHandler handler = factory.newTransformerHandler(templatesHandler.getTemplates());

            //set parameters, if any
            if (parameters != null) {
                final Transformer transformer = handler.getTransformer();
                final Enumeration<?> parameterKeys = parameters.keys();
                while (parameterKeys.hasMoreElements()) {
                    final String paramName = (String) parameterKeys.nextElement();
                    final Object paramValue = parameters.get(paramName);
                    transformer.setParameter(paramName, paramValue);
                }
            }

            final ByteArrayOutputStream os = new ByteArrayOutputStream();
            final StreamResult result = new StreamResult(os);
            handler.setResult(result);

            handler.startDocument();
            srcDoc.toSAX(broker, handler, null);
            handler.endDocument();

            return os.toByteArray();
        }
    }

}