One of the things that has changed in the new version is that in JAX-RS 2.0 there is a
Link
object so rather than being able to only inject String and URI you can also inject the correct rel attributes. This has means that the existing annotations coded by Marc have been merged into once simple set of annotations for both Link headers and for injected properties.This functionality is available now, along with a simple example. The original version of the feature that I committed has some serious limitations that are described later, you will need a version of Jersey post 2.8 or you can build a 2.9-SNAPSHOT image that contains my changes currently to implement the example in this blog.
This blog looks at using this new API to provide simple injection for a collections API. One of the common patterns in RESTful services, in particular those based on JSON, is to have an array of structural links at the top level of the structure. For the purposes of this blog I am going to follow the form of the Collection+JSON hypermedia type.
{ "collection" : { "version" : "1.0", "href" : "http://example.org/friends/?offset=10&limit=10", "links" : [ {"rel" : "create", "href" : "http://example.org/friends/"} {"rel" : "next", "href" : "http://example.org/friends/?offset=20&limit=10"} {"rel" : "previous", "href" : "http://example.org/friends/?offset=0&limit=10"} ], "items" : [ ... ] } }
So I can inject the links in the following form, not there is a bunch of boiler plate missing here for clarity. This is not the tidiest code; but in a later cycle it should be possible to simply them somewhat. The design currently uses EL to access properties - this has the advantage of making it possible to write back values as you can represent properties. I can understand it is disliked by some; but I am not sure if I see any value in moving to JavaScript at the moment. Also don't be put of by the @Xml annotations, I am using MOXy for JSON generation - this isn't an XML only thing.
{ @XmlTransient private int limit, offset; // Getters for these @XmlTransient private int modelLimit; // Getters for these @InjectLink( resource = ItemsResource.class, method = "query", style = Style.ABSOLUTE, bindings = {@Binding(name = "offset", value="${instance.offset}"), @Binding(name = "limit", value="${instance.limit}") }, rel = "self" ) @XmlElement(name="link") private String href; @InjectLinks({ @InjectLink( resource = ItemsResource.class, style = Style.ABSOLUTE, method = "query", condition = "${instance.offset + instance.limit < instance.modelLimit}", bindings = { @Binding(name = "offset", value = "${instance.offset + instance.limit}"), @Binding(name = "limit", value = "${instance.limit}") }, rel = "next" ), @InjectLink( resource = ItemsResource.class, style = Style.ABSOLUTE, method = "query", condition = "${instance.offset - instance.limit >= 0}", bindings = { @Binding(name = "offset", value = "${instance.offset - instance.limit}"), @Binding(name = "limit", value = "${instance.limit}") }, rel = "prev" )}) @XmlElement(name="link") @XmlElementWrapper(name = "links") @XmlJavaTypeAdapter(Link.JaxbAdapter.class) List<Link> links; .... }
The original porting of the declarative linking code that exists in version Jersey before 2.8 had very naive code with regards working out what the URI should be for a particular resource, it couldn't deal with any resources that were not at the root of the application, nor would it cope with query parameters which are so important when dealing with collections.
In theory there could be more than one URI for a particular resource class; but this code does need to assume a 1:1 mapping, the current implementation contains a simple algorithm that walks the Jersey meta-model to try to work out the structure, is this doesn't work in your can you can simple provide another implementation of ResourceMappingContext.
Some may question why should I use these ugly annotations when it might be easier just to inject the URI myself? Well the reason is to provide metadata that other tools can use. One of my next jobs is to extend this work to generate the hypermedia extensions and for this I need the above metadata. (Waiting on a pull request being approved before I can really get into it).
Finally it is worth noting that the paging model has its own problems which become apparent if you think of a REST collection as some kind of array that you can safely page over. Concurrent updates along with the lack of state mean that the client can never be sure they have the complete model and should expect to see some items more than once as the model is updated. Cursor or linking based schemes should be considered instead which is yet another good reminder as to why you should always treat the URI as opaque - the sever might need to changes its structure in the future. But that is a entirely different blog for another day.....
Update: See the following blog for some workarounds when reading and writing Link objects.
3 comments:
Hi,
I tried to follow your example for a single link (lines 11-21).
I am getting URL Ok, but query params are missing.
I am using Jersey 2.11 and Moxy.
Advise, please.
This should work, have you raised a bug against Jersey for this?
Hi,
I'm trying to follow your example and it didn't work as expected.
Generated JSON looks like this :
"href":{
"uri":"http://localhost:8484/contacts/35242365",
"params":{
"rel":"self"
},
"uriBuilder":{
"absolute":true
},
"rel":"self",
"rels":[
"self"
],
"title":null,
"type":null
}
Have you any tips about this issue?
Best regards,
Post a Comment