This tutorial covers building web services with Simple-Access Protocol, better known as SOAP. Visit the following for more information on building REST services:

Summary

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.

Preferences

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.

Content

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.

Creating an HTTP Server

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.

HTTP Server configuration from the MarkLogic Administrative Interface

Note several items:

  • Server Name: For server name, the convention of “database name-port number” is used. This is not required but a matter of convenience so that you can easily see what ports you’ve used from multiple panels in the MarkLogic Administrative Interface.
  • Root: In this field you should specify the root directory where your HTTP application server will look for XQuery or JavaScript Server-side script files to process. For the purposes of this tutorial, we are going to create XQuery script files. The same functionality can easily be created in JavaScript. In the above screen capture, observe that we point our HTTP server to a directory containing the XQuery script files in our development environment. For rapid development purposes, this makes debugging/testing activites convenient before moving code to a purely production location (outside of your dev environment.)
  • Port: Set your HTTP application server to any available port number.
  • Database: Set this entry to the Documents database which holds our content.

Writing Your Web Service

Consider the following code:
(:
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.

Calling the SOAP Web Service

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.

  1. The URL. Assuming MarkLogic is installed on your local computer, the URL for the SOAP request is https://localhost:8008/PlantInfoQueryService.xqy. The port number of 8008 is for our REST Application Server on MarkLogic we created earlier. We call our SOAP request service of PlantInfoQueryService.xqy that we also created earlier.
  2. Authorization. By default, MarkLogic uses Digest Authentication. In Postman, fill out the Authorization tab. Set Type to Digest Authentication, Username and Password would be your administrator username and password. In MarkLogic, the default Realm value is public. Since this value is set when the first administrator account is created, it’s best to verify with your system administrator if you are unsure.Authorization in your SOAP request
  3. Headers. We need only one extra header, SOAPAction. In the Headers tab, create  a header key of SOAPAction and a value of urn:GetPlantInfo.Creating header "SOAPAction"
  4. Body. The HTTP Body contains our SOAP request. In the Body tab, select raw then select XML (application/xml) from the dropdown list. Copy in our XML request containing our SOAP Envelope.

Body containing SOAP request

Click the Send button for MarkLogic to fulfill your SOAP request. The response is returned as XML.

SOAP request response as XML

You’ve now written your first SOAP web service using MarkLogic Server.

Conclusion

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:

  • A MarkLogic database with some content loaded
  • An HTTP Server
  • An XQuery or JavaScript SOAP web service wrapper
  • A simple testing interface to send the XML SOAP request

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.

Learn More

Application Developer's Guide

Read the methodologies, concepts, and use cases related to application development in MarkLogic Server, with additional resources.

MarkLogic Developer Learning Track

Want to build that awesome app? Get off the ground quickly with the developer track, with instructor-led and self-paced courses.

Getting Started Video Tutorials for Developers

This series of short videos tutorials takes developers who are new to MarkLogic from download to data hub, fast.

This website uses cookies.

By continuing to use this website you are giving consent to cookies being used in accordance with the MarkLogic Privacy Statement.