← back to Rants
RSS feed

choosing CDI @Alternatives at runtime

One of the coolest features of JSR-299 are @Alternatives, letting you choose concrete implementations by simply specifying them in your beans.xml, either each one seperately or grouped in @Alternative @Stereotypes. Since we have to choose @Alternatives according to the environment the application is deployed to (development, testing, staging, production...) I was looking for a way to configure the CDI-container whithout building seperate wars for each one.

I just could’nt believe I was the only person having this problem I searched the web for a solution, even made threads at the Weld Users Forum and at stackoverflow, but nobody was able to help me - so after sinking way to much time into the JSR-299 spec and weld source code I came up with this:

Goal: we want to choose different sets of @Alternatives depending on the environment our war file is deployed to. First thing we do is create an @Alternative @Stereotype to hold our meta-information and also in order to just add one type to the beans.xml <alternatives> section instead of adding each @Alternative seperately:

@Alternative
@Stereotype
@Retention(RUNTIME)
@Target(TYPE)
public @interface EnvironmentAlternative {
    EnvironmentType[] value();
}

EnvironmentType is just an enum detailing the different - wait for it… environment types:

public enum EnvironmentType {
    DEVELOPMENT, TESTING, STAGING, PRODUCTION;
}

We need to make sure to have a single @Default implementation for each interface, CDI will abort during deployment otherwise. All other implementations get annotated with our nnw stereotype:

@Default
public class MockupFoo implements Foo {
  ...
}

@EnvironmentAlternative({STAGING, PRODUCTION})
public class FooImpl implements Foo {
  ...
}

As EnvironmentAlternative is an alternative stereotype MockupFoo is now choosen by the CDI container for all injection points. After we add the following lines to our beans.xml FooImpl is choosen instead:

<alternatives>
  <stereotype>com.example.EnvironmentAlternative</stereotype>
</alternatives>

Not exactly what we wanted, but there’s something we can do about that: ever heard of CDI extensions? They are a way to extend CDI (duh) by observing CDI lifecycle events. Writing one is quite easy, just extend the Extension interface:

The extension itself is not very complicated: the processAnotated method gets called for each annotated type the container processes, and if it is annotated as @EnvironmentAlternative AND the current environemnt is NOT in the specified environments we call the event’s veto() method, this prevents the type from being processed any further. Easy! The only thing left to do is create a file called javax.enterprise.inject.spi.Extension in your app’s META-INF/services directory containing a single line: our extension’s fully qualified classname.

In a real application of course instead of hardcoding the currentEnv field you’d have to decide how to obtain the current environment type, I just use a system property in our different glassfish installations that I create with

  nsn@nsn: ~> asadmin create-system-properties ENVIRONMENT=DEVELOPMENT

and then simply obtain via

    String propertyValue = System.getProperty("ENVIRONMENT");
    currentEnv = EnvironmentType.valueOf(propertyValue);

In our current project my team and I use this method to be able to build our application once, then deploy it to different environments and have it behave differently there. The only problem I wasn’t able to solve with this technique is choosing which @Decorators are enabled, as if I just veto() a @Decorator that is explicitly enabled in the beans.xml the container complains about not being able to find that class. If anybody comes up with a solution to this please contact me somehow, for now our @Decorators themselves are responsible for when they are active and when not.

by nsn on 2011-30-11 tags: programming jee java cdi

comments powered by Disqus