One of the best aspects of RESTful orientated services is getting your resources connected, possibly even achieving HATEOAS. Making that jump with with legacy object models or data can be tricky, depending on how intrusive you want to be.
There is such a large amount of legacy data in various forms, usually with minimal links/relations that could be leveraged.
So it is a worthy challenge to try and do it in the least intrusive manner possible.
This is one approach that is reasonably decoupled from an existing legacy model.
There are three main parts to this technique:
- Enhance your model objects so they have a placeholder/collection so you can accumulate links
- Annotate your model at interesting points with link templates, around attributes or methods or classes
- Use a custom ContainerResponseFilter class of jersey to scan for annotations of interest, then evaluate your link templates (at run time), using the data of your models and any request based information you may require.
I chose to enhance my model classes using AspectJ at compile time.
mvn glassfish:run
./curl_test #will hit two urls
and you should see some results, with links like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<country xmlns:ns2="http://www.w3.org/1999/xlink">
<links>
<link rel="" ns2:type="locator"
ns2:href="http://localhost:8080/hateoas4legacy/rs/cities/Brisbane"/>
<link rel=""
ns2:type="locator" ns2:href="http://localhost:8080/hateoas4legacy/rs/cities/Sydney"/>
<link rel=""
ns2:type="locator" ns2:href="http://localhost:8080/hateoas4legacy/rs/cities/Melbourne"/>
<link rel=""
ns2:type="locator" ns2:href="http://localhost:8080/hateoas4legacy/rs/countries/AU"/>
</links>
<cities>
<city>
<links/>
<name>Brisbane</name>
</city>
<city>
<links/>
<name>Sydney</name>
</city>
<city>
<links/>
<name>Melbourne</name>
</city>
</cities>
<isoCode>AU</isoCode>
<name>Australia</name>
</country>
All countries Results
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<countries>
<country xmlns:ns2="http://www.w3.org/1999/xlink">
<links>
<link rel=""
ns2:type="locator" ns2:href="http://localhost:8080/hateoas4legacy/rs/countries/AU"/>
</links>
<cities/>
<isoCode>AU</isoCode>
<name>Australia</name>
</country>
<country xmlns:ns2="http://www.w3.org/1999/xlink">
<links>
<link rel=""
ns2:type="locator" ns2:href="http://localhost:8080/hateoas4legacy/rs/countries/USA"/>
</links>
<cities/>
<isoCode>USA</isoCode>
<name>United States of America</name>
</country>
</countries>
It is very crufty, but does work in the simple cases. I haven't had a chance to demonstrate all of the features yet.
I'm likely to further flesh out this example as it is interesting in other RESTful related ways.
6 Responses
Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.
O.k. so this is for “legacy” models. But what about say hibernate annotated models or models where I can put up my own annotation. Is there any library that achieves an automatic interconnectednes output for jersey?
Although I label “HATEOAS4legacy”, the technique isn’t just for applicable for legacy models nor jaxb annotated models.
This is all about getting a degree of decoupling from your model objects through to your representations aka views. We all know this separation has worked well for us in the past.
You could avoid this separation and implement it in a simpler fashion, but it you would only solve it for a specific problem or set of classes.
There are three main parts of this technique:
1. Enhance your model objects so they have a placeholder/collection so you can accumulate links
2. Annotate your model at interesting members where you want links
3. In a ContentResponseFilter, scanning for annotations of interest and inserting them into the collection
Step 1, I used aspectj, to keep things decoupled, but you could take a simpler and more intrusive approach and add in a Links collection to your hibernate objects that isn’t persisted. To use aspectj on this over your hibernate objects, would likely lead to complications.
In Step 2, If you are doing really simple links like parent to child etc. you probably could get away with this.
In Step 3, You can scan any parts of your class, scanning annotations is convenient, but you could just as easily scan for particular classes or fields, but once again that is more coupling than the annotation scanning.
In my sample code I had link generation being done by a LinkTemplateProcessor, but maybe you could have a HibernateAssociationProcessor.
Largely it will depend on the links you need to generate and whether or not you can get enough contextual information from your hibernate annotation.
Some links you will want to drill down, others you may want to jump back up to a parent, or even just a particular page for a particular user, like user preferences.
More specifically when you have links that aren’t in hibernate mapping, how are you going to specify and generate them?
Sure you can insert them directly, probably in your ContainerResponseFilterDumb, it’s just a matter of choice.
Overall, for really simple links, I guess you could probably use Hibernate.
But for some of the other links, it may just be easier to use the LinkTemplate annotations I’m suggesting.
The ContainerResponseFilterDumb that I’ve implemented, is really shabby and inefficient. Time permitting, I should improve on it, probably using something like scannotations.sf.net, it may be a good fit.
On a side note, I personally would not advocate using Hibernate for this approach in general as it takes away from the designing of your resources free (kind of) of the constraints of database implementation.
But yes I’m pragmatic enough to understand there are times when you just need to pump stuff out.
Hi Brett.
This is great stuff, it is exactly what I need to do. However, my domain objects have an ID of type ‘Long’, and the ‘getResourceLinkFields’ method in the filter is falling over with an NPE. I am having trouble modifying the code to work with Longs rather than strings like in your example.
Can you suggest how to update the code to work in my case ?
sure, send me the stack trace and I’ll take a look.
Hi.
OK, I added a ‘private Long id’ to Country, and ran your sample.
I added some debug so the line numbers are different, but the error is occurring on line :
‘Class clazz = entity.getClass();’ in the ‘getResourceLinkFields’ method.
Stacktrace below:
Hit ENTER for redeploy
Entity is : [com.example.rest.model.jaxb.Country@7f6a92, com.example.rest.model.jaxb.Country@2dbcf0]
clazz is : class com.example.rest.model.jaxb.Country
declared fields of : com.example.rest.model.jaxb.Country@7f6a92 are : [Ljava.lang.reflect.Field;@cf64f8
Apr 4, 2009 3:01:27 PM org.apache.catalina.core.StandardWrapperValve log
SEVERE: StandardWrapperValve[Jersey Web Application]: PWC1406: Servlet.service() for servlet Jersey Web Application threw exception
java.lang.NullPointerException
at com.example.rest.connectedness.filter.ResourceLinkResponseFilterDumb.getResourceLinkFields(ResourceLinkResponseFilterDumb.java:101)
at com.example.rest.connectedness.filter.ResourceLinkResponseFilterDumb.addResourceLinks(ResourceLinkResponseFilterDumb.java:65)
at com.example.rest.connectedness.filter.ResourceLinkResponseFilterDumb.getResourceLinkFields(ResourceLinkResponseFilterDumb.java:109)
at com.example.rest.connectedness.filter.ResourceLinkResponseFilterDumb.addResourceLinks(ResourceLinkResponseFilterDumb.java:65)
at com.example.rest.connectedness.filter.ResourceLinkResponseFilterDumb.filter(ResourceLinkResponseFilterDumb.java:46)
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:581)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:514)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:505)
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:359)
Thanks for your help,
Ross
Continuing the Discussion