This tutorial covers building web services with Simple-Access Protocol, better known as SOAP. Visit the following for more information on building REST services:
It is often the case that you’ll want MarkLogic Server to interact with other applications in a Services-Oriented Architecture (SOA). Happily, MarkLogic Server natively supports web services and working within SOA environments can easily be accomplished. This tutorial will walk the reader through the process of developing a simple Simple Object Access Protocol (SOAP) web service wrapper to exemplify how a SOAP request might be passed to MarkLogic Server.
This tutorial assumes that the reader has downloaded the latest version of MarkLogic Server, is familiar with how to create and populate a MarkLogic database, and how to create an HTTP application server. An excellent tutorial for how to quickly accomplish this is Understanding Applications in MarkLogic Server: Part 1.
The content set used in this tutorial is a set of Gardening Plants information. Consider the following code:
(: Copy and paste this code to an empty query window in Query Console and select Documents as the database. :) xquery version "1.0-ml"; xdmp:document-insert("/catalog/plants.xml", <CATALOG> <PLANT> <COMMON>Bloodroot</COMMON> <BOTANICAL>Sanguinaria canadensis</BOTANICAL> <ZONE>4</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE CURRENCY="US">2.44</PRICE> <AVAILABILITY>2017-03-15</AVAILABILITY> </PLANT> <PLANT> <COMMON>Columbine</COMMON> <BOTANICAL>Aquilegia canadensis</BOTANICAL> <ZONE>3</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE CURRENCY="US">9.37</PRICE> <AVAILABILITY>2017-03-06</AVAILABILITY> </PLANT> <PLANT> <COMMON>Marsh Marigold</COMMON> <BOTANICAL>Caltha palustris</BOTANICAL> <ZONE>4</ZONE> <LIGHT>Mostly Sunny</LIGHT> <PRICE CURRENCY="US">6.81</PRICE> <AVAILABILITY>2017-05-17</AVAILABILITY> </PLANT> <PLANT> <COMMON>Cowslip</COMMON> <BOTANICAL>Caltha palustris</BOTANICAL> <ZONE>4</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE CURRENCY="US">9.90</PRICE> <AVAILABILITY>2017-03-06</AVAILABILITY> </PLANT> <PLANT> <COMMON>Dutchman's-Breeches</COMMON> <BOTANICAL>Dicentra cucullaria</BOTANICAL> <ZONE>3</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE CURRENCY="US">6.44</PRICE> <AVAILABILITY>2017-01-20</AVAILABILITY> </PLANT> <PLANT> <COMMON>Ginger, Wild</COMMON> <BOTANICAL>Asarum canadense</BOTANICAL> <ZONE>3</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE CURRENCY="US">9.03</PRICE> <AVAILABILITY>2017-04-18</AVAILABILITY> </PLANT> <PLANT> <COMMON>Hepatica</COMMON> <BOTANICAL>Hepatica americana</BOTANICAL> <ZONE>4</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE CURRENCY="US">4.45</PRICE> <AVAILABILITY>2017-01-26</AVAILABILITY> </PLANT> <PLANT> <COMMON>Liverleaf</COMMON> <BOTANICAL>Hepatica americana</BOTANICAL> <ZONE>4</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE CURRENCY="US">3.99</PRICE> <AVAILABILITY>2017-01-02</AVAILABILITY> </PLANT> <PLANT> <COMMON>Jack-In-The-Pulpit</COMMON> <BOTANICAL>Arisaema triphyllum</BOTANICAL> <ZONE>4</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE CURRENCY="US">3.23</PRICE> <AVAILABILITY>2017-02-01</AVAILABILITY> </PLANT> <PLANT> <COMMON>Mayapple</COMMON> <BOTANICAL>Podophyllum peltatum</BOTANICAL> <ZONE>3</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE CURRENCY="US">2.98</PRICE> <AVAILABILITY>2017-06-05</AVAILABILITY> </PLANT> </CATALOG> )
Run the above code in a MarkLogic Query Console XQuery buffer, setting the database to Documents, to insert a document of plant data into the Documents database.
After creating a database and loading some content into it, the very first thing you’ll need to do it set up an HTTP Server that will act as the gateway between MarkLogic Server and the outside world.
For your convenience, a snapshot of an HTTP Server configuration from the MarkLogic Administrative Interface is provided.
Note several items:
(: This web service finds plants in the requested ZONE and returns the results. Input: An HTTP POST request with the SOAP Envelope XML given in the body of the HTTP request method. Output: An XML response containing a SOAP Envelope and the results of the web service or an error message, if any occurred. :) declare namespace error="https://marklogic.com/xdmp/error"; declare namespace v1="https://marklogic.com/mlu/soap/tutorial/xsd/v1"; declare function local:return-soap-response($soap-request as element()) as element() { let $zone-request := $soap-request//v1:ZONE/string() let $result := //PLANT[ZONE=$zone-request] return <soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"> <soap:Body> <GetPlantInfoResponse xmlns="https://marklogic.com/mlu/soap/tutorial/xsd/v1"> <return> <ResponseMessage xsi:nil="true" /> <ErrorCode xsi:nil="true" /> <RequestId>123456</RequestId> {$result} </return> </GetPlantInfoResponse> </soap:Body> </soap:Envelope> }; declare function local:return-soap-error($err-message as xs:string) as element { <soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"> <soap:Body> <GetPlantInfoResponse xmlns="https://marklogic.com/mlu/soap/tutorial/xsd/v1"> <return> <ResponseMessage>FAILURE - {$err-message}</ResponseMessage> <ErrorCode xsi:nil="true" /> <RequestId>123456</RequestId> </return> </GetPlantInfoResponse> </soap:Body> </soap:Envelope> } (: Retrieve the given SOAP header and make sure it is asking for the GetPlantInfo service. The SOAP XML request is in the body of the http request. :) let $soap-header := xdmp:get-request-header("SOAPAction") let $soap-request := if ($soap-header eq "urn:GetPlantInfo") then xdmp:get-request-body() else () return if ($soap-request) then try { local:return-soap-response($soap-request) } catch($e) { local:return-soap-error($e/error:format-string) } else local:return-soap-error("No REQUEST found.")
Copy and paste this code into a new file called PlantInfoQueryService.xqy and save this code in the directory specified as the root of your HTTP Server. So, as per the HTTP Server screen capture, this would be saved to the c:\tutorial\soap directory. Create this directory if it does not exist. We will use this directory for all of our tutorial code.
First, observe the helpful documentation that precedes the actual code. This documentation describes your standard input/output specifications as would (*should!*) be provided for any code that is written.
Your web service can issue queries to MarkLogic Server in a variety of ways. For the purposes of this tutorial, we’ve elected to use a simple XQuery predicate to match the ZONE value to any matching PLANT element’s ZONE value. Should you require a greater degree of query flexibility, you are certainly welcome to use other MarkLogic libraries such as the Search API, cts:search() and cts:query() (or perhaps custom ethods you’ve authored) to construct more complicated queries as needed.
To call this web service, we’ll need to pass in a SOAP request message. The request is an XML SOAP Envelope element specifying the request in a SOAP Body element.
<soapenv:Envelope xmlns:soapenv="https://schemas.xmlsoap.org/soap/envelope/" xmlns:v1="https://marklogic.com/mlu/soap/tutorial/xsd/v1"> <soapenv:Header/> <soapenv:Body> <v1:GetPlantInfo> <ZONE>3</ZONE> </v1:GetPlantInfo> </soapenv:Body> </soapenv:Envelope>
Copy and paste this XML into a new file called soap-request.xml and save the XML file in the same c:\tutorial\soap directory. Later, we will use cURL, a command-line tool for calling HTTP, to pass the SOAP request to MarkLogic.
Okay, so lets get into the actual code. The very first thing we do is declare our error namespace for our web service module to use. We are also using a namespace for our request we are passing in the SOAP envelope so we are also declareing that namespace:
declare namespace error="https://marklogic.com/xdmp/error"; declare namespace v1="https://marklogic.com/mlu/soap/tutorial/xsd/v1";
We then declare some local functions to parse the request and to format the SOAP response.
The local:return-soap-response($soap-request as node()) function expects a SOAP request containing a <ZONE> XML element. The value of that element is stored in a $zone-request variable as a string. All matching <PLANT> elements that are in that zone are returned into the $result variable. The response will be an XML SOAP Envelope message with the results as a child within the SOAP Body element. This example has minimal error checking to keep the tutorial easy and quick to follow. But error checks should be implemented eventually to inform the calling application of conditions such as no results found, etc.
declare function local:return-soap-response($soap-request as node()) as element() { let $zone-request := $soap-request//v1:GetPlantInfo/v1:ZONE/string() let $result := //PLANT[ZONE=$zone-request] return <soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"> <soap:Body> <v1:GetPlantInfoResponse xmlns:v1="https://marklogic.com/mlu/soap/tutorial/xsd/v1"> <v1:return> <v1:ResponseMessage xsi:nil="true" /> <v1:ErrorCode xsi:nil="true" /> <v1:RequestId>123456</v1:RequestId> {$result} </v1:return> </v1:GetPlantInfoResponse> </soap:Body> </soap:Envelope> };
The local:return-soap-error($err-message as xs:string) function returns a SOAP Envelope message containing the given error string. The response will be informs the caller of any error that might have occurred.
declare function local:return-soap-error($err-message as xs:string) as element { <soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"> <soap:Body> <GetPlantInfoResponse xmlns="https://marklogic.com/mlu/soap/tutorial/xsd/v1"> <return> <ResponseMessage>FAILURE - {$err-message}</ResponseMessage> <ErrorCode xsi:nil="true" /> <RequestId>123456</RequestId> </return> </GetPlantInfoResponse> </soap:Body> </soap:Envelope> };
Next, in the main XQuery body, we make a call to xdmp:get-request-header() to parse the input stream from the calling application for a header value called SOAPAction that contains the web service. When we call this web service, we will specify a header in the HTTP request for the service SOAPAction:urn:GetPlantInfo. We want to check the request for the value of urn:GetPlantInfo so we know we are responding to the proper request.
Then, we retrieve the actual SOAP message envelope from the HTTP POST body with xdmp:get-request-body(). The SOAP message contains the plant zone value to match in our stored content.
let $soap-header := xdmp:get-request-header("SOAPAction") let $soap-request := if ($soap-header eq "urn:GetPlantInfo") then xdmp:get-request-body() else ()
The next piece of code handles the real logic.
return if ($soap-request) then try { local:return-soap-response($soap-request) } catch($e) { local:return-soap-error($e/error:format-string) } else local:return-soap-error("No REQUEST found.")
We first implement a basic form of error-checking such that if the $soap-request variable is empty, then we return a SOAP message response envelope containing an informative error message (“No REQUEST found”).
However, should the $soap-request variable not be empty, we proceed to pass the request to our local function, return-soap-response() so that we may find any plant zone matches in our content database. Note that this piece of code is encapsulated in a try-catch statement. In this case, should an unexpected error occur during the transaction, the calling application can be provided an informative MarkLogic Server error message wrapped with a SOAP response envelope that can then be used for debugging or otherwise be handled by the calling application. Observe that it is in our catch{} code where we utilize the MarkLogic error library that we first declared at the top of our web service code.
To recap thus far, we created an HTTP Server pointing to a location on disk where we’ve saved off an XQuery web service called PlantInfoQueryService.xqy. This service accepts an XML SOAP request input containing a request for plant information targeting a given plant zone. We need a way to test our interface.
We will use Postman, a utility application for calling REST services. Postman is available on Windows, MacOS and Linux from https://www.getpostman.com
There are 4 pieces of information Postman needs to make our SOAP request: the URL, authorization, headers and body. Let’s begin.
Click the Send button for MarkLogic to fulfill your SOAP request. The response is returned as XML.
You’ve now written your first SOAP web service using MarkLogic Server.
We’ve walked through a pretty thorough code analysis of how to set up a SOAP web service in MarkLogic. To summarize, all that was needed to do this was:
We hope this tutorial was helpful, and that perhaps you picked up a few hints, tricks, and best practices along the way. As your requirements to interact with MarkLogic Server expand, you may need to write several web services to meet these different needs. Hopefully this sample gives you a good idea of just how easily this can be accomplished.
By continuing to use this website you are giving consent to cookies being used in accordance with the MarkLogic Privacy Statement.