Skip to main content
Last updated June 22, 2011 13:34, by spericas
Feedicon  
== Filters and Handlers * [[InterceptorsBackground | Background]] * [[InterceptorsAttic | Original Proposal (no async)]] == Filters and Handlers in JAX-RS 2.0 (Draft) An handler is used to wrap around a method invocation at an extension point. A filter is used to execute code at an extension point but without wrapping a method invocation. This proposal supports both filters and handlers. There are two extension points for filters: Pre and Post. There are two extension points for handlers: ReadFrom and WriteTo. For each of the 4 extension points, there is a corresponding interface (and context): <pre name="java"> public interface RequestFilter { FilterAction preFilter(FilterContext ctx) throws IOException; } public interface ResponseFilter { FilterAction postFilter(FilterContext ctx) throws IOException; } </pre> <pre name="java"> public interface ReadFromHandler<T> { T readFrom(ReadFromHandlerContext<T> context) throws IOException; } public interface WriteToHandler<T> { void writeTo(WriteToHandlerContext<T> context) throws IOException; } </pre> An filter class is a class that implements RequestFilter or ResponseFilter (or both) and is annotated by @Provider. A handler class is a class that implements ReaderFromHandler or WriterToHandler (or both) and is annotated by @Provider. On the client side, filters implementing RequestFilter are executed before the HTTP invocation and before all handlers implementing Writerhandler. Still on the client side, filters implementing ResponseFilter are executed after the HTTP invocation and before all handlers implementing Readerhandler. Similar rules apply to the server side by simply replacing HTTP invocation by resource method invocation. See [[#pseudocode | Section Pre, Post, WriteTo and ReadFrom in Pseudocode]] for additional information. == Example 1: Logging Filter A logging filter that can be used both on the client and on the server can be defined as follows: <pre name="java"> @Provider public class LoggingFilter implements RequestFilter, ResponseFilter { @Override public FilterAction preFilter(FilterContext ctx) throws IOException { logRequest(ctx.getRequest()); return FilterAction.NEXT; } @Override public FilterAction postFilter(FilterContext ctx) throws IOException { logResponse(ctx.getResponse()); return FilterAction.NEXT; } ... } </pre> Filters are all linked in a ''chain''. Most filters will return FilterAction.NEXT to execute the next filter in the chain. However, this is not a requirement, a filter can set a value in its context and return FilterAction.STOP (e.g., a caching filter). == Example 2: GZIP handler The following class implements an handler than can be used to inflate and deflate entities. This use case requires a handler for both the ReadFrom and WriteTo extension points. <pre name="java"> @Provider public class GzipHandler implements ReadFromHandler, WriteToHandler { @Override public Object read(ReadFromHandlerContext ctx) throws IOException { if (!gzipEncoded(ctx)) { return ctx.proceed(); } else { InputStream old = ctx.getInputStream(); ctx.setInputStream(new GZIPInputStream(old)); try { return ctx.proceed(); } finally { ctx.setInputStream(old); } } } @Override public void write(WriteToHandlerContext ctx) throws IOException { if (!acceptsGzip(ctx)) { ctx.proceed(); } else { OutputStream old = ctx.getOutputStream(); GZIPOutputStream gzipOutputStream = new GZIPOutputStream(old); ctx.setOutputStream(gzipOutputStream); try { ctx.proceed(); } finally { gzipOutputStream.finish(); ctx.setOutputStream(old); } } } ... } </pre> == Binding Binding is the mechanism by which a handler or filter is associated with a resource class/method (server side) or an HTTP invocation (client side). Binding can be static, via annotations, or dynamic by using an additional interface. == Static Binding On the server side, an handler/filter can be associated with a resource class/method by defining a new annotation (a la CDI). For example, a new annotation @Logged can be defined and used to decorate a method in a resource class as follows: <pre name="java"> @Path("/") public class MyResourceClass { @Logged @GET @Produces("text/plain") @Path("{name}") public String hello(@PathParam("name") String name) { return "Hello " + name; } } </pre> The same @Logged annotation must be used to decorate the filter class for the binding to work. Thus, LoggingFilter must be defined as follows: <pre name="java"> @Logged @Provider public class LoggingFilter implements RequestFilter, ResponseFilter { ... } </pre> Finally, the @Logged annotation is defined using the meta-annotation @NameBinding which is also part of this proposal. <pre name="java"> @NameBinding @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(value = RetentionPolicy.RUNTIME) public @interface Logged { } </pre> == Global Static Binding It is often useful to define a handler/filter that is associated with all the resource classes in an application. This can be accomplished by using the @GlobalBinding annotation directly on the handler/filter class (no need to define a new annotation in this case). For example, <pre name="java"> @Provider @GlobalBinding public class ServerCachingFilter implements RequestFilter { @Override public FilterAction preFilter(FilterContext ctx) throws IOException { HttpResponse res = getCachedResponse(ctx); if (res != null) { ctx.setResponse(res); return FilterAction.STOP; // break filter chain } return FilterAction.NEXT; } ... } </pre> Note that in the example above, the response is set in the context and the filter returns FilterAction.STOP when there is a cache hit. This instructs the JAX-RS framework to stop executing any additional Pre filters and use the set response instead. == Domain and Priority A handler/filter domain can be set on the client or the server (or both). A handler/filter can also have a priority. The point in time when a handler/filter will be executed depends on its type: Pre, Post for filters and ReadFrom, WriteTo for handlers. The priority within each of these types is determined by the priority() element in @NameBinding and @GlobalBinding. The default priority is BindingPriority.USER. These are the relevant types and annotations and their default values. <pre name="java"> @Target(ElementType.ANNOTATION_TYPE) @Retention(value = RetentionPolicy.RUNTIME) public @interface NameBinding { int priority() default BindingPriority.USER; } @Target(ElementType.TYPE) @Retention(value = RetentionPolicy.RUNTIME) public @interface GlobalBinding { public enum BindingDomain { CLIENT, SERVER } BindingDomain[] domain() default { BindingDomain.CLIENT, BindingDomain.SERVER }; int priority() default BindingPriority.USER; } public class BindingPriority { public static final int SECURITY = 100; public static final int HEADER_DECORATOR = 200; public static final int DECODER = 300; public static final int ENCODER = 400; public static final int USER = 500; } </pre> The lower the numeric value of a priority class, the highest its priority. Implementations must sort all handlers/filters in ascending order of priority before applying them. New priority classes can be defined by using numeric values between those of the built-in priority classes shown above. == Dynamic Binding A handler/filter that implements the DynamicBinding interface supports dynamic binding. Dynamic binding is only supported on the server side. An handler/filter that supports dynamic binding will only be executed if its isBound() method returns true, regardless of whether it was defined via the meta-annotation @NameBinding or the annotation @GlobalBinding. <pre name="java"> public interface DynamicBinding { public boolean isBound(Class<?> type, Method method); } </pre> For example, the ServerCachingFilter class above could be defined as follows: <pre name="java"> @Provider @GlobalBinding public class ServerCachingFilter implements RequestFilter, DynamicBinding { @Override public boolean isBound(Class<?> type, Method method) { return method.isAnnotationPresent(GET.class); } ... } </pre> == Lifecycle By default, just like all other providers in JAX-RS, a single instance of each handler/filter is instantiated for each JAX-RS application. First the constructor is called, then any requested dependencies are injected, then the appropriate methods are called (simultaneously) as needed. Implementations may offer alternative lifecycle options. == Low-Level Client API Handlers and filters can be used on the client side by explicitly adding them to the ClientConfiguration used to create the Client instance. The following example shows how to add a logging filter and a GZIP handler to the set of provider classes in a DefaultClientConfiguration instance. <pre name="java"> ClientConfiguration config = new DefaultClientConfiguration(); config.getClasses().add(LoggingFilter.class); config.getClasses().add(GzipHandler.class); Client client = Client.create(config); HttpResponse res = client.request("http://...")....invoke(); </pre> Implementations may support other ways by which handlers/filters can be added to a ClientConfiguration. For example, an implementation may support a form of classpath scanning in which handlers/filters annotated by @Provider and @GlobalBinding, with a domain that includes BindingDomain.CLIENT, are automatically added. <span id="pseudocode"></span> == Pre, Post, WriteTo and ReadFrom in Pseudocode This section uses pseudocode to explain the location of the extension points. The method clientSideRequestProcessor() would be called by the client API to execute an HTTP request. Note the location of the Pre, Post and WriteTo extension points. The ReadFrom extension point is not shown here: that extension point would be executed in response to the application's code calling getEntity() on a response object. <pre name="java"> public HttpResponse clientSideRequestProcessor(HttpRequest req) { HttpResponse res = null; // Invoke Pre filters FilterContext fc = newFilterContext(req, res); for (RequestFilter f : ...) { FilterAction action = f.preFilter(fc); if (action == FilterAction.STOP) break; } res = fc.getResponse(); if (res == null) { // Execute Write handler chain WriteToHandlerContext whc = newWriteToHandlerContext(req); getFirstWriterToHandler(req).writeTo(whc); // Actual HTTP request invocation res = executeHttpRequest(req); } // Invoke Post filters fc = newFilterContext(req, res); for (ResponseFilter f : ...) { FilterAction action = f.preFilter(fc); if (action == FilterAction.STOP) break; } res = fc.getResponse(); return res; } </pre> The method serverSideRequestProcessor() would be called by the server API to process an HTTP request. Note the location of the Pre, Post, ReadFrom and WriteTo extension points. <pre name="java"> public HttpResponse serverSideRequestProcessor(HttpRequest req, Method m) { HttpResponse res = null; // Invoke Pre filters FilterContext fc = newFilterContext(req, res, m); for (RequestFilter f : ...) { FilterAction action = f.preFilter(fc); if (action == FilterAction.STOP) break; } res = fc.getResponse(); if (res == null) { // Execute Read handler chain ReadFromHandlerContext rfc = newReadFromHandlerContext(req, m); getFirstReadFromHandler(req).read(rfc); // Actual resource method invocation res = executeMethodInvocation(req, m); } // Invoke Post filters fc = newFilterContext(req, res); for (ResponseFilter f : ...) { FilterAction action = f.preFilter(fc); if (action == FilterAction.STOP) break; } res = fc.getResponse(); // Execute Write handler chain WriteToHandlerContext whc = newWriteToHandlerContext(req, res, m); getFirstWriteToHandler(m).write(whc); return res; } </pre> == Context Classes Reference <pre name="java"> public interface BaseContext { Map<String, Object> getProperties(); Method getResourceMethod(); Class<?> getResourceClass(); } </pre> <pre name="java"> public interface FilterContext extends BaseContext { public enum FilterAction { STOP, NEXT }; HttpRequest getRequest(); HttpResponse getResponse(); void setResponse(HttpResponse res); HttpResponse createReponse(); } </pre> <pre name="java"> public interface MessageBodyHandlerContext<T> extends BaseContext { Annotation[] getAnnotations(); void setAnnotations(Annotation[] annotations); Class<T> getType(); void setType(Class<T> type); Type getGenericType(); void setGenericType(Type genericType); MediaType getMediaType(); void setMediaType(MediaType mediaType); MultivaluedMap<String, String> getHeaders(); } </pre> <pre name="java"> public interface ReadFromHandlerContext<T> extends MessageBodyHandlerContext<T> { T proceed() throws IOException; InputStream getInputStream(); void setInputStream(InputStream is); } </pre> <pre name="java"> public interface WriteToHandlerContext<T> extends MessageBodyHandlerContext<T> { void proceed() throws IOException; T getEntity(); void setEntity(T entity); OutputStream getOutputStream(); public void setOutputStream(OutputStream os); } </pre>
 
 
Close
loading
Please Confirm
Close