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