I'd like to revive a thread started way back in Sept last year, see:
https://jsr311.dev.java.net/servlets/ReadMsg?list=dev&msgNo=650
The topic was support for URI-based content negotiation, essentially
allowing a client to
GET /foo.xml
as an alternative to
GET /foo
Accept: application/xml
I'd like to offer the following concrete proposal:
- We only offer automatic support for content types, nothing for
language or charset negotiation.
- When the feature is enabled:
* A request URI that ends with an extension is matched as if that
extension were not present. E.g. @Path("widgets") would match requests
for widgets, widgets.xml and widgets.json
* If a URI template ends in a variable then the variable value is
injected without the extension. E.g. @Path("widgets/{id}") with
request for widgets/1.xml and @PathParam("id") String id would result
in a value of "1" for id.
* The extension is compared to the keys in
ApplicationConfig.getExtensionMappings (Map<String, MediaType). If a
match is found any Accept header value is replaced with the value for
the matching key.
- An @Path property style is provided to control the behavior. A value
of 'platonic' means that the path should be treated as part of a
platonic URI and the above behaviour is enabled. A value of 'distinct'
means that the path is distinct and disables the above behaviour. The
default value of 'default' defers to an application wide default
specified as a property of ApplicationConfig (this will default to not
enabled).
- Existing UriInfo methods are not affected, any extension in the URI
is included in the path returned by any of the methods. For
convenience we add UriInfo.getPathExtension() that returns the
extension or null if there isn't one, and
UriInfo.getPlatonicRequestUriBuilder which returns a URI builder for
the request minus the extension. We also add a
UriBuilder.extension(String) that adds the supplied extension to the
current final path segment.
An example:
@Path("widgets")
public class WidgetsResource {
@Context UriInfo uris;
@GET
@ProduceMime({"application/xml", "application/json")
public WidgetList getWidgets() {
....
}
@POST
@ProduceMime({"application/xml", "application/json"})
public Response addWidget(Widget input) {
Widget w = createWidgetEntry(input);
URI platonic = uris.getBaseUriBuilder()
.path(WidgetResource.class)
.build(w.getId());
URI distinct = uris.getBaseUriBuilder()
.path(WidgetResource.class)
.extension(uris.getExtension())
.build(w.getId());
return Response.created(platonic)
.contentLocation(distinct);
}
}
@Path("widgets/{id}")
public class WidgetResource {
...
}
Assume you have an app config that maps "xml" to application/xml and
"json" to application/json. Also assume you have the required msg body
readers and writers for XML and JSON.
GET /widgets.xml will get you XML, GET /widgets.json will get you
JSON, GET /widgets will get whichever matches your accept header most
closely.
You can POST XML or JSON to /widgets.xml but you'll always get back
XML. Same for /widgets.json but you'll always get back JSON.
If you POST to a distinct URI you'll get a platonic location and a
distinct content location. If you post to a platonic you'll get
platonic location and content location (the latter is unfortunate but
a custom message body writer could patch the value once the format is
known.
Thoughts, comments ?
Marc.
---
Marc Hadley <marc.hadley at sun.com>
CTO Office, Sun Microsystems.