Wednesday, August 23, 2017

Dynamically adding an interceptor to a build-in CDI bean

In Java EE's CDI, beans can be augmented via 2 artefacts; Decorators and Interceptors.

Decorators are typically owned by the application code and can decorate a bean that's shipped by the container (build-in beans) or a library.

Interceptors are typically shipped by a library and can be applied (bound) to a bean that's owned by the application.

So how do you bind a library shipped interceptor to a library shipped/build-in bean? In CDI 1.2 and before this wasn't really possible, but in CDI 2.0 we can take advantage of the new InterceptionFactory to do just this. It's not entirely trivial yet, but it's doable. In this article we'll demonstrate how to apply the @RememberMe interceptor binding from the new Java EE 8 Security spec to a build-in bean of type HttpAuthenticationMechanism, which is from the Security spec as well.

First we configure our authentication mechanism by means of the following annotation:

@BasicAuthenticationMechanismDefinition(
    realmName="foo"
)

 

This will cause the container to enable a build-in bean with interface type HttpAuthenticationMechanism, but having an unknown (vendor specific) implementation.

Next we'll definite an alternative for this bean via a CDI producer:

@Alternative
@Priority(500)
@ApplicationScoped
public class ApplicationInit {
    
    @Produces
    public HttpAuthenticationMechanism produce(InterceptionFactory<HttpAuthenticationMechanismWrapper> interceptionFactory, BeanManager beanManager) {
        return ...
    }
Note that perhaps somewhat counter intuitively the @Alternative annotation is put on the bean hosting the producer method, not on the producer method itself.

A small challenge here is to obtain the bean with type HttpAuthenticationMechanism that would have been chosen by the CDI runtime had our producer not been there. For a decorator this is easy as CDI makes that exact bean injectable via the @Decorated qualifier. Here we'll have to do this manually. One way is to get all the beans of type HttpAuthenticationMechanism from the bean manager (this will include both alternatives and non-alternatives), filter ourselves from that set and then let the bean manager resolve the set to the one that would be chosen for injection. We then create a reference for that chosen bean.

The following shows this in code:

HttpAuthenticationMechanism mechanism =
    createRef(
        beanManager.resolve(
            beanManager.getBeans(HttpAuthenticationMechanism.class)
                       .stream()
                       .filter(e -> !e.getBeanClass().equals(ApplicationInit.class))
                       .collect(toSet())), 
            beanManager);

 

With createRef being defined as:

HttpAuthenticationMechanism createRef(Bean<?> bean, BeanManager beanManager) {
    return (HttpAuthenticationMechanism) 
        beanManager.getReference(
            bean, 
            HttpAuthenticationMechanism.class, 
            beanManager.createCreationalContext(bean));
}

 

We now have an instance to the bean to which we like to apply the interceptor binding. Unfortunately, there's a somewhat peculiar and very nasty note in the CDI spec regarding the method that creates a proxy with the required interceptor attached:

If the provided instance is an internal container construct (such as client proxy), non-portable behavior results.

Since the HttpAuthenticationMechanism is a client proxy (it's application scoped by spec definition) we have no choice but to introduce some extra ceremony here and that's by providing a wrapper ourselves. The interceptor will be applied to the wrapper then, and the wrapper will delegate to the actual HttpAuthenticationMechanism instance:

 

HttpAuthenticationMechanismWrapper wrapper = 
    new HttpAuthenticationMechanismWrapper(mechanism);

 

Having our HttpAuthenticationMechanism instance ready, we can now dynamically configure an annotation instance. Such instance can be created via CDI's provided AnnotationLiteral helper type:

interceptionFactory.configure().add(
    new RememberMeAnnotationLiteral(
        86400, "",       // cookieMaxAgeSeconds
        false, "",       // cookieSecureOnly
        true, "",        // cookieHttpOnly
        "JREMEMBERMEID", // cookieName
        true, ""         // isRememberMe
    )
);

 

Finally, we create the above mentioned new proxy with the configured interceptor binding applied to it using the interception factory's createInterceptedInstance method and return this from our producer method:

return interceptionFactory.createInterceptedInstance(
    new HttpAuthenticationMechanismWrapper(wrapper));

 

A full example can be found in the Java EE 8 samples project.

Note that there's a small caveat here; if the Interceptor needs access to the interceptor bindings (which is almost always the case when the binding has attributes), you can't just inspect the target type as one would usually do in CDI 1.2 and earlier code. The interceptor binding annotation is not physically present on the type. At the moment it's not entirely clear how to obtain these in a portable way. The interceptors in the Java EE Security RI (Soteria) uses an RI specific way for now.

The example was tested on Payara Server 5, of which a snapshot can be downloaded from the snapshot repository. An initial alpha will be released very soon, but in the mean time the latest version can be downloaded here:
payara-5.0.0.173-SNAPSHOT.zip.

Arjan Tijms

No comments:

Post a Comment