8. Assignment - XML Technologies - Winter Term 2015 (Release date: Dec 10 - Date due: Dec 16, 8:00 am)
RESTful XML Information Service (XIS) – Information and Conversion service

Last assignment we build a REST service to create, read, update and delete datastores to store documents inside (CRUD services).

We now want to provide some additional services that operate on our stored XML documents:

  • Information service: Return statistics about an XML document (number of elements, distribution of nodes, etc.)
  • Conversion service: Return JSONML and HTML representation of an XML document

Information service is supposed to return useful information about a specific XML document as an XML report.
You are free come up with an individual solution and assessment of what you consider useful meta data about an XML document.
Following XQuery Modules may help to get some meta data about XML docs in the database:

Conversion service is supposed to return a representation of the stored XML document in another format (JSONML and HTML).

For JSONML nothing special has to be done: JSON Module provides what you need.
You just have to make sure to invoke the correct RESTXQ function once the client sends an Accept-Header to indicate what kind of content should be delivered (see RESTXQ/Content Negotiation for details.
Hint: Once you annotate a RESTXQ function with produces your test requests have to set an Accept-Header. If missing, an error will be returned.
Example:
curl "http://localhost:8984/foo" will fail with '[bxerr:BASX0003] Path "/foo" assigned to several functions'.
curl -H "Accept:application/xml" "http://localhost:8984/foo" will find a matching function.

For an HTML response you are supposed to provide a generic XSLT script which transforms arbitrary XML inputs to an HTML view. The HTML view can be straightforward, for instance:

                                          <html>
                                            <style type="text/css">
                                              div {
                                                border: 1px solid blue;
                                                margin: 5px;
                                                padding: 5px;
                                              }
                                            </style>
  
          <xml>                             <div><b>xml:</b>
            <foo>bar</foo>                    <div><b>foo:</b> bar</div>
            <baz>                             <div><b>baz:</b>
              <foobar>foobaz</foobar>           <div><b>foobar:</b> foobaz</div>
            </baz>                            </div>
          </xml>                            </div>
  
                                          </html>
XSLT Module may be used to trigger XSLT processing from XQuery code.

Discussion of 8. 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");

declare variable $xis:DB-DOC-GET-AS-HTML-XSLT-STYLE :=
  <xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
    <xsl:template match="/">
      <html>
        <style type="text/css">
          div {{
            border: 1px solid blue;
            margin: 5px;
            padding: 5px;
          }}
        </style>
        <xsl:apply-templates select="*"/>
      </html>  
    </xsl:template>
    
    <xsl:template match="*">
      <div>
        <b><xsl:value-of select="name(.)"/>: </b> <xsl:value-of select="text()"/>
        <xsl:apply-templates select="*"/>
      </div>
    </xsl:template>
  </xsl:stylesheet>
;


(: ------ 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 item()+
{
  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 / Conversion 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 function xis:db-doc-get(
    $dbname as xs:string,
    $docname as xs:string,
    $content-type as xs:string
  )
{
  if (db:exists($dbname))
  then
    if (db:exists($dbname, $docname))
    then
      let $doc := doc(concat($dbname, '/', $docname))
      return
        switch ($content-type)
        case 'application/xml'  return $doc
        case 'application/json' return json:serialize($doc, map { 'format': 'jsonml' })
        case 'text/html'        return xslt:transform-text($doc, $xis:DB-DOC-GET-AS-HTML-XSLT-STYLE)
        default     return 'Please specify content-type via Accept header: application/xml, application/json, ...'
    else
      xis:response-404($docname, $dbname)
  else
    xis:response-404($dbname)
};

declare
  %rest:GET
  %rest:produces("application/xml")             (: ... bind function to HTTP request header 'Accept: application/xml' :)
  %rest:path("/api/v1/db/{$dbname}/{$docname}")
function xis:db-doc-get-as-xml(
    $dbname as xs:string,
    $docname as xs:string
  )
{
  xis:db-doc-get($dbname, $docname, 'application/xml')
};

declare
  %rest:GET
  %rest:produces("application/json")           (: ... bind function to HTTP request header 'Accept: application/json' :)
  %rest:path("/api/v1/db/{$dbname}/{$docname}")
  %output:media-type("application/json")       (: ... set HTTP response header to 'Content-Type: application/json' :)
function xis:db-doc-get-as-json(
    $dbname as xs:string,
    $docname as xs:string
  )
{
  xis:db-doc-get($dbname, $docname, 'application/json')
};

declare
  %rest:GET
  %rest:produces("text/html")           (: ... bind function to HTTP request header 'Accept: application/html' :)
  %rest:path("/api/v1/db/{$dbname}/{$docname}")
  %output:media-type("text/html")       (: ... set HTTP response header to 'Content-Type: application/html' :)
function xis:db-doc-get-as-html(
    $dbname as xs:string,
    $docname as xs:string
  )
{
  xis:db-doc-get($dbname, $docname, 'text/html')
};