This is an example of how a a RESTful service, implemented using JAX-RS and CXF, can expose provenance of the resources it exposes.
There are two branches in this project on https://github.com/stain/paq:
- master - REST service that can say hello, and return provenance of greeting
- paq - REST service that also provides link between greeting and its provenance
To compile/run, you will need Java and Maven:
PS C:\users\stain\src\paq> mvn clean jetty:run
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Example PROV-AQ usage 0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
(..)
2013-03-25 15:39:09.419:INFO::Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
[INFO] Starting scanner at interval of 10 seconds.
The base URI should be http://localhost:8080/paq/ unless you modified the port with mvn -Djetty.port=9999
Check the HelloWorld REST service is working using your favourite HTTP client (e.g. browser or curl in a new terminal window).
PS C:\Users\stain\src\paq> curl http://localhost:8080/paq/hello/Alice
Hello, Alice
You may replace Alice with any name, as long as it is URI escaped:
PS C:\Users\stain\src\paq> curl http://localhost:8080/paq/hello/Joe%20Bloggs
Hello, Joe Bloggs
This example service provide provenance, using the PROV-N format:
PS C:\Users\stain\src\paq> curl -i http://localhost:8080/paq/provenance/hello/Alice
HTTP/1.1 200 OK
Content-Type: text/provenance-notation
Date: Mon, 25 Mar 2013 15:41:02 GMT
Content-Length: 305
Server: Jetty(6.1.26)
document
prefix hello <http://localhost:8080/paq/hello/>
prefix app <http://localhost:8080/paq/>
entity(hello:Alice)
wasDerivedFrom(hello:Alice, name)
entity(name, [ prov:value="Alice" ])
agent(app:hello, [ prov:type=prov:SoftwareAgent ])
wasAttributedTo(hello:Alice, app:hello)
endDocument
Note that we used the -i
parameter above to verify that the correct media-type text/provenance-notation was returned.
This provenance says that the resource http://localhost:8080/paq/hello/Alice was derived from a name with value "Alice", and made by the (web) service http://localhost:8080/paq/hello.
This PROV-N trace is generated by HelloWorld.helloProvenance()
by filling in the URIs and name in the template src/main/resources/provTemplate.txt - a more detailed provenance trace might include things like timestamps and details about who provided the name.
A restful client who has requested http://localhost:8080/paq/hello/Alice will not magically know that there is a provenance trace at http://localhost:8080/paq/provenance/hello/Alice - the URI for the provenance resource could just as well have been say http://localhost:8080/about/history/1337.
Rather than for each publisher to invent specific ways to locate the provenance resource, the W3C PROV-AQ Note suggests a common way to find the provenance resource by using HTTP Link:
headers according to RFC5988
Specifically, PAQ says that a resource accessed by HTTP can
describe its provenance trace by adding a Link}} header with the relation "http://www.w3.org/ns/prov#has_provenance". So in our case, this can be achieved with:
Link: <http://localhost:8080/paq/provenance/hello/Alice>; rel="http://www.w3.org/ns/prov#has_provenance"
OK, so how do we provide this Link header? Our existing greeting is
quite simple thanks to JAX-RS and CXF:
@GET
@Path("hello/{name}")
@Produces("text/plain")
public String hello(@PathParam("name") String name) {
String greeting = "Hello, " + name + "\n";
return greeting;
}
Our provenance method is a bit more complicated as it generates
the absolute URIs for the greeting resource (depending on the name parameter) and then build the PROV-N trace - here using a simple MessageFormat template.
@GET
@Path("provenance/hello/{name}")
@Produces("text/provenance-notation")
public String helloProvenance(@PathParam("name") String name,
@Context UriInfo ui) throws IOException {
// Get our absolute URI
// See http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-URIcalculationusingUriInfoandUriBuilder
UriBuilder appUri = ui.getBaseUriBuilder();
// Absolute URIs for resources we are to give provenance about
URI helloURI = appUri.path(getClass(), "hello").build(name);
// Prepare prefixes for PROV-N qualified names
URI appURI = appUri.build("").resolve("../");
URI helloPrefix = helloURI.resolve("./");
// The PROV-N qualified name for our /hello/{name} resource
String helloEntity = "hello:" + helloPrefix.relativize(helloURI);
// Simple PROV-N trace, see <http://www.w3.org/TR/prov-n/>
// Here this is done in a naive way by loading a template
// from src/main/resources and do string-replace to insert
// our URIs.
String template = IOUtils.toString(getClass().getResourceAsStream("/provTemplate.txt"));
String prov = MessageFormat.format(template,
helloPrefix, appURI, helloEntity, name);
// Note: PROV-N should be be built using say the PROV Toolbox
// rather than this naive template approach!
return prov;
}
So in order to provide the RESTful links we will need to insert Link:
headers in the hello() response. As we need to return both the greeting and HTTP headers,
we change our return to a Response:
@GET
@Path("hello/{name}")
@Produces("text/plain")
public Response hello(@PathParam("name") String name) {
String greeting = "Hello, " + name + "\n";
ResponseBuilder responseBuilder = Response.ok().entity(greeting);
return responseBuilder.build();
}
We'll inject the same @Context UriInfo ui
parameter as in
in order to find the absolute URI to calling the helloProvenance() method:
public Response hello(@PathParam("name") String name, @Context UriInfo ui) {
URI provUri = ui.getBaseUriBuilder().path(getClass(), "helloProvenance").build(name);
and then build a new Link instance:
Link provLink = Link.fromUri(provUri).rel(HAS_PROVENANCE).build();
This uses the fixed URI for the provenance relation:
private static final String HAS_PROVENANCE = "http://www.w3.org/ns/prov#has_provenance";
Finally we include the new Link header by adding it to the response builder before returning:
return responseBuilder.header(HttpHeaders.LINK, provLink).build();
The final version of hello() should then look something like:
@GET
@Path("hello/{name}")
@Produces("text/plain")
public Response hello(@PathParam("name") String name, @Context UriInfo ui) {
String greeting = "Hello, " + name + "\n";
ResponseBuilder responseBuilder = Response.ok().entity(greeting);
// TODO: Could have used Link.fromResourceMethod but it seems to return wrong URI in CXF :(
URI provUri = ui.getBaseUriBuilder().path(getClass(), "helloProvenance").build(name);
Link provLink = Link.fromUri(provUri).rel(HAS_PROVENANCE).build();
return responseBuilder.header(HttpHeaders.LINK, provLink).build();
}
You may check out the paq
branch from https://github.com/stain/paq to see the final version.
If you have not followed the tutorial above, make sure you check out and build
the paq
branch from https://github.com/stain/paq to include the PROV-AQ Link headers.