users@jersey.java.net

Re: [Jersey] thoughts on _at_Link and Marc's blog post

From: James Strachan <james.strachan_at_gmail.com>
Date: Thu, 11 Mar 2010 14:37:06 +0000

On 11 March 2010 13:32, Marc Hadley <Marc.Hadley_at_sun.com> wrote:
> On Mar 11, 2010, at 5:22 AM, James Strachan wrote:
>> This seems great for 'root resources' or top level resources; though
>> it gets harder when you build deeply nested resources, where (say) a
>> Shipment resource might be a child of a Widget resource - with the
>> Shipment resource only knowing how its linked within a Widget. e.g.
>>
>> class Widget {
>>  @Path("shipment/{shipmentId}")
>>  Shipment getShipment(@PathParam("shipmentId") String shipmentId) {
>>     return new Shipment(this, shipmentId)
>>   }
>> }
>>
>> I wonder if the @Link annotation could also be configured with the
>> 'owner'? e.g.
>>
>> class Shipment {
>>  @Link("shipment/{shipmentId}", ownedBy="widget")
>>   URI link;
>>
>>   String shipmentId;
>>   Widget widget;
>> }
>>
>> Though this is not very DRY as there's a need to replicate the child
>> URI template in both @Link and @Path.
>>
>> I wonder if there's a way to just specify the URI template once on
>> Widget which can then be used both when navigating from Widget ->
>> Shipment and when trying to reverse engineer the URI from a Shipment?
>>
> I've been thinking about this too. One issue I see is that subresources can be made available via more than one root resource so it might not always be ideal to fix on a particular path (though I suppose a canonical URI for accessing a resource isn't really a bad thing).

Agreed. Though in the use case where you want to create a URI for a
resource/DTO, you're typically not going to want to generate a list of
links for every permutation of how you can navigate to the resource;
you probably just want to pick one of the paths to be used as the
canonical path (which with HATEOS you could change your mind later on
if you wish). So choosing one doesn't seem a biggie to me - if its
more complex figuring out what a link is, just do it by hand and don't
use the @Link.

If you wanted to generate 2 links for a given resource/DTO (which
seems really unlikely) you could always use 2 different @Link
annotations on 2 different fields using different owners and/or
locators?


> When you are happy to fix the path then we could offer something like (using your example above):
>
> @Link(resource=Widget.class, locator={"getShipment"})
>
> where the locator property provides the sub-resource locator method. Given this info the link processor can assemble the URI from the root resource and locator path templates. Things get trickier if there are multiple locators used within a single request, annotations don't offer much in the way of syntax to represent a list of class+method pairs other than parallel arrays.

I was kinda thinking that you could use the owner object (the Widget
instance) to generate its link; then keep walking up the tree using
the same '_at_Link mechanism' until you get to a root. So that each @Link
just specifies how to link the current object (Shipment) from its
owner (Widget) - which could be a sub-resource of something else

For example if we had /widgets/5/shipments/7, we'd need the Widget
instance to be able to figure out the "/widgets/5" prefix of the URI.
Note I'm considering resources not on the call stack here too (more
later...)



> When you want to reflect the actual URI used to access the subresource then I was thinking that we could offer access to UriInfo via EL like:
>
> @Link("${UriInfo.requestUri}")
>
>> e.g. if the @Link could just refer to the owner, then you could find
>> the getShipment() method on Widget and find its URI template from the
>> @Path annotation? e.g.
>>
>> class Shipment {
>>  @Link(ownedBy="widget")
>>   URI link;
>>
>>   String shipmentId;
>>   Widget widget;
>> }
>>
>> Then the URI template is only specified once - in the usual place -
>> and it can be deduced by looking for the method in the owner with a
>> @Path annotation which returns an object of the type Shipment? (For
>> the @Path URI template on Widget.getShipment() to be usable with @Link
>> we'd need to use consistent naming of the 'shipmentId' parameter).
>>
>> You could argue that the owner could be deduced too (by analysing all
>> of the available resources and figuring out which resource is capable
>> of making a Shipment) - but you could possibly have a resource which
>> is available via multiple URIs maybe - so being explicit in this case
>> is not such a bad thing IMHO.
>>
> For the URI of the resource or its subresources then I think the ${UriInfo.requestUri} approach will get us most of the way. For linking back up the call stack then UriInfo also has the getMatchedURIs method so you can, e.g., pull out the URI of each resource in the call stack.

Yeah - though I was wondering if a single mechanism could work for
creating the link of a resource/DTO - whether its in the call stack or
not. (Hence my idea of passing the owner instance rather than relying
on the current call stack).

For example, when rendering a Widget's view you might want to include
links to each of its Shipments, despite the fact that the only call
stack is /widgets/5 and no Shipments are in the call stack yet. Or you
might want to render related Widget links other than the current
widget in the call stack. So you might be looking up Widgets using
some other means - outside of the Jersey call stack.

BTW in addition to the @Link annotation I'm hoping we can have a
helper method to generate the link too for times when you just want to
grab related resources/DTOs and generate their links.

Imagine some helper method somewhere...

public URI linkOf(Object resource) { ... }

if we wanted to do...

Shipment shipment = ...
URI linkToShipment = linkOf(shipment);

then we'd maybe want to define the 'link owner' outside of the @Link
annotation; so that it can be used with the @Link annotation or with
the linkOf() method too.

e.g.

public class Shipment {
  @Owner
  Widget widget;

  // lets use @Link too
  @Link URI link;
}

or if you don't want the code to figure out the locator method on
Widget which returns a Shipment you could be specific

public class Shipment {
  @Owner(locator="getShipment")
  Widget widget;
}


-- 
James
-------
http://macstrac.blogspot.com/
Open Source Integration
http://fusesource.com/