users@jax-rs-spec.java.net

[jax-rs-spec users] [jsr339-experts] HEADS-UP, IMPORTANT: Problems with JAX-RS interceptors

From: Marek Potociar <marek.potociar_at_oracle.com>
Date: Thu, 26 Apr 2012 14:29:52 +0200

Hello all,

As we started to look more closely into implementing support for JAX-RS Interceptors API in Jersey, we ran into several major issues. It seems, that there are serious questions around the interaction between filters and interceptors. This is IMO to a large extend caused by the functionality overlap between the interceptors and filters (applies to both filtering API proposals - the old one as well as the new one).

A very brief summary of the concept: Interceptors form a wrapping chain and are supposed to intercept calls to entity providers (MBR/MBW) whenever the entity is read or written either by the client, resource method or a filter. Filters form an un-wrapping chain and filter every request and response send or received. Unfortunately, it seems that the two concepts just don't work well together the way they are designed.

Here is the list of the issues and open questions that we identified so far:

Name-bound interceptors seem to clash with pre-matching filters. If a pre-matching filter will try to manipulate the entity, the name-bound interceptors would not get invoked, which may be fatal (see Example 1).
The generic type information on the interceptor seems redundant. It is not clear what should the runtime do when an interceptor is configured for a type that it does not support in it's generic type declaration.
The reader interceptors are now not restricted in any way wrt. changing the returned entity Java type to a type that is incompatible with the Java type requested by the caller (either JAX-RS runtime or an interceptor up in the chain). Changing the type of the returned entity in a non-compatible way seems to always have fatal consequences as, by definition, the caller expects to receive the original type it asked for.
Filters may un-intentionally corrupt the entity stream in applications configured asymmetrically wrt. interceptors (see Example 2)

A couple of examples to demonstrate the issues:

Example 1: Interceptors interfere with filters reading/writing entity - named interceptors

Suppose the successful reading requires a special named interceptor attached to a resource method.
How do we guarantee that the entity can be read in a pre-matching filter?

Example 2: Interceptors interfere with filters reading/writing entity - asymmetrical configuration

Suppose a filter wants to replace an entity using writeEntity method. Suppose a special (inbound) reader interceptor is configured in the interceptor chain (e.g. signature verifier), but there is no (outbound) writer interceptor counterpart (e.g. signature appender).
How do we guarantee that the entity written by the filter will be readable again? Note that not having a corresponding outbound (signature appender) interceptor configured for the request is a valid and justifiable use case. Even worse, the corresponding outbound functionality can be, in general, provided by an outbound filter instead of an interceptor, which will obviously not get invoked as part of the writeEntity method.

We haven't found any reasonable way to resolve the outlined issues without changing the API. Here are couple of options for a solution we can see:
Option 1
interceptors should not be typed, should be stream focused - i.e. could be renamed to Input/OutputStreamInterceptors
people should use MBW and/or filters for entity type-dependent manipulation
interceptors should only be global (as they may need to be invoked in the pre-matching phase)
interceptors should be executed just once per request/response when reading from/writing to the wire, not for intermediate transformations (hence calling it MBW interceptors may be misleading)
filters should work with entities (decoded) - with one possible kind of entity being a stream
drop the get/setEntityStream methods as redundant (or keep them just as convenience shortcuts for read/writeEntity counterparts)
Option 1 seems to allow the interceptors to be reused between client and server side. But not sure if this is not only useful in a very limited set of use cases (e.g. real life may show that in most cases a server and client-side implementation may need to be separate anyway due to the need of attaching/checking different headers on the client side than on the server side etc.).
Option 2
completely remove interceptors, have just filters
filters are more versatile and can wrap the input/output stream by the way of calling get/setEntityStream() - which covers the functionality separated out into interceptors
Option 2 stream-lines the API and makes it easier to understand by the end user as it removes the need to take the two different concepts into account when writing a filter or interceptor or make decisions about whether the intended functionality belongs to a filter or an interceptor or a combination of the two.

Please, let us know what is your preferred solution for the issues. We need your feedback ASAP as we plan to postpone the EDR3 release a week or two so that we can release a solid filtering API proposal.

Thank you.

Marek & Santiago