Hi Dhanji,
Dhanji R. Prasanna wrote:
> I prefer #2 as well but not terribly happy about the
> AcceptabilityEvaluator (seems like an unnecessary dep on a framework
> artifact).
Note that we also have a precondition evaluator for calculating not
modified response. With some minor modifications the two can be chained
rather nicely.
> How about if we flip the apis:
>
> public class Widget {
> @Accepts public boolean canAccept(@Lang lang, @MediaType String
> type) { ... }
>
> @HttpMethod public Response get(@Lang String lang, @MediaType String
> type) {
> return Response.Builder.entity(widget.getStream(lang, type)
> ).build();
> }
> }
>
> This lets it be dynamic as well as reducing any large set of artifacts
> or dependency on a framework API.
>
I presume that the 'canAccept' method would get called n times where n
is the product of the number of items on each acceptable axis. And the
runtime has to order the calls w.r.t. to the quality parameters.
Although there could still be ambiguity, which takes precedence a
"application/xml;q=0.8" with "en;q=0.6" or "application/json;q=0.6" with
"fr;q=0.8"? However, i think that AcceptabilityEvaluator could be
extended to a builder pattern (a nice parallel to Response.Builder) that
could get around this issue:
ae.accept(languages, type).
accept(type, languages).build();
Hmm... we could do:
AcceptabilityEvaluator.accept(languages, type).
accept(type, languages).build();
if the static method can get access to a runtime context from the
current thread (although i am not sure if this is bad practice or not).
Of course the accept header values could be passed instead for just one
call, but then if that is the case, some form of helper would be useful
to aid in working work out what is acceptable...
I find that the introduction of a method calling order contract with the
runtime can introduce complexities for the developer (which may be OK
but we need to be aware of the consequences). Letting the developer be
in control of the thread of execution for the HTTP request with the
runtime passing control just once requires little or no modification to
existing concepts: for documentation, inheritance,
ProduceMime/ConsumeMime, the HTTP method, sub-resource methods,
singleton or per-request lifecycle etc.
Paul.
> I haven't thought about how it compares to the list of Variants case
> thoroughly... comments?
>
> Dhanji.
>
> On 7/4/07, *Marc Hadley* < Marc.Hadley_at_sun.com
> <mailto:Marc.Hadley_at_sun.com>> wrote:
>
> Trying to weigh the possible approaches for supporting content
> negotiation based on dynamic metadata, I came up with the example of
> a set of widgets that are available in few different media types and
> languages but the availability differs for each widget (i.e. some are
> available in one language, some in two, some have html, some just a
> jpeg).
>
> The first approach is my interpretation of what Jerome is suggesting
> (Jerome if this isn't what you meant then please correct - I looked
> at the Variant class in Restlet but I might have missunderstood its
> use):
>
> @UriTemplate("widgets/{widget}")
> public class Widget {
>
> WidgetEntity widget;
>
> public Widget(@UriParam("widget") String widgetId) {
> widget = findWidgetEntity(widgetId);
> }
>
> @Variants
> List<Variant> getVariants(@HttpMethod String method) {
> List<Variant> variants = new ArrayList<Variant>();
> for (MediaType type: widget.getTypes()) {
> for (String lang: widget.getLanguages()) {
> Variant v = new Variant(type, lang);
> variants.add(v);
> }
> }
> return widget.getTypes();
> }
>
> @HttpMethod
> public InputStream getWidget(@HttpContext Variant v) {
> return widget.getStream(v.type, v.lang);
> }
> }
>
> In the above, the runtime:
>
> (i) calls the constructor,
> (ii) calls the getVariants method to obtain a list of possible
> variants for the current request (I added a @HttpMethod since it
> seems possible that you'd want to return different things depending
> on the method though that isn't shown above, you could also use any
> of the other annotations that can be used on a resource method).
> (iii) perform the conneg matching and either returns an appropriate
> error if there's no acceptable variant or
> (iv) calls the getWidget method passing in the selected variant (note
> there's no need to set the negotiated metadata since the runtime can
> do that automatically. If an application wanted to add metadata then
> it would need to return Response and create one from the passed in
> Variant.
>
> An alternate approach is my interpretation of what Paul is suggesting:
>
> @UriTemplate("widgets/{widget}")
> public class Widget {
>
> WidgetEntity widget;
> @HttpContext AcceptabilityEvaluator ae;
>
> public Widget(@UriParam("widget") String widgetId) {
> widget = findWidgetEntity(widgetId);
> }
>
> @HttpMethod
> public Response getWidget() {
> Response.Builder rb = ae.evaluate (widget.getTypes(),
> widget.getLanguages());
> if (rb.getStatus() < 300)
> rb.entity(widget.getStream(rb.getType(), rb.getLanguage()));
> return rb.build();
> }
> }
>
> In the above, the runtime calls the constructor followed by the
> getWidget method and the application uses the
> "AcceptabilityEvaluator" to determine if there is an acceptable
> response or not. The ae.evaluate method returns an appropriate error
> response if there's no acceptable combination or a prefilled OK
> response if there is. The application adds an entity if required and
> return the response.
>
> Personally I prefer the second approach for the following reasons:
>
> - There's no need to build a list of variants every time
> - The context of the request is implicit since the conneg occurs
> directly in the method that will service the request
> - The application gets a chance to tweak or override the default
> error response when no matching variant is found
>
> Thoughts, comments ?
> Marc.
>
>
> On Jun 30, 2007, at 3:02 AM, Jerome Louvel wrote:
>
> >
> > Hi Mark,
> >
> >>> It seems that you mix the ability to expose dynamic representation
> >>> metadata
> >>> and the content negotiation algorithm (interpreting the Accept*
> >>> headers).
> >>>
> >> I don't think its really a matter of mixing them, rather the
> runtime
> >> hands off to the application when there isn't sufficient static
> >> information to make a determination.
> >
> > The conneg algo doesn't need to rely on static metadata only. It
> could
> > perfectly work on dynamic metadata too (like we do in Restlet).
> >
> >>> For the first one, we should just have annotation ways to
> >> expose those metadata, and for some rare cases rely on an
> >> "EntityProvider" service.
> >>>
> >> I don't think EntityProvider is related to dynamic metadata. You can
> >> associate some static metadata with one (ConsumeMime, ProduceMime)
> >> but by the time an EntityProvider enters the process all of the
> >> conneg will have already taken place.
> >
> > Why does it have to be processed this way? If you design the API
> for a
> > stricter separation between the resources/representations and
> > conneg algo,
> > this perfectly possible IMHO.
> >
> >> For "annotation ways to expose those metadata" do you mean something
> >> like an annotated method that would return, say, the list of
> >> available languages ?
> >
> > Yes, and not only for languages. The notion of variant (consistent
> > set of
> > metadata) is important too.
> >
> > Best regards,
> > Jerome
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: dev-unsubscribe_at_jsr311.dev.java.net
> <mailto:dev-unsubscribe_at_jsr311.dev.java.net>
> > For additional commands, e-mail: dev-help_at_jsr311.dev.java.net
> <mailto:dev-help_at_jsr311.dev.java.net>
> >
>
> ---
> Marc Hadley <marc.hadley at sun.com <http://sun.com>>
> CTO Office, Sun Microsystems.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe_at_jsr311.dev.java.net
> <mailto:dev-unsubscribe_at_jsr311.dev.java.net>
> For additional commands, e-mail: dev-help_at_jsr311.dev.java.net
> <mailto:dev-help_at_jsr311.dev.java.net>
>
>
--
| ? + ? = To question
----------------\
Paul Sandoz
x38109
+33-4-76188109