users@jsr311.java.net

JAX-RS: UriBuilder.fromResource treats templates differently

From: Manger, James H <James.H.Manger_at_team.telstra.com>
Date: Mon, 7 Jul 2008 09:19:34 +1000

A @Path value can be used to 1) match a request to a resource, and 2) to build a URI.
The two uses are inconsistent in how they treat {…} placeholders.
UriBuilder effectively treats all placeholders as unlimited even when they were limited during matching (disallowing /).

Consider this code:

  @Path(“{bar}”)
  public class FooResource
  {
    @GET
    public URI doStuff(@PathParam(“bar”) String s)
    {
      return UriBuilder.fromResource(FooResource.class).build(s);
    }
  }

The URI “123%2Fabc” matches this resource.
However, UriBuilder returns “123/abc”, which does NOT match the resource –
despite being built with fromResource(). YIKES, that was surprising!

This problem could be fixed *in this example* by annotating doStuff’s parameter with @Encoded and telling UriBuilder not to encode:
    public URI doStuff(@PathParam(“bar”) @Encoded String s)
    {
      return UriBuilder.fromResource(FooResource.class).encode(false).build(s);
    }
This is not a general solution however. It is still a problem when the parameter values passed to UriBuilder.build(…) come from some other source. The UriBuilder.build() caller has to do their own encoding, and the caller has to know what encoding to do (eg is / allowed? how about ampersands or semicolons? …), which will depend on the template. This seems to defeat the purpose of templates somewhat.


UriBuilder is not consistent with draft-gregorio-uritemplate-03 – nor is it likely to be consistent with any final spec. A specific design choice in draft-gregorio-uritemplate-03 is to prevent any <reserved> chars coming from a parameter value. All URI structure (ie <reserved> chars) is controlled by the template author, not the party providing the parameter values. <reserved> chars in parameter values are always %-escaped. UriBuilder, in contrast, %-escapes much less (just ? and # in paths, I think; not sure what elsewhere).
The 18 <reserved> chars are : / ? # [ ] @ ! $ & ‘ ( ) * + , ; =


SOLUTION

I don’t have a nice, quick, easy solution.

My preference would be for UriBuilder to support an Internet URI template spec once such a thing is finalized.
That point is not close enough for JAX-RS so I think UriBuilder should only try to support the most basic templates that will (almost) certainly be compatible with a future URI template spec.

My suggestion:
• UriBuilder should accept templates with {name} placeholders.
• Each placeholder is replaced with the corresponding parameter value after %-escaping all non-<unreserved> characters in that value (or replaced by an empty string if the parameter is not defined). UriBuilder.isEncoded() does NOT apply to parameters passed to UriBuilder.build() – they always get encoded.
• The name in {name} should be a valid Java identifier (or an identifier as defined in Unicode annex #31 “Identifier and Pattern syntax” http://www.unicode.org/reports/tr31/ ). Future versions of JAX-RS may give special meaning to other names, though implementations of this version of JAX-RS must treat them all the same.
Now the hard part, what to do with unlimited (or :regex) placeholders from @Path values. Three options (from most preferred to least):
1. Scrap the UriBuilder.fromResource(Class) and UriBuilder.path(Class/Method) methods – admitting that parsing and building (though related) are too awkward to unite in a single syntax. Code can still call
  UriBuilder.fromPath(FooResource.class.getAnnotation(Path.class).value());
if it really wants.
2. UriBuilder explicitly states that when it accepts @Path values, it totally ignores the limited=false attribute (or:regex component).
3. Something more complicated.

A URI template spec is likely to allow a placeholder to expand into multiple path segments (like an unlimited @Path placeholder) – but only if each segment is passed as a separate string (eg { “123”, “abc” } -> “123/abc”; while “123/abc” -> “123%2Fabc”). Consequently, the following method would be useful:
  UriBuilder.build(MultivaluedMap<String,Object> values)
It would be a bit strange to define such a method before UriBuilder supports templates sophisticated enough to indicate how multiple values are handled. Would it cause any hassles if adding this method was deferred to a future version of the spec (given that UriBuilder is an abstract class, not an interface)?

James Manger