Sometimes it would be nice to have a possibility to wrap a resource method call. Imagine a use-case in which you need to process business code, placed in your resource method, in a transaction (open transaction before resource method, commit or rollback transaction at the end) or apply some advanced security constraints. If you’re using JAX-RS together with CDI in your application it’s not a problem to do such things. Fortunately, with Jersey it’s also possible to do those things outside of CDI.
In Jersey 1 there are dedicated SPI classes to achieve this task, ResourceMethodDispatchProvider and RequestDispatcher. Once you register your dispatch provider by placing an identifier in the resource directory (META-INF/services
) you can start creating request dispatchers that wraps and affects the invocation of resource methods.
Jersey 2 doesn’t expose similar SPI classes but users are be able to leverage means provided by HK2, an injection framework Jersey uses internally. HK2 supports some AOP capabilities which means that Jersey 2 applications can take advantage of these capabilities as well.
In general, constructors and methods of each instance managed by HK2 can be intercepted but, in this article, we’re going to focus on intercepting just JAX-RS Resources and Providers.
To demonstrate the feature I’ve created a simple example, jersey-intercepting-resource-methods, excerpts of which I’d be showing in the following sections.
Setup
Interception entry point in HK2, that identifies instances containing constructors and methods to be intercepted, is called InterceptionService.
In the simplest case you just need to, in your implementation, provide a filter (getDescriptorFilter() method, see line 5 in the example below) to filter out managed objects on the class level and then you can assign each given constructor or method a list of interceptors to be invoked before the actual constructor or method is invoked.
1@Override
2public Filter getDescriptorFilter() {
3 // We're only interested in classes (resources, providers) from this
4 // applications packages.
5 return new Filter() {
6 @Override
7 public boolean matches(final Descriptor d) {
8 final String clazz = d.getImplementation();
9 return clazz.startsWith("sk.dejavu.blog.examples.intercepting.resources")
10 || clazz.startsWith("sk.dejavu.blog.examples.intercepting.providers");
11 }
12 };
13}
14
15@Override
16public List<MethodInterceptor> getMethodInterceptors(final Method method) {
17 // Apply interceptors only to methods annotated with @Intercept.
18 if (method.isAnnotationPresent(Intercept.class)) {
19 ...
20 }
21 return null;
22}
23
24@Override
25public List<ConstructorInterceptor> getConstructorInterceptors(
26 final Constructor<?> constructor) {
27
28 // Apply interceptors only to constructors annotated with @Intercept.
29 if (constructor.isAnnotationPresent(Intercept.class)) {
30 ...
31 }
32 return null;
33}
A working implementation of the service is available in MyInterceptionService class.
Now, when we have our custom implementation of the interception service we need to let HK2 know about it. There are two ways how to do the job. The first approach is to extend AbstractBinder and register it’s instance in Jersey ResourceConfig (or sub-class of JAX-RS Application).
public class MyInterceptionBinder extends AbstractBinder {
@Override
protected void configure() {
bind(MyInterceptionService.class)
.to(org.glassfish.hk2.api.InterceptionService.class)
.in(Singleton.class);
}
}
MyInterceptionService has to be bound to HK2’s InterceptionService contract in the Singleton scope.
@ApplicationPath("/")
public class Application extends ResourceConfig {
public Application() {
packages("sk.dejavu.blog.examples.intercepting");
// Register Interception Service.
register(new MyInterceptionBinder());
}
}
The second way is to annotate our custom interception service with @Service annotation and use hk2-inhabitant-generator to look for all HK2 services in the application. References to HK2 services are saved during build into a file bundled with the application. The locator file is later used to automatically register services during runtime.
To try the latter approach in the demo application just uncomment the mentioned plugin in pom.xml and disable the registration of MyInterceptionBinder in the Application class.
Resources
Let’s take a look at intercepting JAX-RS Resources first. Assume that we have a simple resource with two resource methods that return plain String.
@Path("server")
public class ServerResource {
/**
* Resource method is not intercepted itself.
*/
@GET
public String get() {
return "ServerResource: Non-intercepted method invoked\n";
}
/**
* Resource method is intercepted by {@code ResourceInterceptor}.
*/
@GET
@Path("intercepted")
@Intercept
public String getIntercepted() {
return "ServerResource: Intercepted method invoked\n";
}
}
The first method, get(), is not intercepted so when we invoke a request that is handled by this method, either via test (see InterceptingTest) or via curl
, we’d get:
ServerResource: Non-intercepted method invoked
As expected we received the message defined in the resource method on the client side and nothing more.
The second method, getIntercepted(), is intercepted by a custom resource method interceptor, ResourceInterceptor, which appends an additional line at the end of the message returned from the method. The @Intercept annotation placed above this method tells our interception/filtering service that this method should be considered for intercepting.
public class ResourceInterceptor implements MethodInterceptor {
@Override
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
// Modify the entity returned by the invoked resource method
// by appending additional message.
return methodInvocation.proceed()
+ " ResourceInterceptor: Method \""
+ methodInvocation.getMethod().getName()
+ "\" intercepted\n";
}
}
When we fetch this resource the resulting entity would look like:
ServerResource: Intercepted method invoked
ResourceInterceptor: Method "getIntercepted" intercepted
In the output we can clearly see that the method was intercepted and the additional line was added by our method interceptor.
Providers
I wrote that all types/objects managed by HK2 can be intercepted. This also includes JAX-RS Providers that are registered in your application. For instance, lets create a message body provider (MessageBodyReader) that would be reading input stream and transforming it into String. (Yes, there is such a provider available in every JAX-RS implementation but with our implementation we’re going to suppress the default one and slightly modify its behavior.)
public class StringProvider extends AbstractMessageReaderWriterProvider<String> {
@Context
private Configuration config;
private String providerName;
@Intercept
public StringProvider() {
}
public void setProviderName(final String providerName) {
this.providerName = providerName;
}
@Override
@Intercept
public boolean isReadable(final Class<?> type,
final Type genericType,
final Annotation[] annotations,
final MediaType mediaType) {
return false;
}
@Override
public String readFrom(final Class<String> type,
final Type genericType,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap<String, String> headers,
final InputStream entityStream) throws IOException, WebApplicationException {
return readFromAsString(entityStream, mediaType)
+ " " + providerName + "(" + getRuntime() + "): Entity reading intercepted\n";
}
private String getRuntime() {
return config.getRuntimeType() == RuntimeType.SERVER
? "Server" : "Client";
}
}
As you can see we’re intercepting the processing here in two places – constructor and isReadable(…) method (notice the presence of the @Intercept annotation). The reason the constructor is intercepted is because we’d like to set the providerName
field, after an instance is created, directly in the interceptor.
Then we have the isReadable(…) method with default implementation that always returns false. This prevents our custom StringProvider to do something useful in readFrom(…) method as it is never called if isReadable(…) returns false. However, the isReadable(…) method is intercepted and we can alter its behavior in our interceptor and that’s exactly what we do.
The whole implementation of ProviderInterceptor looks like the following.
public class ProviderInterceptor implements MethodInterceptor, ConstructorInterceptor {
@Override
public Object construct(final ConstructorInvocation invocation) throws Throwable {
final Object proceed = invocation.proceed();
// After an instance of StringProvider is ready,
// set a name to that instance.
if (proceed instanceof StringProvider) {
((StringProvider) proceed)
.setProviderName(StringProvider.class.getSimpleName());
}
return proceed;
}
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// This method intercepts either StringProvider.isReadable
// or StringProvider.isWriteable methods.
// First argument of intercepted methods is class
// of instance to be written or produced.
final Object type = invocation.getArguments()[0];
// Do not invoke the provider's method itself. Return true
// if a String is about to be written or produced, false otherwise.
return type == String.class;
}
}
Client
Since we can intercept HK2 managed JAX-RS Providers we can affect behavior of the client to the some degree as well. If the custom StringProvider and MyInterceptionBinder are registered in the client the constructor and isReadable(…) method of the provider would be intercepted as well.
@Path("client")
public class ClientResource {
@ClientConfig
@Uri("server")
private WebTarget server;
/**
* Resource method is not intercepted itself.
*
* In the injected client the {@code StringProvider} is registered
* and the provider is intercepted (constructor, isReadable).
*/
@GET
public String get() {
return "ClientResource: Invoke request to non-intercepted"
+ " server resource method\n"
+ " " + server.request().get(String.class);
}
}
Lets create a client resource on the server as illustrated above. At first, a configured instance of JAX-RS WebTarget is injected into our resource and then this instance is used to retrieve a message from (non-intercepted) server resource method. Entity returned from server is used to assemble a new response returned from this ClientResource‘s resource method.
Injecting configured client (web target) is a Jersey custom feature. You can find more in Managed JAX-RS Client article.
On the other side of the wire the response would look like:
ClientResource: Invoke request to non-intercepted server resource method
ServerResource: Non-intercepted method invoked
StringProvider(Client): Entity reading intercepted
The StringProvider was used on the client to transform the response coming from ServerResource and the last line was added to the result. A similar output (without the first line) is expected even in case when we use JAX-RS Client API outside of an application deployed on server – see InterceptingTest.clientFetchingServerResource test.
Transactions
Intercepting JAX-RS resources can be useful in situations when you need to wrap business code present in a resource method into a JTA transaction. Rather than starting and committing (rollbacking) a transaction directly in your resource method you can abstract the code into a method interceptor and annotate the resource method with an appropriate annotation (e.g. @Transactional) to denote that the transactional processing should be started.
@Path("server")
public class ServerResource {
/**
* Resource method wrapped in a transaction.
*/
@POST
@Transactional
public String save(final Entity entity) {
// Do something with DB.
// ...
return ...;
}
}
In the method interceptor you need an instance of a UserTransaction (e.g. provided by application server) obtained from e.g. JNDI via InitialContext. The method interceptor might in this case look like:
public class TransactionalResourceInterceptor implements MethodInterceptor {
@Override
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
final UserTransaction utx = ...;
// Begin the transaction.
utx.begin();
try {
// Invoke JAX-RS resource method.
final Object result = methodInvocation.proceed();
// Commit the transaction.
utx.commit();
return result;
} catch (final RuntimeException re) {
// Something bad happened, rollback;
utx.rollback();
// Rethrow the Exception.
throw re;
}
}
}
Now, for every method annotated with @Transactional a separate transaction would be started before the processing actually enters the resource method and committed (rollbacked) after we return from that method.
Further Reading
- HK2 – Aspect Oriented Programming (AOP) Example
- HK2 – Default Interception Service Implementation
- Managed JAX-RS Client
Resources
- Example – jersey-intercepting-resource-methods
Thanks
To Jakub for reading draft of this.