7. Assignment - XML Technologies - Winter Term 2015 (Release date: Dec 03 - Date due: Dec 09, 8:00 am)
RESTful XML Information Service (XIS)

A common way to offer services on the internet is via REST. The BaseX XML Database and XQuery Processor has XML super powers. It is your task to make (some of those) facilities easily available as a RESTful service.

During this assignment we will provide an initial Storage service:

  • Create/delete datastores.
  • Add/remove document(s) to datastore.
In later steps we will enhance it with some additional services:
  • Info service: Return statistics about document(s).
  • Validation service: Check document(s) against schemata.
  • Conversion service: Return different representations (CSV, JSON, HTML, ...) of XML document(s).
  • ...

Following is the underlying specification of the REST service: REST API - BaseX XML Information Service (XIS) (can be loaded into swagger.io for a nice visual repesentation).

RESTXQ (slides) is the technique that should be used to implement the REST service according to the specification.

Here is some sample output of a sequence of REST calls to the service. It can help you to see what kind of results are expected and compare the results of your service while implementing it. cURL was used to initiate the REST calls (via this script).

Implementation Hints

1. Create a REST service using RESTXQ.

  • Use following stripped-down ZIP Distribution
  • Make sure a .basexhome file (can be empty) is present in the root directory
  • From the root directory start ./bin/basexhttp(.bat) from the command-line
  • Server delivers on http://127.0.0.1:8984/
  • Implement webapp/restxq.xqm

2. In essence what you have to do is

Following is an example: A file upload service is offered when POSTing to URL /upload. It leverages the XQuery File Module inside an XQuery function enriched with RESTXQ annotations.

3. Responses via RESTXQ

  • In the sample output it can be seen that a responses consist of:
    • (a) a HTTP response and
    • (b) additional content (text, binary, html)
    (
      <rest:response>
        <http:response status="500">
          <http:header name="Content-Language" value="en"/>
          <http:header name="Content-Type" value="text/plain; charset=utf-8"/>
        </http:response>
        <output:serialization-parameters>
          <output:media-type value="text/plain"/>
        </output:serialization-parameters>
      </rest:response>,
      "Upps, we are doomed (Internal server error)."
    )
  • If a RESTXQ function is annoted to be updating, i.e.,
      declare
        %updating
        %rest:DELETE
        %rest:path("/api/v1/db/{$dbname}")
      function xis:db-delete(...)
    
    wrap responses in db:output() to overcome XQUP constraints.

Discussion of 7. Assignment - XML Technologies - Winter Term 2015
(:~
 : XIS - XML Information Service
 :)
module namespace xis = 'http://basex.org/xis';

declare variable $xis:REST-API-DESC as xs:string+ := file:read-text("doc/rest-api/xis-rest-api.yaml");

(: ------ REST/HTTP responses --- :)
declare %private function xis:response-200(
    $src as xs:string,
    $dbname as xs:string
  ) as item()+
{
  xis:response-200($src, $dbname, '')
};

declare %private function xis:response-200(
    $src as xs:string,
    $dbname as xs:string,
    $docname as xs:string
  ) as item()+
{
  let $msg :=
    switch($src)
    case 'db-create' return 'Datastore (' || $dbname || ') created.'
    case 'db-drop'   return 'Datastore (' || $dbname || ') deleted.'
    case 'db-add'    return 'Document (' || $docname || ') added to datastore (' || $dbname || ').'
    case 'db-delete' return 'Document (' || $docname || ') deleted from datastore (' || $dbname || ').'
    default          return $dbname || ' ' || $docname
  return
    xis:response-text(200, $msg)
};

(: Constructs REST response containing 404 HTTP response and a text message. :)
declare %private function xis:response-404(
    $dbname as xs:string
  ) as item()+
{
  xis:response-text(404, 'Datastore (' || $dbname || ') not found.')
};

declare %private function xis:response-404(
    $dbname as xs:string,
    $docname as xs:string
  ) as item()+
{
  xis:response-text(404, 'Document (' || $docname || ') not found in datastore (' || $dbname || ').')
};

declare %private function xis:response-409(
    $dbname as xs:string
  ) as item()+
{
  xis:response-text(409, 'Datastore (' || $dbname || ') already exists.')
};

declare %private function xis:response-409(
    $dbname as xs:string,
    $docname as xs:string
  ) as xs:string
{
  xis:response-text(409, 'Document (' || $docname || ') already exists in datastore (' || $dbname || ').')
};

declare %private function xis:response-text(
    $status as xs:integer,
    $msg as xs:string
  ) as item()+
{
  (
    <rest:response>
      <http:response status="{ $status }">
        <http:header name="Content-Language" value="en"/>
        <http:header name="Content-Type" value="text/plain; charset=utf-8"/>
      </http:response>
      <output:serialization-parameters>
        <output:media-type value='text/plain'/>
      </output:serialization-parameters>
    </rest:response>,
    $msg
  )
};

(: -------------------- Basic information services -------------------------- :)

(:~
 : This function generates the welcome page.
 : @return HTML page
 :)
declare
  %rest:GET
  %rest:path("/")
function xis:landing-page()
  as element(Q{http://www.w3.org/1999/xhtml}html)
{
  let $title := 'XIS – XML Information Service'
  return
    <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
        <title>{ $title }</title>
        <link rel="stylesheet" type="text/css" href="static/style.css"/>
      </head>
      <body>
        <div class="right"><img src="static/basex.svg" width="96"/></div>
        <h2>{ $title }</h2>
        
        <ul>
          <li><a href="api/v1">XIS REST API</a></li>
          <li><a href="http://phobos103.inf.uni-konstanz.de/xml15/tutorial/xis/test/curl/curl.txt">cURL log</a></li>
        </ul>
      </body>
    </html>
};

declare
  %rest:GET
  %rest:path("/api/v1")
  %output:media-type("text/plain")
function xis:rest-api-description()
  as xs:string+
{
  $xis:REST-API-DESC
};

(: ------------------------- Database services ------------------------------ :)
declare
  %rest:GET
  %rest:path("/api/v1/db")
  %output:media-type("text/plain")  
function xis:create-db-info()
  as xs:string+
{
  $xis:REST-API-DESC
};

declare
  %updating
  %rest:POST
  %rest:path("/api/v1/db/{$dbname}")
function xis:db-post(
    $dbname as xs:string
  )
{
  if (db:exists($dbname))
  then
    (: Return 409 (CONFLICT) and text message. :)
    db:output(xis:response-409($dbname))
  else
    (
      db:create($dbname),
      db:output(xis:response-200('db-create', $dbname))
    )
};

declare
  %rest:GET
  %rest:path("/api/v1/db/{$dbname}")
function xis:db-get(
    $dbname as xs:string
  )
{ 
  if (db:exists($dbname))
  then
    element datastore {
      attribute name { $dbname },
      db:list($dbname) ! element document { attribute name { . } }
    }
  else
    xis:response-404($dbname)
};

declare
  %updating
  %rest:DELETE
  %rest:path("/api/v1/db/{$dbname}")
function xis:db-delete(
    $dbname as xs:string
  )
{
  if (db:exists($dbname))
  then (
    db:drop($dbname),
    db:output(xis:response-200('db-drop', $dbname))
  ) 
  else
    db:output(xis:response-404($dbname))
};

(: ----------------- Database / Document services ------------------------------ :)
declare
  %updating
  %rest:POST('{$content}')
  %rest:path("/api/v1/db/{$dbname}/{$docname}")
function xis:db-doc-post(
    $dbname as xs:string,
    $docname as xs:string,
    $content as document-node()
  )
{
  if (db:exists($dbname))
  then
    if (db:exists($dbname, $docname))
    then
      db:output(xis:response-409($dbname, $docname))
    else
      (
        db:add($dbname, $content, $docname),
        db:output(xis:response-200('db-add', $dbname, $docname))
      )
  else
    db:output(xis:response-404($dbname))
};

declare
  %updating
  %rest:DELETE
  %rest:path("/api/v1/db/{$dbname}/{$docname}")
function xis:db-doc-delete(
    $dbname as xs:string,
    $docname as xs:string
  )
{
  if (db:exists($dbname))
  then
    if (db:exists($dbname, $docname))
    then
      (
        db:delete($dbname, $docname),
        db:output(xis:response-200('db-delete', $dbname, $docname))
      )
    else
        db:output(xis:response-404($docname, $dbname))
  else
    db:output(xis:response-404($dbname))
};

declare
  %rest:GET
  %rest:path("/api/v1/db/{$dbname}/{$docname}")
function xis:db-doc-get(
    $dbname as xs:string,
    $docname as xs:string
  )
{
  if (db:exists($dbname))
  then
    if (db:exists($dbname, $docname))
    then
      doc(concat($dbname, '/', $docname))
    else
      xis:response-404($docname, $dbname)
  else
    xis:response-404($dbname)
};