Loading TOC...
REST Application Developer's Guide (PDF)

REST Application Developer's Guide — Chapter 7

Extending the REST API

You can extend the MarkLogic REST API by installing a resource service extension. This chapter covers the following topics:

Understanding Resource Service Extensions

Use the resource service extension capability to make new REST service endpoints available through a MarkLogic REST API instance. To make a resource service extension available, follow these steps, which are discussed in detail in this chapter.

  1. Create an XQuery library module that implements the extension.
  2. Install the extension in a MarkLogic REST API instance to make it available to applications.
  3. Access the extension service through a /resources/{extensionName} service endpoint.

Creating a Resource Service Extension

This section covers the following topics:

Guidelines for Writing Resource Service Extensions

Keep the following guidelines in mind when authoring a resource extension:

  • Methods such as put and post that accept an input document receive an in-memory representation. MarkLogic Server builtin functions that operate on in-database documents, such as xdmp:node-replace, cannot be applied directly to the input document. To modify the structure of the input document, perform the modifications during construction of a copy of the input content.
  • Your code executes in the transaction context of the HTTP request on whose behalf it is called. You should not commit or rollback the current transaction. You can use xdmp:eval and xdmp:invoke to perform operations in a different transaction.
  • If your extension XQuery library modules depends on other user-defined modules, those modules must be installed in the modules database of the MarkLogic REST API instance.
  • Report errors by throwing one of the exceptions provided by the MarkLogic REST API. For details, see Reporting Errors.

The Resource Extension Interface

To create a resource service extension, implement an XQuery library module based on the template below. The module namespace, function names, and function signatures must be exactly as they appear in the template, with the following exceptions:

  • The extensionName in the module namespace is the user-defined name under which the extension is installed and by which applications access the service. Each extension should have a unique name within the MarkLogic REST API instance in which it is installed.
  • yourNSAlias is a namespace alias of your choosing.
  • You only need to implement functions for the HTTP verbs supported by the extension.

Base your extension implementation on the following template. You must use the function localnames shown (get, put, etc.).

xquery version "1.0-ml"
module namespace yourNSAlias =
    "http://marklogic.com/rest-api/resource/extensionName";
declare function yourNSAlias:get(
    $context as map:map,
    $params  as map:map
) as document-node()*
declare function yourNSAlias:put(
    $context as map:map,
    $params  as map:map,
    $input   as document-node()*
) as document-node()?
declare function yourNSAlias:post(
    $context as map:map,
    $params  as map:map,
    $input   as document-node()*
) as document-node()*
declare function yourNSAlias:delete(
    $context as map:map,
    $params  as map:map
) as document-node()?

The map:map type is a MarkLogic Server extended XQuery type representing a key-value map. For details, see Using the map Functions to Create Name-Value Maps in the Application Developer's Guide.

Your methods can accept input arguments. You must define the argument names and types when installing your extension; for details see Installing a Resource Service Extension. Arguments are passed to your methods through the $params map. See the get implementation in Example Resource Service Extension.

The following template summarizes the meaning of the function parameters:

Parameter Description
$context Service request context information such as input document type and URI, and output types accepted by the caller. For details, see Context Map Keys.
$params Extension-specific parameters. The map contains a key for each parameter on a request to the extension service. Define parameters when installing the extension. For details, see Installing a Resource Service Extension.
$input The input document to which to apply the transformation.

Your extension can return XML, text, or binary documents. Set output-types in the $context map to the MIME type appropriate for the returned content. The content is returned as-is, with the MIME type you specify.

Returning JSON Documents

This section covers important information for extensions that support JSON documents.

To return a constructed JSON document, create a text document containing JSON and set output-types to application/json in the $context map. For example:

declare function example:get(
    $context as map:map,
    $params  as map:map
) as document-node()* {
    map:put($context,"output-types","application/json"),
    document { text { '{"key":"value"}' } }
};

If your extension operates on a document previously inserted into the database as a JSON document using the /documents service, the document will be in the internal XML representation when you access it in your extension function. If you want to return such a document from your extension as JSON, you are responsible for converting it back to JSON using the XQuery builtin json:transform-to-json. For example:

declare function example:get(
    $context as map:map,
    $params  as map:map
) as document-node()* {
    map:put($context,"output-types","application/json"),
    document { text {
      json:transform-to-json(fn:doc("/example/from-json.xml")) 
    } }
};

For details, see Working With JSON in Application Developer's Guide.

Reporting Errors

Extensions and transforms use the same mechanism to report errors to the calling application: Use fn:error to report a RESTAPI-EXTNERR error and provide additional information in the $data parameter of fn:error. You can control the response status code, status message, and provide an additional error reporting response payload. For details, see Reporting Errors.

Context Map Keys

The $context parameter to resource service extension functions can contain the following keys. Not all keys are always present. For example, there is no input-type on a GET request.

Key Description
input-types The MIME type of the input document(s). If there are multiple input documents, the value is a sequence of strings, one for the MIME type of each input document.
accept-types For a GET, the MIME types accepted by the requestor. If there are multiple MIME types, the value is a sequence of strings.
output-types The output document MIME type. The service must set output-types for all output documents.

For a complete example of a resource service extension, see Example Resource Service Extension.

Controlling Transaction Mode

This section discusses the default transaction mode under which the implementation of a resource service extension method is evaluated, and how you can override the default behavior. This discussion assumes you are familiar with the MarkLogic Server transaction mode concept; for details, see Transaction Mode in Application Developer's Guide.

Choosing the appropriate transaction mode can improve the resource utilization of your implementation. For example, the function implementing your POST method runs in 'update' transaction mode by default. This means it acquires exclusive locks on documents, as described in Update Transactions: Readers/Writers Locks in Application Developer's Guide. If you know that your POST implementation is actually a read-only operation, you can override the default transaction mode so that the function is evaluated in 'query' transaction mode and does not acquire exclusive locks.

The XQuery functions in a resource service extension implementation are evaluated using the following default transaction modes:

  • get - query
  • put - update
  • post - update
  • delete - update

To override the default behavior, include a transaction mode annotation in the declaration of your function:

declare %rapi:transaction-mode("the-mode") function yourNS:method(...)

The value for %rapi:transaction-mode can be either 'update' or 'query'.

For example, to specify query transaction mode for your put function, use the following declaration:

declare %rapi:transaction-mode("query") function example:put(
    $context as map:map,
    $params  as map:map,
    $input   as document-node()*
) as document-node()?
{...};

Installing a Resource Service Extension

You install a resource service extension XQuery module using the MarkLogic REST API. Any dependent user-defined modules must be installed separately before installing your extension.

This section covers the following topics:

Installing the Extension XQuery Module

To install or update an extension, send a PUT request to the /config/resources service with a URL of the following form, with the extension code in the request body:

http://host:port/version/config/resources/extensionName

Where extensionName is the name used when administering and accessing the extension.

extensionName must match the name in the extension module namespace declaration. For example:

xquery version "1.0-ml";
module namespace yourNSAlias = 
  "http://marklogic.com/rest-api/resource/extensionName";
...

Set the Content-type of the request body to application/xquery.

If the extension service expects parameters, 'declare' the parameters using request parameters when installing the extension. Use a request parameter of the following form to declare each extension parameter:

method:paramName=paramTypeAndCardinality

Where method is one of the extension functions: get, put, post, or delete; paramName is the name of the parameter; and paramTypeAndCardinality reflects the type and cardinality the parameter would have in an XQuery function declaration.

For example, 'xs:string?' means a string type parameter which may appear 0 or 1 time. The following URL illustrates an extension named 'example' with an optional string parameter named 'reviewer' to the put method and a required string parameter 'genre' to the get method:

'http://host:port/version/config/resources/example?method=put&put:reviewer=string\?&method=get&get:genre=xs:string'

You can also specify extension metadata for provider, version, title, and description as request parameters during installation. For example:

http://host:port/version/config/resources/example?method=get&get:genre=string&provider=MarkLogic%20Corporation

For a complete example, see Example Resource Service Extension.

If your module depends upon other user-defined modules, you must install them manually in the modules database of the REST API instance or in the Modules directory of your MarkLogic Server installation. For details, see Installing Dependent XQuery Library Modules.

Installing Dependent XQuery Library Modules

If your resource extension or transform XQuery library module depends on additional library modules, install these modules into the modules database of the REST API instance or in the Modules directory of your MarkLogic Server installation.

Installation into the modules database is strongly recommended because this ensures MarkLogic Server will propagate the dependent library modules throughout your cluster.

Install dependent modules before using the MarkLogic REST API to install your extension or transform that requires them.

This section covers the following topics:

Installing Into the Modules Database

It is recommended that you install dependent libraries into the modules database of your REST API instance so that MarkLogic Server will propagate them throughout your cluster automatically.

This feature is supported in REST API instances created with MarkLogic Server 6.0-2 and later.

You can install your dependent libriaries under any document URI, but it is good practice to prefix the URI by your domain name and the name of your extension. To avoid name collisions, you should not install your module using the URI prefixes reserved by the MarkLogic REST API for installing extension and transformation components (/marklogic.rest.resource/ or /marklogic.rest.transform/).

To use a dependent library installed in the modules database in your extension or transform module, use the same URI under which you installed the dependent library. For example, if a dependent library is installed with the URI /my.domain/my-ext/lib/dependent.xqy, then the extension module using this library should include an import of the form:

import module namespace dep="mylib" at
    "/my.domain/my-ext/lib/dependent.xqy";

Follow these instructions to use Query Console to install a dependent library module:

  1. Place your dependent library modules in a directory on your MarkLogic Server host that is accessible to MarkLogic Server. For example:
    $ cp /user/me/dependent.xqy /space/resources/
  2. Open Query Console in your browser by navigating to http://yourhost:8000/qconsole.
  3. In Query Console, create a query similar to the following to load the module.
    xdmp:document-load(
      "/space/resources/dependent.xqy",
      <options xmlns="xdmp:document-load">
        <uri>/my.domain/my-ext/lib/dependent.xqy</uri>
      </options>
      )
  4. In the Query Console Content source dropdown list, select the modules database for your REST API instance. For example, if your instance is named 'RESTstop', select 'RESTstop-modules'.
  5. Click Run to execute the query and install the dependent module.
  6. Optionally, click Explore to see that the library is now in the modules database.
Installing Into the Modules Directory

If you choose to install dependent libraries into the Modules directory of your MarkLogic Server installation, you must manually do so on each node in your cluster. Best practice is to install your libraries into the modules database of your REST API instance.

To install a dependent library into the Modules directory, copy the file into MARKLOGIC_INSTALL_DIR/Modules or into a subdirectory of this directory. The default location of this directory is:

  • Unix: /opt/MarkLogic/Modules
  • Windows: C:\Program Files\MarkLogic\Modules

To use a dependent library installed in the Modules directory, use a module URI that is the same as the file pathname after stripping MARKLOGIC_INSTALL_DIR/Modules. For example, if your extension module contains an import of the form:

import module namespace dep="mylib" at "/my-ext/dependent.xqy";

Then dependent.xqy should be located in:

MARKLOGIC_INSTALL_DIR/my-ext/dependent.xqy

MarkLogic Server does not automatically replicate modules installed in the Modules directory across a cluster. You must manually copy the files to each host in the cluster.

Maintenance of Dependent Libraries

When you install or update a dependent library module installed to the modules database of your REST API instance, the module is replicated across your cluster automatically. There can be a delay of up to one minute between updating and availability.

If you install a dependent library module in the Modules directory of your MarkLogic Server installation, then you must manually install and update the module throughout your cluster.

MarkLogic Server does not automatically remove dependent modules when you delete the related extension or transform.

Dependent modules installed in the modules database are removed when you remove the REST API instance if you include the modules database in the instance teardown. For details, see Removing an Instance.

You must always manually delete library modules installed in the Modules directory of your MarkLogic Server installation.

Using a Resource Service Extension

To access extension services, send a request of the appropriate type (GET, PUT, POST, or DELETE) to a URL of the following form:

http://host:port/version/resources/extensionName

Where extensionName is the name under which the extension is installed.

If the service expects parameters, supply the parameter names and values as request parameters, prefixing each parameter name with rs:. For example, if the 'example' service for GET expects a parameter named 'genre', the request URL would be similar to the following:

http://localhost:8003/v1/resources/example?rs:genre=literature

The names, types, and cardinality of method parameters are resource specific. You can send a GET request to /config/resources to dynamically discover resources extension interfaces. For details, see Discovering Resource Service Extensions.

Discovering Resource Service Extensions

To discover the name, interface, and other metadata about installed resource extensions, send a GET request to the /config/resources service of the form:

http://host:port/version/config/resources

MarkLogic Server returns a summary of the installed extensions in XML or JSON. The default summary content type is XML. Use the Accept headers or the format request parameter to explicitly specify a content type, as described in Controlling Output Content Type.

The following example requests an XML report. One extension is installed, called 'example'. The extension implements only a get method, which accepts a string parameter named 'genre'. The extension provider is 'Acme Widgets'.

# Windows users, see Modifying the Example Commands for Windows 
$ curl --anyauth --user user:password -H "Accept: application/xml" \
    -X GET http://localhost:8004/v1/config/resources
...
<rapi:resources xmlns:rapi="http://marklogic.com/rest-api">
  <rapi:resource>
    <rapi:name>example</rapi:name>
    <rapi:title/>
    <rapi:version/>
    <rapi:provider-name>Acme Widgets</rapi:provider-name>
    <rapi:description/>
    <rapi:methods>
      <rapi:method>
        <rapi:method-name>get</rapi:method-name>
        <rapi:parameter>
          <rapi:parameter-name>genre</rapi:parameter-name>
          <rapi:parameter-type>string</rapi:parameter-type>
        </rapi:parameter>
      </rapi:method>
    </rapi:methods>
    <rapi:resource-source>/v1/resources/example</rapi:resource-source>
  </rapi:resource>
</rapi:resources>

Retrieving the Implementation of a Resource Service Extension

To retrieve the XQuery code that implements an extension, send a GET request to the /config/resources/{name} service of the form:

http://host:port/version/config/resources/extensionName

Where extensionName is the name under which the extension is installed.

Set the request Accept header to application/xquery.

MarkLogic Server returns the XQuery library module that implements the extension in the response body. For example:

# Windows users, see Modifying the Example Commands for Windows 
$ curl --anyauth --user user:password -H "Accept: application/xquery" \
    -X GET http://localhost:8004/v1/config/resources/example
...
xquery version "1.0-ml";
module namespace example="http://marklogic.com/rest-api/resource/example";
declare function example:get( ...

Example Resource Service Extension

This section demonstrates the implementation, installation and invocation of a resource service extension.

Overview

This example implements all the HTTP methods supported by the resource extension interface. It demonstrates the use of input arguments, as well as setting content type and response status codes. The methods implemented by this extension do the following:

Method Description
GET Accepts one or more string values through an 'arg1' parameter. Returns an XML document that contains the input argument values using the template:
<args>
  <arg1>value</arg1>
<args>
POST Logs a message.
PUT Accepts XML as input and returns 'Done'.
DELETE Logs a message.

If you implementation function returns content, MarkLogic Server responds with a status 200. If your function raises no errors but does not return content, MarkLogic Server responds with status 204. If your function raises an error, the response code depends on the error; for details, see Reporting Errors.

Installing the Example Extension

Installing the example requires identifying all the implemented methods, as well as the argument expected by the get method. To install the example:

  1. If you do not already have a REST API instance, create one. This example assumes an instance is running on localhost:8004.
  2. Copy the code from Example Extension Implementation into a file. You can use any file name. This example assumes example.xqy.
  3. Install the example using a command similar to the following. The extension metadata such as title, version, provider and description are all optional.
    # Windows users, see Modifying the Example Commands for Windows 
    $ curl --anyauth --user user:password -X PUT \
        -H "Content-type: application/xquery" -d@"./example.xqy" \
        "http://localhost:8004/v1/config/resources/example?title=test resource&version=1.0&provider=marklogic&description=A simple resource example&method=get&method=post&method=put&method=delete&get:arg1=xs:string*"

The table below breaks down the request parameters so it is easier to see what the installation URL does.

Request Parameter Description
title=test resource Optional metadata.
version=1.0 Optional metadata.
provider=marklogic Optional metadata.
description=A simple resource example Optional metadata.
method=get The extensions supports GET requests.
method=post The extension supports POST requests.
method=put The extension supports PUT requests
method=delete The extension supports DELETE requests
get:arg1=xs:string* The GET method accepts an argument named 'arg1' which can occur 1 or more times.

Using the Example Extension

The following example command invokes the GET method of the example extension, passing the string 'super fantastic' as arguments to the GET implementation:

# Windows users, see Modifying the Example Commands for Windows 
$ curl --anyauth --user user:password -X GET \
  -H "Accept: application/xml" \
  "http://localhost:8004/v1/resources/example?rs:arg1=super fantastic"<?xml version="1.0" encoding="UTF-8"?>
<args>
  <arg1>super fantastic</arg1>
</args>

The following example invokes the PUT method:

$ curl --anyauth --user user:password -X PUT -d "<input/>" \
    -H "Content-type: application/xml" \
    http://localhost:8004/v1/resources/example

Done

The POST and DELETE methods simply log a message when invoked. If you call them, check the MarkLogic Server error log for output.

Example Extension Implementation

xquery version "1.0-ml";

(: Namespace pattern must be:  
 : "http://marklogic.com/rest-api/resource/{$rname}" 
 : and prefix must match resource name :)
module namespace example =
  "http://marklogic.com/rest-api/resource/example";

declare default function namespace
  "http://www.w3.org/2005/xpath-functions";
declare option xdmp:mapping "false";

(: Conventions: 
 : Module prefix must match resource name, 
 : and function signatures must conform to examples below.
 : The $context map carries state between the extension
 : framework and the extension.
 : The $params map contains parameters set by the caller,
 : for access by the extension.
 :)

(: Function responding to GET method - must use local name 'get':)
declare function example:get(
    $context as map:map,
    $params  as map:map
) as document-node()*
{
    (: set 'output-types', used to generate content-type header :)
    let $output-types :=
        map:put($context,"output-types","application/xml") 
    let $arg1 := map:get($params,"arg1")
    let $content := 
        <args>
            {for $arg in $arg1
             return <arg1>{$arg1}</arg1>
            }
        </args>
    return (xdmp:set-response-code(200,"OK"),document { $content })  
    (: must return document node(s) :)
};

(: Function responding to PUT method - must use local name 'put'. :)
declare function example:put(
    $context as map:map,
    $params  as map:map,
    $input   as document-node()*
) as document-node()?
{
    (: get 'input-types' to use in content negotiation :)
    let $input-types := map:get($context,"input-types")
    let $negotiate := 
        if ($input-types = "application/xml")
        then () (: process, insert/update :) 
        else error((),"ACK",
          "Invalid type, accepts application/xml only") 
    return document { "Done"}  (: may return a document node :)
};

(: Function responding to POST method - must use local name 'post'. :)
declare function example:post(
    $context as map:map,
    $params  as map:map,
    $input   as document-node()*
) as document-node()*
{
    xdmp:log("post!")
};

(: Func responding to DELETE method - must use local name 'delete'. :)
declare function example:delete(
    $context as map:map,
    $params  as map:map
) as document-node()?
{
    xdmp:log("delete!")
};

« Previous chapter
Next chapter »