JBoss.orgCommunity Documentation

Chapter 29. Interceptors

29.1. MessageBodyReader/Writer Interceptors
29.2. PreProcessInterceptor
29.3. PostProcessInterceptors
29.4. ClientExecutionInterceptors
29.5. Binding Interceptors
29.6. Registering Interceptors
29.7. Interceptor Ordering and Precedence
29.7.1. Custom Precedence

Resteasy has the capability to intercept JAX-RS invocations and route them through listener-like objects called interceptors. There are 4 different interception points on the serverside: wrapping around MessageBodyWriter invocations, wrapping around MessageBodyReader invocations, pre-processors the intercept the incoming request before anything is unmarshalled, and post processors which are invoked right after the JAX-RS method is finished. On the client side you can also intercept MessageBodyReader and Writer as well as the remote invocation to the server.

MessageBodyReader and Writer interceptors work off of the same principles. They wrap around the invocation of MessageBodyReader.readFrom() or MessageBodyWriter.writeTo(). You can use them to wrap the Output or InputStream. For example, the Resteasy GZIP support has interceptors that create and override the default Output and InputStream with a GzipOutpuStream or GzipInputStream so that gzip encoding can work. You could use them to append headers to the response (or on the client side, the outgoing request).

To implement one you implement the org.jbos.resteasy.spi.interception.MessageBodyReaderInterceptor or MessageBodyWriterInterceptor


public interface MessageBodyReaderInterceptor
{
   Object read(MessageBodyReaderContext context) throws IOException, WebApplicationException;

}

public interface MessageBodyWriterInterceptor
{
   void write(MessageBodyWriterContext context) throws IOException, WebApplicationException;

}

            

Interceptors are driven by the MessageBodyWriterContext or MessageBodyReaderContext. The interceptors and the MessageBodyReader or Writer is invoked in one big Java call stack. You must call MessageBodyReaderContext.proceed() or MessageBodyWriterContext.proceed() to go to the next interceptor or, if there are no more interceptors to invoke, the readFrom() or writeTo() method of the MessageBodyReader or MessageBodyWriter. This wrapping allows you to modify things before they get to the Reader or Writer then clean up after proceed() returns. The Context objects also have methods to modify the parameters going to the Reader or Writer.


public interface MessageBodyReaderContext
{
   Class getType();

   void setType(Class type);

   Type getGenericType();

   void setGenericType(Type genericType);

   Annotation[] getAnnotations();

   void setAnnotations(Annotation[] annotations);

   MediaType getMediaType();

   void setMediaType(MediaType mediaType);

   MultivaluedMap<String, String> getHeaders();

   InputStream getInputStream();

   void setInputStream(InputStream is);

   Object proceed() throws IOException, WebApplicationException;
}

public interface MessageBodyWriterContext
{
   Object getEntity();

   void setEntity(Object entity);

   Class getType();

   void setType(Class type);

   Type getGenericType();

   void setGenericType(Type genericType);

   Annotation[] getAnnotations();

   void setAnnotations(Annotation[] annotations);

   MediaType getMediaType();

   void setMediaType(MediaType mediaType);

   MultivaluedMap<String, Object> getHeaders();

   OutputStream getOutputStream();

   public void setOutputStream(OutputStream os);

   void proceed() throws IOException, WebApplicationException;
}

MessageBodyReaderInterceptors and MessageBodyWriterInterceptors can be used on the serverside or client side. The must be annotated with @org.jboss.resteasy.annotations.interception.ServerInterceptor or @org.jboss.resteasy.annotations.interception.ClientInterceptor so that resteasy knows whether or not to add them to the interceptor list. If you do not annotate your interceptor classes with one or both of these annotations, you will receive a deployment error. They also should be annotated with @Provider. Lets look at an example:

@Provider
@ServerInterceptor
public class MyHeaderDecorator implements MessageBodyWriterInterceptor {

    public void write(MessageBodyWriterContext context) throws IOException, WebApplicationException
    {
       context.getHeaders().add("My-Header", "custom");
       context.proceed();
    }
}

Here we have a server side interceptor that adds a header value to the response. You see that it is annotated with @Provider and @ServerInterceptor. It must modify the header before calling context.proceed() as the reseponse may be committed after the MessageBodyReader runs. Remember, you MUST call context.proceed(). If you don't, your invocation will not happen.

The org.jboss.resteasy.spi.interception.PreProcessInterceptor runs after a JAX-RS resource method is found to invoke on, but before the actual invocation happens. They are only usable on the server, but still must be annotated with @ServerInterceptor. They can be used to implement security features or can preempt the Java request. The Resteasy security implementation uses this type of interceptor to abort requests before the actually happen if the user does not pass authorization. The Resteasy caching framework also uses this to return cached responses to avoid invoking methods again. Here's what the interceptor interface looks like:

    public interface PreProcessInterceptor
    {
       ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException;
    }

PreProcessInterceptors run in sequence and do not wrap the actual JAX-RS invocation. Here's some pseudo code that illustrates how they work:

    for (PreProcessInterceptor interceptor : preProcessInterceptors) {
       ServerResponse response = interceptor.preProcess(request, method);
       if (response != null) return response;
    }
    executeJaxrsMethod(...);

If the preProcess() method returns a ServerResponse then the underlying JAX-RS method will not get invoked and the runtime will process the response and return to the client.

The org.jboss.resteasy.spi.interception.PostProcessInterceptor runs after the JAX-RS method was invoked but before MessageBodyWriters are invoked. They can only be used on the server side. Use them if you need to set a response header when there might not be any MessageBodyWriter invoked. They are there for symetry with PreProcessInterceptor. They do not wrap anything and are invoked in order like PreProcessInterceptors are.

    public interface PostProcessInterceptor
    {
       void postProcess(ServerResponse response);
    }
    

org.jboss.resteasy.spi.interception.ClientExecutionInterceptor classes only are usable on the client side. They run after the MessageBodyWriter and after the ClientRequest has been totally built on the client side. They wrap around the actually HTTP invocation that goes to the server. Resteasy GZIP support uses them to set the Accept header to contain "gzip, deflate" before the request goes out. The Resteasy client cache uses it to check to see if its cache contains the resource before going over the wire. These interceptors must be annotated with @ClientInterceptor and @Provider.

    public interface ClientExecutionInterceptor
    {
       ClientResponse execute(ClientExecutionContext ctx) throws Exception;
    }

    public interface ClientExecutionContext
    {
       ClientRequest getRequest();

       ClientResponse proceed() throws Exception;
    }
    

The work work in the same pattern as MessageBodyReader/WriterInterceptors in that you must call proceed() unless you want to abort the invocation.

By default, any registered interceptor will be invoked for any request you do. By default, every request will use your interceptors. You can fine tune this by having your interceptors implement the org.jboss.resteasy.spi.AcceptedByMethod interface:

    public interface AcceptedByMethod
    {
       public boolean accept(Class declaring, Method method);
    }

If your interceptor implements this interface, Resteasy will invoke the accept() method. If this method returns true, Resteasy will add that interceptor to the JAX-RS method's call chain. If it returns false then it won't be added to the call chain. For example:

@Provider
@ServerInterceptor
public class MyHeaderDecorator implements MessageBodyWriterInterceptor, AcceptedByMethod {

    public boolean accept(Class declaring, Method method) {
       return method.isAnnotationPresent(GET.class);
    }

   public void write(MessageBodyWriterContext context) throws IOException, WebApplicationException
   {
      context.getHeaders().add("My-Header", "custom");
      context.proceed();
   }
}
        

In this example, our accept() method checks to see if the @GET annotation is present on our JAX-RS method. If it is, then this interceptor will be applied to that method's call chain.

Registering interceptors is easy. Since they are a @Provider, (you remebered to annotate it right?) they can be listed in the resteasy.providers context-param in web.xml or returned as a class or object in the Application.getClasses() or Appication.getSingletons() method.

Some interceptors are very sensitive in which order they are invoked. For example, you always want your security interceptor invoked first. Other interceptor's behavior might be triggered by a different interceptor that adds a header. By default, you have no control over the order in which registered interceptors are invoked. There is a way to specify interceptor precedence though.

You do not specify interceptor precedence by listing interceptor classes. Instead, there are precedence families and a particular interceptor class is associated with a family via the @org.jboss.resteasy.annotations.interception.Precedence annotation. We did this because some of the built in interceptors included with Resteasy are very sensitive to ordering. By specifying precedence through a family structure, we can protect these built in interceptors. An advantage to this approach is that configuration is also a lot easier too for you.

These are the families and the order in which they are executed:

    SECURITY
    HEADER_DECORATOR
    ENCODER
    REDIRECT
    DECODER

Any interceptor not associated with a precedence family will be invoked last. SECURITY usually involves PreProcessInterceptors. They should be invoked first because you want to do as little as possible before your invocation is authorized. HEADER_DECORATORs are interceptors that add headers to a response or an outgoing request. They need to come next because these added headers may effect the behavior of other interceptors. ENCODER interceptors change the OutputStream. For example, the GZIP interceptor creates a GZIPOutputStream to wrap the real OutputStream for compression. REDIRECT interceptors usually are used in PreProcessInterceptors as they may reroute the request and totally bypas the JAX-RS method. DECODER interceptors wrap the InputStream. For example, the GZIP interceptor decoder wraps the InputStream in a GzipInputStream instance.

To marry your custom interceptors to a particular family you annotate it with the @org.jboss.resteasy.annotations.interception.Precendence annotation.

@Provider
@ServerInterceptor
@ClientInterceptor
@Precedence("ENCODER")
public class MyCompressionInterceptor implements MessageBodyWriterInterceptor {...}

For complete type safety, there are convenience annotations in the org.jbos.resteasy.annotations.interception package: @DecoredPrecedence, @EncoderPrecedence, @HeaderDecoratorPrecedence, @RedirectPrecedence, @SecurityPrecedence. Use these instead of the @Precedence annotation

You can define your own precedence families. Apply them using the @Precedence annotation.

 @Provider
 @ServerInterceptor
 @Precedence("MY_CUSTOM_PRECEDENCE")
 public class MyCustomInterceptor implements MessageBodyWriterInterceptor {...}
            

You can create your own convenience annotation by using @Precedence as a meta-annotation

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Precedence("MY_CUSTOM_PRECEDENCE")
public @interface MyCustomPrecedence {}

You must register your custom precedence. Otherwise, Resteasy will give you an error at deployment time. You do this with the context params:

resteasy.append.interceptor.precedence
resteasy.interceptor.before.precedence
resteasy.interceptor.after.precedence

resteasy.append.interceptor.precedence simply appends the precedence family to the list. resteasy.interceptor.before.precedence allows you to specify a family your new precedence comes before. resteasy.interceptor.after.precedence allows you to specify a family your new precedence comes after. For example:


web-app>
    <display-name>Archetype RestEasy Web Application</display-name>

    <!-- testing configuration -->
    <context-param>
        <param-name>resteasy.append.interceptor.precedence</param-name>
        <param-value>END</param-value>
    </context-param>
    <context-param>
        <param-name>resteasy.interceptor.before.precedence</param-name>
        <param-value>ENCODER : BEFORE_ENCODER</param-value>
    </context-param>

    <context-param>
        <param-name>resteasy.interceptor.after.precedence</param-name>
        <param-value>ENCODER : AFTER_ENCODER</param-value>
    </context-param>

    <context-param>
        <param-name>resteasy.servlet.mapping.prefix</param-name>
        <param-value>/test</param-value>
    </context-param>

    <listener>
        <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
    </listener>

    <servlet>
        <servlet-name>Resteasy</servlet-name>
        <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Resteasy</servlet-name>
        <url-pattern>/test/*</url-pattern>
    </servlet-mapping>

</web-app>
        

In this web.xml file, we've define 3 new precedence families: END, BEFORE_ENCODER, and AFTER_ENCODER. Here's what the family order would look like with this configuration:

SECURITY
HEADER_DECORATOR
BEFORE_ENCODER
ENCODER
AFTER_ENCODER
REDIRECT
DECODER
END