jsr339-experts@jax-rs-spec.java.net

[jsr339-experts] Re: Filters, Interceptors and Priorities

From: Bill Burke <bburke_at_redhat.com>
Date: Thu, 25 Oct 2012 16:03:11 -0400

On 10/25/2012 2:24 PM, Markus KARG wrote:
> Bill,
>
> thank you for your examples.
>
> If fact there is still some open question:
>
>> One example is GZIP encoding:
>>
>> GZIP encoding needs to wrap around the MBW
>>
>> try
>> {
>> setOutputStream(GZIPOutputStream);
>> proceed();
>> }
>> finally
>> {
>> gzipOutputStream.finish();
>> }
>
> This can be done easily with a filter instead, which can call finish() on
> itself as soon as close() is invoked. So I do not need an entity interceptor
> for this:
>
> filter(req,resp) {
> ...
> setEntityStream(MyFilterWhichCallsFinishAtClose(getEntityStream()));
> }
>

Not following you. You mean wrap the OutputSTream to call finish() on
close()? I'm not even sure it is guaranteed close() will be called (or
should be called). Plus, not pushing/popping the outputstream is real
bad practice.

>> Another is DKIM signature header:
>>
>> setOutputStream(buffer);
>> proceed();
>> String signature = getSignature(buffer); ctx.setHeader
>> outptuStream.write(buffer);
>
> Now I see! So the ultimate use case for entity interceptors (in comparison
> to filters) is when one must set a header which is dependent of the
> representation. Indeed, this is impossible to do using the current filter
> APIs! On the other hand, I think this example looks more like an excuse for
> the fact that filters are non-wrapping, which I do not like, BTW (but I
> don't want to warm up the discussion). ;-)
>

Filters cannot wrap client invocations or resource method invocations
because of asynchronicity.

On the server side, you could merge Filters and interceptors and wrap
around marshalling and unmarshalling:

Request call stack:

preMatchfilter1
    ctx.proceed()
       matchRequest()
          postFilter2
            ctx.proceed()
              mbr.readFrom()
          postFilter2 - return
       matchRequest return
preMatchFilter 1 return

and do the same with response. The problem is, its really weird to
explain to users. Also the wrapping pattern would only work on the
client for for outgoing requests (and would impose a difficult
implementation design especially if you were using Apache Client as a
back end). It wouldn't work with client response processing because of
the readEntity() flow problem mentioned below.


>> Response res = target.get(); // response filters run here
>> Customer cust = res.readEntity(); // ReaderInterceptors run here.
>
> ...which more is a technical explanation of filters() vs. entity
> interceptors, but not really a use case in the sense of "Why should user
> want to do that?". ;-)
>
>> Another example is a client side cache that wants to cache based on
>> Java object type. The filter could cache, but it doesn't know the Java
>> object type the user wants yet.
>
> Well, a use case should start with the *user's* intention, not with the
> *implementation's* intention.

The user wants a cache of Java objects, not bytes. A filter could only
cache bytes because it won't know the Java type the user wants until
readEntity() is invoked. A ReaderInterceptorCache would look at the
desired type, see if the java type is cached for that URI, and return
it, if not, it will let the MBR proceed.

Also consider a conditional GET that returns 304. The filter would
change the response code, headers, and would point the response output
stream to a byte buffer. The ReaderInterceptorCache would be
responsible for either getting a cached Java object, or unmarshalling
the cached byte buffer by proceeding with the interceptor chain. Make
sense?

> So the use case is client side caching, which
> can be done using a filter. Whether the filter is caching the representation
> or the java instance does not make a big difference.

There's also the case of wanting to do post-processing on an
unmarshalled Java object. Finally there is the case of re-use. A few
of our entity interceptors work on both the client and server side.

> The difference only
> comes up when looking at performance, as caching java certainly preserves
> the need for again-and-again parsing XML / JSON. And exactly this case is
> why Jan and me asked if we can manually invoke a MBR manually and get the
> interceptor running: If THAT is the ultimate use case for introducing
> interceptors, THEN interceptors must be invoked at manual MBR calls, too.
> Otherwise it would be rather strange to have a cache that works only in SOME
> cases.
>

I completely disagree that manually invoked MBRs should be intercepted.
  Forcing this would break a lot of my current features which depend on
the MBR/MBWs returned by Providers to not be intercepted.

> Thanks a lot for this really interesting insight into the use cases for an
> entity interceptor! :-)
>

I wish you had followed our conversation months ago about this because
all the things i've written here are a complete re-hash of our
arguments. Personally, I've put a lot of thought into this as the
current design evolved out of me trying to implement various features
for Resteasy.

-- 
Bill Burke
JBoss, a division of Red Hat
http://bill.burkecentral.com