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') };