/*
* 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();
}
}
}