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:
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.
(:~
: 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')
};