| <?xml version="1.0"?> |
| <!-- |
| |
| Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. |
| |
| This program and the accompanying materials are made available under the |
| terms of the Eclipse Public License v. 2.0, which is available at |
| http://www.eclipse.org/legal/epl-2.0. |
| |
| This Source Code may also be made available under the following Secondary |
| Licenses when the conditions for such availability set forth in the |
| Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| version 2 with the GNU Classpath Exception, which is available at |
| https://www.gnu.org/software/classpath/license.html. |
| |
| SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| |
| --> |
| |
| <!DOCTYPE chapter [<!ENTITY % ents SYSTEM "jersey.ent" > %ents; ]> |
| <chapter xmlns="http://docbook.org/ns/docbook" |
| version="5.0" |
| xml:lang="en" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xmlns:xlink="http://www.w3.org/1999/xlink" |
| xsi:schemaLocation="http://docbook.org/ns/docbook http://docbook.org/xml/5.0/xsd/docbook.xsd" |
| xml:id="filters-and-interceptors"> |
| <title>Filters and Interceptors</title> |
| <section> |
| <title>Introduction</title> |
| <para>This chapter describes filters, interceptors and their configuration. Filters and interceptors |
| can be used on both sides, on the client and the server side. Filters can modify inbound and outbound requests |
| and responses including modification of headers, entity and other request/response parameters. Interceptors |
| are used primarily for modification of entity input and output streams. You can use interceptors for example |
| to zip and unzip output and input entity streams. |
| </para> |
| </section> |
| |
| |
| <section> |
| <title>Filters</title> |
| <para> |
| Filters can be used when you want to modify any request or response parameters like headers. For example |
| you would like to add a response header "X-Powered-By" to each generated response. Instead of adding this header |
| in each resource method you would use a response filter to add this header. |
| </para> |
| <para> |
| There are filters on the server side and the client side. |
| </para> |
| <para> |
| Server filters: |
| <simplelist> |
| <member>&jaxrs.container.ContainerRequestFilter; |
| </member> |
| <member>&jaxrs.container.ContainerResponseFilter; |
| </member> |
| </simplelist> |
| Client filters: |
| <simplelist> |
| <member>&jaxrs.client.ClientRequestFilter; |
| </member> |
| <member>&jaxrs.client.ClientResponseFilter; |
| </member> |
| </simplelist> |
| </para> |
| |
| <section> |
| <title>Server filters</title> |
| The following example shows a simple container response filter adding a header to each response. |
| |
| <para> |
| <example> |
| <title>Container response filter</title> |
| <programlisting language="java" linenumbering="numbered">import java.io.IOException; |
| import javax.ws.rs.container.ContainerRequestContext; |
| import javax.ws.rs.container.ContainerResponseContext; |
| import javax.ws.rs.container.ContainerResponseFilter; |
| import javax.ws.rs.core.Response; |
| |
| public class PoweredByResponseFilter implements ContainerResponseFilter { |
| |
| @Override |
| public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) |
| throws IOException { |
| |
| responseContext.getHeaders().add("X-Powered-By", "Jersey :-)"); |
| } |
| }</programlisting> |
| </example> |
| </para> |
| |
| |
| <para> |
| In the example above the <literal>PoweredByResponseFilter</literal> |
| always adds a header "X-Powered-By" to the |
| response. The filter must inherit from the &jaxrs.container.ContainerResponseFilter; and must be registered |
| as a provider. The filter will be executed for every response which is in most cases after the resource method |
| is executed. Response filters are executed even if the resource method is not run, for example when |
| the resource method is not found and 404 "Not found" response code is returned by the Jersey runtime. In this case |
| the filter will be executed and will process the 404 response. |
| </para> |
| <para> |
| The <literal>filter()</literal> method has two arguments, the container request and container response. The |
| &jaxrs.ContainerRequestContext; is accessible only for read only purposes as the filter is executed already |
| in response phase. The modifications can be done in the &jaxrs.ContainerResponseContext;. |
| </para> |
| |
| <para> |
| The following example shows the usage of a request filter. |
| </para> |
| <example> |
| <title>Container request filter</title> |
| <programlisting language="java" linenumbering="numbered">import java.io.IOException; |
| import javax.ws.rs.container.ContainerRequestContext; |
| import javax.ws.rs.container.ContainerRequestFilter; |
| import javax.ws.rs.core.Response; |
| import javax.ws.rs.core.SecurityContext; |
| |
| public class AuthorizationRequestFilter implements ContainerRequestFilter { |
| |
| @Override |
| public void filter(ContainerRequestContext requestContext) |
| throws IOException { |
| |
| final SecurityContext securityContext = |
| requestContext.getSecurityContext(); |
| if (securityContext == null || |
| !securityContext.isUserInRole("privileged")) { |
| |
| requestContext.abortWith(Response |
| .status(Response.Status.UNAUTHORIZED) |
| .entity("User cannot access the resource.") |
| .build()); |
| } |
| } |
| }</programlisting> |
| </example> |
| |
| |
| <para> |
| The request filter is similar to the response filter but does not have access to the ContainerResponseContext |
| as no response is accessible yet. Response filter inherits from &jaxrs.container.ContainerResponseFilter;. |
| Request filter is executed before the resource method is run and before the |
| response is created. The filter has possibility to manipulate the request parameters including request |
| headers or entity. |
| </para> |
| <para> |
| The <literal>AuthorizationRequestFilter</literal> in the example checks whether the |
| authenticated user is in the privileged role. If it is not then the request is <emphasis>aborted</emphasis> |
| by calling <literal>ContainerRequestContext.abortWith(Response response)</literal> method. The method |
| is intended to be called from the request filter in situation when the request should not be processed further in the standard processing chain. |
| When the <literal>filter</literal> method is finished the response passed as a parameter to the |
| <literal>abortWith</literal> method is used to respond to the request. Response filters, if any are registered, |
| will be executed and will have possibility to process the aborted response. |
| </para> |
| |
| <section> |
| <title>Pre-matching and post-matching filters</title> |
| </section> |
| <para> |
| All the request filters shown above was implemented as post-matching filters. It means that the filters |
| would be applied only after a suitable resource method has been selected to process the actual request |
| i.e. after request matching happens. Request matching is the process of finding a resource method that |
| should be executed based on the request path and other request parameters. Since post-matching request |
| filters are invoked when a particular resource method has already been selected, such filters can not |
| influence the resource method matching process. |
| </para> |
| <para> |
| To overcome the above described limitation, there is a possibility to mark |
| a server request filter as a <emphasis>pre-matching</emphasis> filter, |
| i.e. to annotate the filter class with the &jaxrs.Prematching; annotation. |
| Pre-matching filters are request filters that are executed before |
| the request matching is started. Thanks to this, pre-matching request filters have |
| the possibility to influence which method will be matched. Such a pre-matching request filter example is shown |
| here: |
| </para> |
| <para> |
| <example> |
| <title>Pre-matching request filter</title> |
| <programlisting language="java" linenumbering="numbered">... |
| import javax.ws.rs.container.ContainerRequestContext; |
| import javax.ws.rs.container.ContainerRequestFilter; |
| import javax.ws.rs.container.PreMatching; |
| ... |
| |
| @PreMatching |
| public class PreMatchingFilter implements ContainerRequestFilter { |
| |
| @Override |
| public void filter(ContainerRequestContext requestContext) |
| throws IOException { |
| // change all PUT methods to POST |
| if (requestContext.getMethod().equals("PUT")) { |
| requestContext.setMethod("POST"); |
| } |
| } |
| }</programlisting> |
| </example> |
| </para> |
| <para> |
| The <literal>PreMatchingFilter</literal> is a simple pre-matching filter which changes all PUT HTTP |
| methods to POST. This might be useful when you want to always handle these PUT and POST HTTP methods |
| with the same Java code. After the <literal>PreMatchingFilter</literal> has been invoked, the rest |
| of the request processing will behave as if the POST HTTP method was originally used. |
| You cannot do this in post-matching filters |
| (standard filters without &lit.jaxrs.Prematching; annotation) |
| as the resource method is already matched (selected). An attempt to tweak the original HTTP method in |
| a post-matching filter would cause an <literal>IllegalArgumentException</literal>. |
| </para> |
| <para> |
| As written above, pre-matching filters can fully influence the request matching process, which means |
| you can even modify request URI in a pre-matching filter by invoking |
| the <literal>setRequestUri(URI)</literal> method of &lit.jaxrs.container.ContainerRequestFilter; |
| so that a different resource would be matched. |
| </para> |
| <para> |
| Like in post-matching filters you can abort a response in pre-matching filters too. |
| </para> |
| </section> |
| <section> |
| <title>Client filters</title> |
| <para> |
| Client filters are similar to container filters. The response can also be aborted |
| in the &jaxrs.client.ClientRequestFilter; which would cause that no request will actually be sent to the server at all. |
| A new response is passed to the <literal>abort</literal> method. This response will be used and delivered |
| as a result of the request invocation. Such a response goes through the client response filters. |
| This is similar to what happens on the server side. The process is shown in the following example: |
| </para> |
| <para> |
| <example> |
| <title>Client request filter</title> |
| <programlisting language="java" linenumbering="numbered">public class CheckRequestFilter implements ClientRequestFilter { |
| |
| @Override |
| public void filter(ClientRequestContext requestContext) |
| throws IOException { |
| if (requestContext.getHeaders( |
| ).get("Client-Name") == null) { |
| requestContext.abortWith( |
| Response.status(Response.Status.BAD_REQUEST) |
| .entity("Client-Name header must be defined.") |
| .build()); |
| } |
| } |
| }</programlisting> |
| </example> |
| </para> |
| <para> |
| The <literal>CheckRequestFilter</literal> validates the outgoing request. It is checked for presence of |
| a <literal>Client-Name</literal> header. If the header is not present the request will be aborted |
| with a made up response with an appropriate code and message in the entity body. This will cause that |
| the original request will not be effectively sent to the server but the actual invocation |
| will still end up with a response as if it would be generated by the |
| server side. If there would be any client response filter it would be executed on this response. |
| </para> |
| <para> |
| To summarize the workflow, for any client request invoked from the client API |
| the client request filters (&jaxrs.client.ClientRequestFilter;) |
| are executed that could manipulate the request. |
| If not aborted, the outgoing request is then physically sent over to the server side |
| and once a response is received back from the server the client response |
| filters (&jaxrs.client.ClientResponseFilter;) |
| are executed that might again manipulate the returned response. |
| Finally the response is passed back to the code that invoked the request. |
| If the request was aborted in any client request filter then the |
| client/server communication is skipped and the aborted response |
| is used in the response filters. |
| </para> |
| |
| </section> |
| </section> |
| <section> |
| <title>Interceptors</title> |
| <para> |
| Interceptors share a common API for the server and the client side. Whereas filters are primarily intended to manipulate |
| request and response parameters like HTTP headers, URIs and/or HTTP methods, interceptors are intended to manipulate entities, via manipulating |
| entity input/output streams. If you for example need to encode entity body of a client request then you could |
| implement an interceptor to do the work for you. |
| </para> |
| <para> |
| There are two kinds of interceptors, &jaxrs.ReaderInterceptor; and &jaxrs.WriterInterceptor;. |
| Reader interceptors are used to manipulate inbound entity streams. These are the streams coming from |
| the "wire". So, using a reader interceptor you |
| can manipulate request entity stream on the server side (where |
| an entity is read from the client request) and response entity stream on the client side (where an entity |
| is read from the server response). Writer interceptors are used for cases where entity is written to the |
| "wire" which on the server means when writing out a response entity and on the client side when writing |
| request entity for a request to be sent out to the server. Writer and reader interceptors are executed before message body |
| readers or writers are executed and their primary intention is to wrap the entity streams that will be used in message body |
| reader and writers. |
| </para> |
| <para> |
| The following example shows a writer interceptor that enables GZIP compression of the whole entity body. |
| </para> |
| <para> |
| <example> |
| <title>GZIP writer interceptor</title> |
| <programlisting language="java" linenumbering="numbered">public class GZIPWriterInterceptor implements WriterInterceptor { |
| |
| @Override |
| public void aroundWriteTo(WriterInterceptorContext context) |
| throws IOException, WebApplicationException { |
| final OutputStream outputStream = context.getOutputStream(); |
| context.setOutputStream(new GZIPOutputStream(outputStream)); |
| context.proceed(); |
| } |
| }</programlisting> |
| </example> |
| </para> |
| <para> |
| The interceptor gets an output stream from the &jaxrs.WriterInterceptorContext; and sets |
| a new one which is a GZIP wrapper of the original output stream. After all interceptors are executed the |
| output stream lastly set to the &lit.jaxrs.WriterInterceptorContext; will be used for serialization of the entity. In the |
| example above the entity bytes will be written to the GZIPOutputStream which will compress the stream data |
| and write them to the original output stream. The original stream is always the stream which writes the data to |
| the "wire". When the interceptor is used on the server, the original output stream is the stream into which writes |
| data to the underlying server container stream that sends the response to the client. |
| </para> |
| <para> |
| The interceptors wrap the streams and they itself work as wrappers. This means that each interceptor is a wrapper |
| of another interceptor and it is responsibility of each interceptor implementation to call the wrapped interceptor. |
| This is achieved by calling the <literal>proceed()</literal> method on the &lit.jaxrs.WriterInterceptorContext;. |
| This method will call the next registered interceptor in the chain, so effectivelly this will call all remaining registered interceptors. Calling <literal>proceed()</literal> from the last |
| interceptor in the chain will call the appropriate message body reader. Therefore every interceptor must call the |
| <literal>proceed()</literal> method otherwise the entity would not be written. The wrapping principle is reflected |
| also in the method name, aroundWriteTo, which says that the method is wrapping the writing of the entity. |
| </para> |
| <para> |
| The method aroundWriteTo() gets &lit.jaxrs.WriterInterceptorContext; as a parameter. This context contains getters |
| and setters for header parameters, request properties, entity, entity stream and other properties. These are the |
| properties which will be passed to the final &lit.jaxrs.ext.MessageBodyWriter;. Interceptors are allowed to modify |
| all these properties. This could influence writing of an entity by &lit.jaxrs.ext.MessageBodyWriter; and even |
| selection of such a writer. By changing media type (&lit.jaxrs.WriterInterceptorContext;.setMediaType()) |
| the interceptor can cause that different message body writer will be chosen. The interceptor can also |
| completely replace the entity if it is needed. However, for modification of headers, request |
| properties and such, the filters are usually more preferable choice. Interceptors are executed |
| only when there is any entity and when the entity is to be written. So, when you always want to add a new |
| header to a response no matter what, use filters as interceptors might not be executed when no entity is |
| present. Interceptors should modify properties only for entity serialization |
| and deserialization purposes. |
| </para> |
| <para> |
| Let's now look at an example of a &lit.jaxrs.ReaderInterceptor; |
| </para> |
| <para> |
| <example> |
| <title>GZIP reader interceptor</title> |
| <programlisting language="java" linenumbering="numbered">public class GZIPReaderInterceptor implements ReaderInterceptor { |
| |
| @Override |
| public Object aroundReadFrom(ReaderInterceptorContext context) |
| throws IOException, WebApplicationException { |
| final InputStream originalInputStream = context.getInputStream(); |
| context.setInputStream(new GZIPInputStream(originalInputStream)); |
| return context.proceed(); |
| } |
| }</programlisting> |
| </example> |
| </para> |
| <para> |
| The <literal>GZIPReaderInterceptor</literal> wraps the original input stream with the |
| <literal>GZIPInputStream</literal>. All further reads from the entity stream will cause that data will be decompressed |
| by this stream. The interceptor method <literal>aroundReadFrom()</literal> must return an entity. The entity |
| is returned from the <literal>proceed</literal> method of the &jaxrs.ReaderInterceptorContext;. The |
| <literal>proceed</literal> method internally calls the wrapped interceptor which must also return an entity. |
| The <literal>proceed</literal> method invoked from the last interceptor in the chain calls message body reader which deserializes |
| the entity end returns it. Every interceptor can change this entity if there is a need but in the most cases |
| interceptors will just return the entity as returned from the <literal>proceed</literal> method. |
| </para> |
| <para> |
| As already mentioned above, interceptors should be primarily used to manipulate entity body. |
| Similar to methods exposed by &lit.jaxrs.WriterInterceptorContext; the &lit.jaxrs.ReaderInterceptorContext; |
| introduces a set of methods for modification of request/response properties like HTTP headers, |
| URIs and/or HTTP methods (excluding getters and setters for entity as entity has not been read yet). |
| Again the same rules as for &lit.jaxrs.WriterInterceptor; applies for changing these properties (change only |
| properties in order to influence reading of an entity). |
| </para> |
| |
| </section> |
| <section> |
| <title>Filter and interceptor execution order</title> |
| <para> |
| Let's look closer at the context of execution of filters and interceptors. The following steps describes scenario |
| where a JAX-RS client makes a POST request to the server. The server receives an entity and sends a response back |
| with the same entity. GZIP reader and writer interceptors are registered on the |
| client and the server. Also filters are registered on client and server which change the headers of request |
| and response. |
| <orderedlist> |
| <listitem>Client request invoked: The POST request with attached entity is built on the client and invoked.</listitem> |
| <listitem>ClientRequestFilters: client request filters are executed on the client and they |
| manipulate the request headers.</listitem> |
| <listitem>Client &lit.jaxrs.WriterInterceptor;: As the request contains an entity, writer interceptor registered on the client is executed before |
| a MessageBodyWriter is executed. It wraps the entity output stream with the GZipOutputStream.</listitem> |
| <listitem>Client MessageBodyWriter: message body writer is executed on the client which serializes the entity |
| into the new GZipOutput stream. This stream zips the data and sends it to the "wire".</listitem> |
| <listitem>Server: server receives a request. Data of the entity is compressed which means that pure read from |
| the entity input stream would return compressed data.</listitem> |
| <listitem>Server pre-matching ContainerRequestFilters: ContainerRequestFilters are executed that can manipulate |
| resource method matching process.</listitem> |
| <listitem>Server: matching: resource method matching is done.</listitem> |
| <listitem>Server: post-matching ContainerRequestFilters: ContainerRequestFilters post matching filters are executed. |
| This include execution of all global filters (without name binding) and filters name-bound to the matched |
| method.</listitem> |
| <listitem>Server &lit.jaxrs.ReaderInterceptor;: reader interceptors are executed on the server. The GZIPReaderInterceptor |
| wraps the input stream (the stream from the "wire") into the GZipInputStream and set it to context.</listitem> |
| <listitem>Server MessageBodyReader: server message body reader is executed and it deserializes the entity |
| from new GZipInputStream (get from the context). This means the reader will read unzipped data and not |
| the compressed data from the "wire".</listitem> |
| <listitem>Server resource method is executed: the deserialized entity object is passed to the matched resource |
| method as a parameter. The method returns this entity as a response entity.</listitem> |
| <listitem>Server ContainerResponseFilters are executed: response filters are executed on the server and |
| they manipulate the response headers. This include all global bound filters (without name binding) and all filters name-bound to the resource method.</listitem> |
| <listitem>Server &lit.jaxrs.WriterInterceptor;: is executed on the server. It wraps the original |
| output stream with a new GZIPOuptutStream. The original stream is the stream that "goes to the wire" (output |
| stream for response from the underlying server container). |
| </listitem> |
| <listitem>Server MessageBodyWriter: message body writer is executed on the server which serializes the entity |
| into the GZIPOutputStream. This stream compresses the data and writes it to the original stream which sends |
| this compressed data back to the client. |
| </listitem> |
| <listitem>Client receives the response: the response contains compressed entity data.</listitem> |
| <listitem>Client ClientResponseFilters: client response filters are executed and they manipulate the response headers.</listitem> |
| <listitem>Client response is returned: the javax.ws.rs.core.Response is returned from the request invocation.</listitem> |
| <listitem>Client code calls response.readEntity(): read entity is executed on the client to extract the entity from the response.</listitem> |
| <listitem>Client &lit.jaxrs.ReaderInterceptor;: the client reader interceptor is executed when readEntity(Class) is called. The interceptor |
| wraps the entity input stream with GZIPInputStream. This will decompress the data from the original input stream. |
| </listitem> |
| <listitem>Client MessageBodyReaders: client message body reader is invoked which reads decompressed data from |
| GZIPInputStream and deserializes the entity.</listitem> |
| <listitem>Client: The entity is returned from the readEntity().</listitem> |
| </orderedlist> |
| It is worth to mention that in the scenario above the reader and writer interceptors are invoked only if the |
| entity is present (it does not make sense to wrap entity stream when no entity will be written). The same behaviour |
| is there for message body readers and writers. As mentioned above, interceptors are executed before |
| the message body reader/writer as a part of their execution and they can wrap the input/output stream |
| before the entity is read/written. There are exceptions when interceptors are not run before message body |
| reader/writers but this is not the case of simple scenario above. This happens for example when the entity is |
| read many times from client response using internal buffering. Then the data are intercepted only once and kept |
| 'decoded' in the buffer. |
| </para> |
| |
| </section> |
| |
| <section> |
| <title>Name binding</title> |
| |
| <para> |
| Filters and interceptors can be <emphasis>name-bound</emphasis>. Name binding is a concept that allows to say to a JAX-RS |
| runtime that a specific filter or interceptor will be executed only for a specific resource method. When a filter or |
| an interceptor is limited only to a specific resource method we say that it is <emphasis>name-bound</emphasis>. |
| Filters and interceptors that do not have such a limitation are called <emphasis>global</emphasis>. |
| </para> |
| <para> |
| Filter or interceptor can be assigned to a resource method using the &jaxrs.NameBinding; annotation. The annotation |
| is used as meta annotation for other user implemented annotations that are applied to a providers and resource |
| methods. See the following example: |
| </para> |
| <para> |
| <example> |
| <title>&lit.jaxrs.NameBinding; example</title> |
| <programlisting language="java" linenumbering="numbered">... |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.zip.GZIPInputStream; |
| |
| import javax.ws.rs.GET; |
| import javax.ws.rs.NameBinding; |
| import javax.ws.rs.Path; |
| import javax.ws.rs.Produces; |
| ... |
| |
| |
| // @Compress annotation is the name binding annotation |
| @NameBinding |
| @Retention(RetentionPolicy.RUNTIME) |
| public @interface Compress {} |
| |
| |
| @Path("helloworld") |
| public class HelloWorldResource { |
| |
| @GET |
| @Produces("text/plain") |
| public String getHello() { |
| return "Hello World!"; |
| } |
| |
| @GET |
| @Path("too-much-data") |
| @Compress |
| public String getVeryLongString() { |
| String str = ... // very long string |
| return str; |
| } |
| } |
| |
| // interceptor will be executed only when resource methods |
| // annotated with @Compress annotation will be executed |
| @Compress |
| public class GZIPWriterInterceptor implements WriterInterceptor { |
| @Override |
| public void aroundWriteTo(WriterInterceptorContext context) |
| throws IOException, WebApplicationException { |
| final OutputStream outputStream = context.getOutputStream(); |
| context.setOutputStream(new GZIPOutputStream(outputStream)); |
| context.proceed(); |
| } |
| }</programlisting> |
| </example> |
| </para> |
| <para> |
| The example above defines a new <literal>@Compress</literal> annotation which is a name binding annotation as |
| it is annotated with &lit.jaxrs.NameBinding;. The <literal>@Compress</literal> is applied on the |
| resource method <literal>getVeryLongString()</literal> and on the interceptor |
| <literal>GZIPWriterInterceptor</literal>. The interceptor will be executed only if any resource method |
| with such an annotation will be executed. In our example case the interceptor will be executed only for |
| the <literal>getVeryLongString()</literal> method. The interceptor will not be executed for method |
| <literal>getHello()</literal>. In this example the reason is probably clear. We would like to compress |
| only long data and we do not need to compress the short response of "Hello World!". |
| </para> |
| <para> |
| Name binding can be applied on a resource class. In the example <literal>HelloWorldResource</literal> |
| would be annotated with <literal>@Compress</literal>. This would mean that all resource |
| methods will use compression in this case. |
| </para> |
| <para> |
| There might be many name binding annotations defined in an application. When any provider (filter |
| or interceptor) is annotated with more than one name binding annotation, then it will be executed for |
| resource methods which contain ALL these annotations. So, for example if our interceptor would be |
| annotated with another name binding annotation @GZIP then the resource method would need to have both annotations attached, |
| @Compress and @GZIP, otherwise the interceptor would not be executed. Based on the previous paragraph we can |
| even use the combination when the |
| resource method <literal>getVeryLongString()</literal> would be annotated with @Compress and resource class |
| <literal>HelloWorldResource</literal> would be annotated from with @GZIP. This would also trigger the interceptor as |
| annotations of resource methods are aggregated from resource method and from resource class. But this is probably |
| just an edge case which will not be used so often. |
| </para> |
| <para> |
| Note that <emphasis>global filters are always executed</emphasis>, even for resource methods |
| which have any name binding annotations. |
| </para> |
| </section> |
| <section> |
| <title>Dynamic binding</title> |
| <para>Dynamic binding is a way how to assign filters and interceptors to the resource methods in a dynamic |
| manner. Name binding from the previous chapter uses a static approach and changes to binding require source |
| code change and recompilation. With dynamic binding you can implement code which defines bindings during the application |
| initialization time. The following example shows how to implement dynamic binding.</para> |
| |
| |
| |
| <para> |
| <example> |
| <title>Dynamic binding example</title> |
| <programlisting language="java" linenumbering="numbered">... |
| import javax.ws.rs.core.FeatureContext; |
| import javax.ws.rs.container.DynamicFeature; |
| ... |
| |
| @Path("helloworld") |
| public class HelloWorldResource { |
| |
| @GET |
| @Produces("text/plain") |
| public String getHello() { |
| return "Hello World!"; |
| } |
| |
| @GET |
| @Path("too-much-data") |
| public String getVeryLongString() { |
| String str = ... // very long string |
| return str; |
| } |
| } |
| |
| // This dynamic binding provider registers GZIPWriterInterceptor |
| // only for HelloWorldResource and methods that contain |
| // "VeryLongString" in their name. It will be executed during |
| // application initialization phase. |
| public class CompressionDynamicBinding implements DynamicFeature { |
| |
| @Override |
| public void configure(ResourceInfo resourceInfo, FeatureContext context) { |
| if (HelloWorldResource.class.equals(resourceInfo.getResourceClass()) |
| && resourceInfo.getResourceMethod() |
| .getName().contains("VeryLongString")) { |
| context.register(GZIPWriterInterceptor.class); |
| } |
| } |
| }</programlisting> |
| </example> |
| </para> |
| |
| <para> |
| The example contains one <literal>HelloWorldResource</literal> which is known from the previous name binding example. |
| The difference is in the <literal>getVeryLongString</literal> method, which now does not define |
| the <literal>@Compress</literal> name binding annotations. The binding is done |
| using the provider which implements &jaxrs.container.DynamicFeature; interface. The interface defines |
| one <literal>configure</literal> |
| method with two arguments, <literal>ResourceInfo</literal> and <literal>FeatureContext</literal>. |
| <literal>ResourceInfo</literal> contains information about the resource and method to which the binding can be done. |
| The <literal>configure</literal> method will be executed once for each resource method that is defined in the application. |
| In the example above the provider will be executed twice, once for the <literal>getHello()</literal> method |
| and once for <literal>getVeryLongString()</literal> ( |
| once the resourceInfo will contain information about getHello() method and once it will point to |
| getVeryLongString()). If a dynamic binding provider wants to register any provider for the actual resource method |
| it will do that using provided <literal>FeatureContext</literal> which extends |
| JAX-RS <literal>Configurable</literal> API. All methods for registration of filter or interceptor classes or instances can be used. |
| Such dynamically registered filters or interceptors will be bound only to the actual resource method. In the example above the |
| <literal>GZIPWriterInterceptor</literal> will be bound only to the method <literal>getVeryLongString()</literal> |
| which will cause that data will be compressed only for this method and not for the method |
| <literal>getHello()</literal>. The code of <literal>GZIPWriterInterceptor</literal> is in the examples above. |
| </para> |
| <para> |
| Note that filters and interceptors registered using dynamic binding are only additional filters run for the |
| resource method. If there are any name bound providers or global providers they will still be executed. |
| </para> |
| </section> |
| |
| <section> |
| <title>Priorities</title> |
| |
| <para>In case you register more filters and interceptors you might want to define an exact order in which |
| they should be invoked. The order can be controlled by the <literal>@Priority</literal> annotation defined |
| by the <literal>javax.annotation.Priority</literal> class. The annotation accepts an integer parameter of priority. |
| Providers used in request processing (&lit.jaxrs.container.ContainerRequestFilter;, |
| &lit.jaxrs.client.ClientRequestFilter;) as well as entity interceptors (&lit.jaxrs.ReaderInterceptor;, |
| &lit.jaxrs.WriterInterceptor;) are sorted based on the priority in an ascending manner. So, a request filter with |
| priority defined with <literal>@Priority(1000)</literal> |
| will be executed before another request filter with priority defined as <literal>@Priority(2000)</literal>. Providers |
| used during response processing (&lit.jaxrs.container.ContainerResponseFilter;, |
| &lit.jaxrs.client.ClientResponseFilter;) are executed |
| in the reverse order (using descending manner), so a provider with the priority defined with |
| <literal>@Priority(2000)</literal> will be executed before another provider with |
| priority defined with <literal>@Priority(1000)</literal>. |
| </para> |
| <para> |
| It's a good practice to assign a priority to filters and interceptors. Use &jaxrs.Priorities; class which |
| defines standardized priorities in JAX-RS for different usages, rather than inventing your |
| own priorities. For example, when you write an authentication filter you would assign a priority 1000 which |
| is the value of &lit.jaxrs.Priorities;<literal>.AUTHENTICATION</literal>. The following example |
| shows the filter from the beginning |
| of this chapter with a priority assigned. |
| </para> |
| |
| <para> |
| <example> |
| <title>Priorities example</title> |
| <programlisting language="java" linenumbering="numbered">... |
| import javax.annotation.Priority; |
| import javax.ws.rs.Priorities; |
| ... |
| |
| @Priority(Priorities.HEADER_DECORATOR) |
| public class ResponseFilter implements ContainerResponseFilter { |
| |
| @Override |
| public void filter(ContainerRequestContext requestContext, |
| ContainerResponseContext responseContext) |
| throws IOException { |
| |
| responseContext.getHeaders().add("X-Powered-By", "Jersey :-)"); |
| } |
| }</programlisting> |
| </example> |
| </para> |
| <para> |
| As this is a response filter and response filters are executed in the reverse order, |
| any other filter with priority lower than 3000 (<literal>Priorities.HEADER_DECORATOR</literal> is 3000) will be executed after |
| this filter. So, for example <literal>AUTHENTICATION</literal> filter (priority 1000) would be run after this filter. |
| </para> |
| </section> |
| </chapter> |