users@jersey.java.net

Re: Resources and subresources

From: Richard Wallace <rwallace_at_thewallacepack.net>
Date: Fri, 30 Nov 2007 09:19:44 -0800

Paul Sandoz wrote:
> Hi Richard,
>
> Richard Wallace wrote:
>> Ok, so I jumped the gun a little again. I was using 0.5-ea but I
>> just updated to the latest trunk build and things are better. My
>> getBookmark(@UriParam("uri") uri) is no longer getting the full path,
>> it's getting just the parameter part.
>>
>
> If you want to match the whole URI then you need to set the limited
> parameter on the @Path to false.
>
> You cannot have a template like the following:
>
> @Path("/bookmarks/{uri}/comments/{comment-id}"
>
> if {uri} should be the bookmark URI because you never know the
> termination of that URI.
>

When I saw this I can't tell you how disappointed I was. Thankfully, it
seems you were mistaken. I just tried it and it works perfectly. I
created two more resources as follows

@Path("/bookmarks/{uri}/comments")
public class CommentsResource { ... }

and

@Path("/bookmarks/{uri}/comments/{comment-id}")
public class CommentResource { ... }

When I hit

http://localhost:8080/jersey-spike-webapp/api/bookmarks/http%3A%2F%2Fwww.google.com/comments

it goes to my CommentsResource as I expected. When I hit

http://localhost:8080/jersey-spike-webapp/api/bookmarks/http%3A%2F%2Fwww.google.com/comments/0

it goes to my CommentResource and passes http://google.com as the value
for the @UriParam("uri") parameter and 0 as the value for the
@UriParam("comment-id") parameter.

I think this is the last major hurdle I had to overcome. I think I am
going to move forward with using Jersey on my new project.
> (BTW if you don't know already there is a bookmark example in the
> distribution).
>
>
>> I also I finally figured out why my BookmarksResource wasn't getting
>> used. I was trying to hit
>>
>> http://localhost:8080/spike/bookmarks/
>>
>> instead of
>>
>> http://localhost:8080/spike/bookmarks
>>
>> Hitting the first results in the BookmarkResource being called with
>> an empty string for the uri. Hitting the second does the right
>> thing. Not the most intuitive behavior, but now that I know what to
>> look out for...
>>
>
> Agreed. The problem is the URI template variable is replaced by the
> expression (.*?) rather than (+*?) which is why when you put a '/' at
> the end of the request URI a different match occurs. There is a unit
> test for this in place that is currently failing. We plan to do
> something about this in the 311 EG. IMHO i think by default the
> expression should be (+*?).
>
> Note that template draft has been updated for richer expression of
> template values. The draft is written from the perspective of
> generation rather than matching so we have to look at what subset of
> the expressions make sense for matching purposes.
>
> Paul.
>
>> Richard Wallace wrote:
>>> Alright, well it looks like I spoke too soon. I tried using the
>>> following as you suggested:
>>>
>>> @UriTemplate("bookmarks")
>>> public class BookmarksResource { ... }
>>>
>>> @UriTemplate("bookmarks/{uri}")
>>> public class BookmarkResource { ... }
>>>
>>> with the uris
>>>
>>> http://localhost:8080/spike/bookmarks/http://google.com
>>> http://localhost:8080/spike/bookmarks
>>>
>>> But this seems to be even more broken. Now my
>>>
>>> @HttpMethod("GET") BookmarksResource.getBookmarks()
>>>
>>> still never gets called. Instead the
>>>
>>> @HttpMethod("GET") BookmarkResource.getBookmark(@UriParam("uri") uri)
>>>
>>> is _always_ called, and when it does get called, the value of the
>>> uri param is "bookmarks" or "bookmarks/http://google.com". For some
>>> reason it's using the path for the parameter!
>>>
>>> What am I screwing up now?
>>>
>>> Rich
>>>
>>> Richard Wallace wrote:
>>>> Awesome. Thanks for the response. That is exactly what I was
>>>> hoping for.
>>>>
>>>> Rich
>>>>
>>>> Marc Hadley wrote:
>>>>> On Nov 28, 2007, at 12:52 AM, Richard Wallace wrote:
>>>>>>
>>>>>> I've been playing with with resources and subresources and was
>>>>>> trying to fit in with the way the JSR311 spec works. So, you
>>>>>> have root resources that return subresources. What I was hoping
>>>>>> to do was get an instance of a ResourceFactory to create the the
>>>>>> resource to return. Unfortunately, the createResource() method
>>>>>> needs the resource features and properties from the
>>>>>> ResourceConfig which I don't have. I was then hoping to just use
>>>>>> my Guice injector to create the resource, but I don't have access
>>>>>> to the ServletContext (well, I can, but it's an ugly hack).
>>>>>>
>>>>>> What I tried next is the way I would prefer to create my
>>>>>> resources. That is to annotate each resource with the
>>>>>> @UriTemplate that it handles. So for a list of all bookmarks and
>>>>>> then a single bookmark I'd have resources like:
>>>>>>
>>>>>> @UriTemplate("/")
>>>>>> public class BookmarksResource { ... }
>>>>>>
>>>>>> @UriTemplate("/{uri}")
>>>>>> public class BookmarkResource { ... }
>>>>>>
>>>>>> With this pattern I don't have to worry about creating
>>>>>> BookmarkResource instances from BookmarkResource and injecting
>>>>>> all the right values. It would also make deeper hierarchies
>>>>>> easier to build because I'm not sure how it's intended to be
>>>>>> done, but from my understanding hitting a resource with a path of
>>>>>> /bookmarks/{uri}/comments/{comment-id} would require the
>>>>>> BookmarksResource to be created, which would create a
>>>>>> BookmarkResource, which would create a CommentsResource which
>>>>>> would finally create a CommentResource that is returned. That
>>>>>> means you could need to inject a whole bunch of dependencies into
>>>>>> your root level resource so they can trickle down to the deeper
>>>>>> resource objects. I'd much rather have all my resources created
>>>>>> equally.
>>>>>>
>>>>> If you're resources are such that you can use a URI template to
>>>>> identify them then that is definitely the way to go rather than
>>>>> using sub-resources. Sub-resources are there to handle cases where
>>>>> the URI path is somewhat dynamic or when the same resource can be
>>>>> found at multiple paths (though you can always create a subclass
>>>>> for each and then still use a template).
>>>>>
>>>>>> I had thought this might still work even if it seems to go
>>>>>> against the grain of JSR311, but found that it doesn't.
>>>>>
>>>>> Its not against the grain of 311, that is how it is supposed to be
>>>>> used for the majority of cases. I think you've just run into an
>>>>> issue using '/' as a template. The value of @UriTemplate is a
>>>>> relative URI whose base URI is supplied by the deployment context.
>>>>> I suspect we may have an issue dealing with what is effectively an
>>>>> empty template.
>>>>>
>>>>>> What happens is that when I hit
>>>>>> http://localhost:8080/spike/http://google.com, everything works
>>>>>> as expected. But when I try and hit
>>>>>> http://localhost:8080/spike/, I get a 500 response with a message
>>>>>> that says "The "Content-Type" header is set to text/xml, but the
>>>>>> response has no entity". After doing some digging it seems this
>>>>>> is because for the / path the BookmarkResource is being used
>>>>>> instead of the BookmarksResource. Because the uri is empty no
>>>>>> bookmark is found and null is returned.
>>>>>>
>>>>>> There were 2 things unexpected about this. The first was that
>>>>>> BookmarkResource was being used, which I'll get to in a minute.
>>>>>> The second is that returning null resulted in a 500. My
>>>>>> expectation was that returning null would indicate a 404, because
>>>>>> the resource at the location could not be found. To me, that's
>>>>>> really the only reason a method annotated with @HttpMethod would
>>>>>> ever return null, is if there was nothing to return. Otherwise
>>>>>> it would throw an exception.
>>>>>>
>>>>>> Getting back to the issue at hand, I did some more digging to
>>>>>> find out exactly how it was being decided that the
>>>>>> BookmarkResource should handle / instead of the
>>>>>> BookmarksResource. What I found was that the rule for matching
>>>>>> the BookmarkResource was being checked first and was using the
>>>>>> regex /(.*?)(/)?, or something similar, that's from memory. The
>>>>>> important part is the /.* bit. This means the BookmarkResource
>>>>>> will be matched for anything. That would solve this problem. Is
>>>>>> there any ordering going on when the resources are being added,
>>>>>> or are they simply matched in the order they're found when
>>>>>> scanning? If there is an order, shouldn't the rule for the
>>>>>> BookmarksResource be first, since it is less specific? If there
>>>>>> isn't any ordering being done, don't you think there probably
>>>>>> should be?
>>>>>>
>>>>> Matching resources are sorted using the number of literal
>>>>> characters (i.e. those not resulting from template variables) as
>>>>> the primary key and the number of template variables as the
>>>>> secondary key.
>>>>>
>>>>> I think if you changed your template to be something like:
>>>>>
>>>>> @UriTemplate("bookmarks")
>>>>> public class BookmarksResource { ... }
>>>>>
>>>>> @UriTemplate("bookmarks/{uri}")
>>>>> public class BookmarkResource { ... }
>>>>>
>>>>> with the uris
>>>>>
>>>>> http://localhost:8080/spike/bookmarks/http://google.com
>>>>> http://localhost:8080/spike/bookmarks
>>>>>
>>>>> then everything would be OK.
>>>>>
>>>>> Marc.
>>>>>
>>>>> ---
>>>>> Marc Hadley <marc.hadley at sun.com>
>>>>> CTO Office, Sun Microsystems.
>>>>>
>>>>>
>>>>> ---------------------------------------------------------------------
>>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>>>
>>>>
>>>> ---------------------------------------------------------------------
>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>>
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>
>