The HTTP object server is a resource oriented platform for hosting resources and RESTful services. It differs from other resource oriented frameworks because it allows resources to be dynamic and change over time. In AliBaba, resources are objects and are instances of classes. Every resource in the system has properties and methods, just like objects. These properties and methods are defined within a class that has resource instances. What makes resources different from objects is that they can also be documents or files.
Resources are identified by IRIs that are recommended to be URLs that dereference to an AliBaba HTTP object server. Properties and method results are accessible through the notation: http://server/path?operation, where "http://server/path" is the resource IRI and "operation" is the operation name of the property or method. Methods may have named parameters, which can be passed as URL query parameters, or a body parameter with a type parameter, which is passed in the request body. AliBaba uses a built in cache control to serve repeated safe dynamic requests as fast as it would static Web resources.
The HTTP Object Server includes two types of persistence stores: blobs are stored using the local file system and accessed through the interface FileObject and data is stored in RDF stores.
To start the HTTP object server run the provided bin/object-server.sh (or .bat) file with the main class org.openrdf.http.object.Server. The server has optional command line options to assign the repository, data directory, and web directory. Use command line options to enable ther server to read the schema from the RDF store. For details run the server with the '-h' option.
The server can read the schema from the repository and look for http:method and http:operation annotations on rdfs:subClassOf obj:Messages to handle incoming requests. The server will also direct incoming requests to Java methods with the annotations @method or @operation that are declared on registered concepts or behaviour (as described in the object-repository).
Figure 1. putHTML.ttl
@prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>.
@prefix owl:<http://www.w3.org/2002/07/owl#>.
@prefix obj:<http://www.openrdf.org/rdf/2009/object#>.
@prefix http:<http://www.openrdf.org/rdf/2009/httpobject#>.
# PUT text/html request method with request body
<#putHTML> rdfs:subClassOf obj:Message;
rdfs:subClassOf [owl:onProperty obj:objectResponse; owl:allValuesFrom owl:Nothing];
http:method "PUT";
obj:groovy """
def out = openWriter()
try {
int len
char[] buf = new char[1024]
while ((len = msg.content.read(buf)) >= 0) {
out.write(buf, 0, len)
}
} finally {
out.close()
}
objectConnection.addDesignations(this, "urn:mimetype:text/html")
""".
<#putHTML-content> a owl:ObjectProperty, owl:FunctionalProperty;
rdfs:domain <#putHTML>;
rdfs:range <java:java.io.Reader>;
http:type "text/html";
obj:name "content".
# GET request for resources of type text/html
<#getHTML> rdfs:subClassOf obj:Message;
rdfs:subClassOf [owl:onProperty obj:target;
owl:allValuesFrom <urn:mimetype:text/html>];
rdfs:subClassOf [owl:onProperty obj:functionalObjectResponse;
owl:allValuesFrom <java:java.io.Reader>];
http:method "GET";
http:type "text/html";
obj:groovy "return openReader(true)".
# DELETE request for resources of type text/html
<#deleteHTML> rdfs:subClassOf obj:Message;
rdfs:subClassOf [owl:onProperty obj:target;
owl:allValuesFrom <urn:mimetype:text/html>];
rdfs:subClassOf [owl:onProperty obj:objectResponse;
owl:allValuesFrom owl:Nothing];
http:method "DELETE";
obj:groovy """
delete()
objectConnection.removeDesignations(this, "urn:mimetype:text/html")
""".
Figure 1 shows how PUT, GET, and DELETE requests can be handled using messages with groovy. By starting the server with this file on the command line (as a relative URL), the service will act as an HTML store -- supporting basic CRUD operations on any resource. Once the server is started, it can be accessed from the assigned port (8080 by default). HTML files can be added to the server via the HTTP PUT method from any supporting HTTP client. Here is an example using the curl client.
curl -X PUT -H "Content-Type: text/html" --data @welcome.html \ http://localhost:8080/welcome.html
Once the above command is run successfully, the server will return a copy of the welcome.html file when accessing the http://localhost:8080/welcome.html URL. The server's copy of a file can now be removed using the HTTP DELETE method.
The server has built in support for content negotiation. When a request for a resource is received with an Accept header that cannot be satisfied by its primary representation (if it has one at all) the server will search for properties and methods of the resource that have a @rel annotation of "alternate" and @type annotation that is acceptable by the request to redirect (302) the client to. Otherwise the server will search for @rel("describedby") to redirect (303) the client to.
Most of the examples in this page are in OWL because they can be written in a single file and don't need to be compiled and server restarted. However, every example here could also be written in Java. Below in an example of how to use Java, for more details see the Object Repository.
Figure 2. CRUD RDF Implemented in Java
// PUTRDFSupport.java
import java.io.*;
import org.openrdf.rio.*;
import org.openrdf.repository.object.*;
import org.openrdf.http.object.annotations.*;
public abstract class PUTRDFSupport implements RDFObject {
@method("PUT")
public void putRDF(@type("application/rdf+xml") InputStream in) throws Exception {
ObjectConnection con = getObjectConnection();
con.clear(getResource());
con.add(in, getResource().stringValue(), RDFFormat.RDFXML, getResource());
con.addDesignation(this, NamedGraph.class);
}
@method("PUT")
public void putTurtle(@type("text/turtle") Reader in) throws Exception {
ObjectConnection con = getObjectConnection();
con.clear(getResource());
con.add(in, getResource().stringValue(), RDFFormat.TURTLE, getResource());
con.addDesignation(this, NamedGraph.class);
}
}
// NamedGraph.java
import org.openrdf.model.*;
import org.openrdf.query.*;
import org.openrdf.query.impl.*;
import org.openrdf.repository.object.*;
import org.openrdf.repository.object.annotations.*;
import org.openrdf.http.object.*;
import org.openrdf.http.object.annotations.*;
@iri("http://data.leighnet.ca/rdf/2009/example#NamedGraph")
public abstract class NamedGraph implements RDFObject {
@rel("alternate")
@operation("graph")
public GraphQueryResult getRDF() throws Exception {
String qry = "CONSTRUCT {?s ?p ?o} WHERE {?s ?p ?o}";
DatasetImpl ds = new DatasetImpl();
ds.addDefaultGraph((URI) getResource());
ObjectConnection con = getObjectConnection();
GraphQuery query = con.prepareGraphQuery(qry);
query.setDataset(ds);
return query.evaluate();
}
@method("DELETE")
public void deleteRDF() throws Exception {
ObjectConnection con = getObjectConnection();
con.clear((URI) getResource());
con.removeDesignation(this, NamedGraph.class);
}
}
# META-INF/org.openrdf.behaviours
PUTRDFSupport = http://www.w3.org/2000/01/rdf-schema#Resource
# META-INF/org.openrdf.concepts (empty)
When the four files in Figure 2 are compiled into a jar and included on the command line when the server starts (or in the class path), RDF files uploaded in a PUT request will be added to the RDF store and these graphs will be made available as an alternate GET requests. In this case when an RDF file is PUT onto the server, the contents of the file are indexed in the RDF store using the target URL as the named graph. If a client asks for the contents of the graph in a different RDF format, the server will redirect the client and return the indexed graph in the requested format, as shown here.
curl -X PUT -H Content-Type:text/turtle --data @my-graph.ttl \ http://localhost:8080/my-graph curl -L -H Accept:application/rdf+xml http://localhost:8080/my-graph
As stated earlier, the server can read schema information from the repository (see command line options), including uploaded named graphs. By uploading the graph in Figure 3, or including it on the command line, the sever will respond to any request for application/rdf+xml with a description of the resource. The server has built-in support for RDF, a hypermedia format that is used to power Linked Data, but can also be used to build other RESTful services. As shown in Figure 3 the server can automatically serialise resources into a bounded RDF description and expose their relationships to other resources that can be followed by clients.
Figure 3. Describe Operation
@prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>.
@prefix owl:<http://www.w3.org/2002/07/owl#>.
@prefix obj:<http://www.openrdf.org/rdf/2009/object#>.
@prefix http:<http://www.openrdf.org/rdf/2009/httpobject#>.
# GET request to describe resources
<#describe> rdfs:subClassOf obj:Message;
rdfs:subClassOf [owl:onProperty obj:functionalObjectResponse;
owl:allValuesFrom rdfs:Resource];
http:operation "describe";
http:rel "describedby";
http:type "application/rdf+xml";
obj:groovy "return this;".
If the client requests a URL as RDF and no RDF graph has been uploaded, the server will redirect the client to describe the RDF resource in the format requested. This allows the metadata server to participate as a Linked Data node, by returning something useful for any resource request.
HTTP requests can also be handled by properties. By using the schema in Figure 4, any resource with the dc:creator property can be followed using the suffix ?creator. It can also be changed using PUT or DELETE requests using the same URL. The server will expose these annotated properties (and other @rel operations) in response Link header of GET requests. This allows non-hypermedia formats to be used in RESTful designs.
Figure 4. creator-operation.ttl
@prefix dc:<http://purl.org/dc/elements/1.1/>. @prefix http:<http://www.openrdf.org/rdf/2009/httpobject#>. @prefix owl:<http://www.w3.org/2002/07/owl#>. @prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>. @prefix rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>. dc:creator a rdf:Property; http:operation "creator"; http:type "text/uri-list"; http:expect "303-see-other"; http:rel "creator".
For example, if the graph in Figure 5 is uploaded to the server, the request for http://localhost:8080/welcome.html?creator will be redirected to http://localhost:8080/somebody.
Figure 5. creator-graph.ttl
@prefix dc:<http://purl.org/dc/elements/1.1/>. <http://localhost:8080/welcome.html> dc:creator <http://localhost:8080/somebody>. <http://localhost:8080/somebody> dc:title "Somebody".
The following commands allow the Link header to be viewed and the description of the welcome resource viewed as RDF. The dc:creator property can also be changed by issuing the PUT request below.
curl -I http://localhost:8080/welcome.html curl -L -H Accept:application/rdf+xml http://localhost:8080/welcome.html curl -X PUT -H Content-Type:text/uri-list --data @- \ http://localhost:8080/welcome.html?creator me
Furthermore if the ontology in Figure 6 is used, the result of the embedded query can be accessed using any HTTP client as shown below using curl. Figure 6 uses the annotation obj:matches, when this annotation is placed on anonymous classes it represents the set of all things that have a URI matching the pattern. The pattern may start with '/' for path matching and/or ends with '*' for prefix matching, a pattern starting with '*' is suffixed matched against the authority until the first '/', otherwise the pattern is an exact match of the complete URI.
Figure 6. text-html.ttl
@prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>.
@prefix owl:<http://www.w3.org/2002/07/owl#>.
@prefix obj:<http://www.openrdf.org/rdf/2009/object#>.
@prefix http:<http://www.openrdf.org/rdf/2009/httpobject#>.
<#text-html-query> rdfs:subClassOf obj:Message;
http:method "GET";
http:cache-control "must-reevaluate";
rdfs:subClassOf [owl:onProperty obj:target;
owl:allValuesFrom [obj:matches "/text-html"]];
rdfs:subClassOf [owl:onProperty obj:functionalObjectResponse;
owl:allValuesFrom obj:TupleResult];
obj:sparql """
SELECT REDUCED ?html WHERE { ?html a <urn:mimetype:text/html> }
""".
curl -H accept:application/sparql-results+json \ http://localhost:8080/text-html
The annotation http:cache-control (or in Java @cacheControl) assigns a cache directive that must be followed by AliBaba's caching mechanism. The value "no-store" directs the server to never cache the result, causing the request to be reprocessed every time. The cache extension "must-reevaluate" directs the server to re-evaluate the result every time the cache expires. Otherwise the request will only be reprocessed when the resources has changed (web resource data or RDF resource metadata) either through a change in the serialised file or in the RDF store.
Methods that are invoked by the server may also have their parameters and response transformed. When the annotation @transform is placed on the message (and the client accepts the final type) the response of the message will be passed to the named transform in the @transform annotation. Furthermore, when the @transform annotation is placed on a parameter it will first pass through the named transform. A named transformation is any message (or method) that is a identified by the IRI (@iri in Java) in the @transform annotation.
Figure 7. search-creator.ttl
@prefix xsd:<http://www.w3.org/2001/XMLSchema#>.
@prefix rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
@prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>.
@prefix owl:<http://www.w3.org/2002/07/owl#>.
@prefix dc:<http://purl.org/dc/elements/1.1/>.
@prefix obj:<http://www.openrdf.org/rdf/2009/object#>.
@prefix http:<http://www.openrdf.org/rdf/2009/httpobject#>.
<#search-creator-query> rdfs:subClassOf obj:Message;
http:method "GET";
http:cache-control "must-reevaluate";
rdfs:subClassOf [owl:onProperty obj:target;
owl:allValuesFrom [obj:matches "/search-creator"]];
rdfs:subClassOf [owl:onProperty obj:functionalObjectResponse;
owl:allValuesFrom obj:TupleResult];
http:transform <#list2html>;
obj:sparql """
SELECT ?document
WHERE { ?document dc:creator ?c
FILTER (regex(str(?c), $creator, "i")) }
""".
<#search-creator-param> a owl:DatatypeProperty, owl:FunctionalProperty;
rdfs:domain <#search-creator-query>;
rdfs:range xsd:string;
obj:name "creator";
http:parameter "creator";
http:transform <#search-creator-regex>.
<#search-creator-regex> rdfs:subClassOf obj:Message;
rdfs:subClassOf [owl:onProperty obj:target;
owl:allValuesFrom [obj:matches "/search-creator"]];
rdfs:subClassOf [owl:onProperty obj:functionalLiteralResponse;
owl:allValuesFrom xsd:string];
obj:groovy "return '/' + msg.input + '$'".
<#search-creator-regex-input> a owl:DatatypeProperty, owl:FunctionalProperty;
rdfs:domain <#search-creator-regex>;
rdfs:range xsd:string;
obj:name "input".
<#list2html> rdfs:subClassOf obj:Message;
rdfs:subClassOf [owl:onProperty obj:target;
owl:allValuesFrom [obj:matches "/search-creator"]];
rdfs:subClassOf [owl:onProperty obj:functionalLiteralResponse;
owl:allValuesFrom rdf:XMLLiteral];
obj:xslt """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html><body><pre>
<xsl:apply-templates />
</pre></body></html>
</xsl:template>
</xsl:stylesheet>""";
http:type "text/html".
<#list2html-input> a owl:DatatypeProperty, owl:FunctionalProperty;
rdfs:domain <#list2html>;
rdfs:range rdf:XMLLiteral;
http:type "application/sparql-results+xml".
The first message in Figure 7 takes a creator as a query parameter that is transformed into a regular expression before being used in the query. If the client accepts text/html, the response is transformed using the inline XML (obj:xslt can also point to a URL of the XSLT file). Below is how this transformation can be initiated.
curl http://localhost:8080/search-creator?creator=me
AliBaba is designed for distributed authentication. It delegates all authentication to what it calls a Realm. These realms are resources that implement a small set of messages, used for authentication. The @realm annotation can be added to any class or method invoked by the server to link the class/method to a realm. If the authorized username is needed for processing it can be read from the Authorization header or as a query parameter. Use the @header("Authorization") and parse out the username from the HTTP header to see the username.
HTTP request headers can be read by placing using the @header annotation on a message parameter. The parameter will be populated with the header value when the request is recieved and method is called.
HTTP response headers can be set by returning an instance of org.apache.http.HttpResponse with the envelope message type, that was passed on the command line, in the @type annotation. The type "message/x-response" is recommended.
AliBaba also supports client side RESTful interactions using the same annotations that were used on the server side. When an http:// or https:// object is retrieved from the object repository any method with the @method and/or @operation annotation the method will be overridden and converted into an HTTP request and the response will be parsed into the method return type or an exception. This allows the object's method to be evaluated on the object's authority server within its own transaction and facilitates distributed persisted objects.
Figure 8. Client Interface
// HTMLFile.java
@matches("/html/*")
public interface HTMLFile {
@method("PUT")
void save(@type("text/html") String html);
@method("GET")
String load();
@operation("creator")
Object getCreator();
@operation("creator")
void setCreator(Object creator);
}
# META-INF/org.openrdf.concepts (empty)
Using the interface in Figure 7, the operations below will allow the remote object to be manipulated from any object-repository connection using the authority server (example.com).
HTMLFile html = (HTMLFile) con.getObject("http://example.com/html/hello.html");
Object james = con.getObject("http://www.leighnet.ca/people/james");
html.save("<html><body>hello!</body></html>"); // issue PUT request
html.setCreator(james); // issue PUT /html/hello.html?creator
String content = html.load(); // issue GET request
Object creator = html.getCreator(); // issue GET /html/hello.html?creator
html.setCreator(null); // issue DELETE /html/hello.html?creator
Annotated methods can return any registered concept, a concept Set, Model, GraphQueryResult, TupleQueryResult, InputStream, Readable, ReadableByteChannel, XMLEventReader, Document, Element, DocumentFragment, ByteArrayOutputStream, byte[], or String. They can include a body parameter of any of the for listed types with a @type annotation and other parameters with the @parameter annotation with a query parameter name. Query parameters can be any datatype or concept type. A @parameter("*") can be used with a Map<String, String[]> for a complete set of query parameters. The method FileObject#toUri() returns the URI of the resource (without any query parameters). The annotation @type restricts the possible media types a method will produce or consume.