Donnerstag, 17. Oktober 2013

The power of the ContextResolver Part I

How to use a ContextResolver? What can you do with this?
Let´s say you have something like the following:
@Inject @DemoLogicContext DemoLogic demologic;
But it depends on the transient context of the application to decide what is the right implementation of the interface DemoLogic.

The implementation DemoLogicB is a normal JavaClass, KotlinDemoLogic is writen in Kotlin. Both implementations are paired with a qualifier and a producer like the following in the picture.

At this point you have n implementations with n qualiefiers and maybe n producer. Or it must be possible to extend the running system with a few more different customer dependent implementations. To write this inside your code with an big if else tree it is not a good style. From the busineslogic point of view the right definition is:
@Inject @DemoLogicContext DemoLogic demologic;

For this you can use the producer methods from cdi. The way is quite easy:
@Produces @DemoLogicContext
public DemoLogic create(){...}
With this you have the entry-point for the final injection point. You can extend the signature of the create method with more attributes. Mostly used is the BeanManager and/or InjectioPoint, but you can use your own classes. For example the ContextResolver.
@Produces @DemoLogicContext
public DemoLogic create(ContextResolver contextResolver){...}
The ContextResolver itself is an interface.. this means you have to use an qualifier to define the producer or implementing class for this interface.
@Produces @DemoLogicContext
public DemoLogic create(@DemoLogicContext ContextResolver contextResolver){...}

Now we have to implement the first class, called DemoContextResolver. The DemoContextResolver will be injected into every producer that must be able to decide something.
public class DemoLogicProducer {
    private @Inject ManagedInstanceCreator creator;

    @Produces @DemoLogicContext
    public DemoLogic create(@New DemoContextResolver contextResolver){
        final Class beanType = DemoLogic.class;
        final AnnotationLiteral annotationLiteral = contextResolver.resolveContext(beanType);
        final DemoLogic demoLogic = creator.getManagedInstance(beanType, annotationLiteral);
        return demoLogic;
    }
}
From this point you are able to decide what will be the implementation to use. For this example I wrote a very simple version, but you can extend this to a tree of deciding ContextResolvers.
public class DemoContextResolver implements ContextResolver {

    @Inject DemoContext demoContext;

    @Override public AnnotationLiteral resolveContext(Class targetClass) {
        if(demoContext.getContextInfo()){
            return new AnnotationLiteral() {};
        } else{
            return new AnnotationLiteral() {};
        }
    }
}
The good thing is, that every module will be usable for itself. You can write jUnit-Tests per implementation. At runtime you can add more implementations. Think about the possibility to change the controller for a GUI in this way ;-) I will show this more in detail in my next blog entry (Part II)