Writing a Portable Extension - JBoss Weld CDI for Java Platform (2013)

JBoss Weld CDI for Java Platform (2013)

Chapter 8. Writing a Portable Extension

As CDI was never intended to be a stationary target, only being enhanced and updated as part of a JSR, a series of SPIs (Service Provider Interfaces) are exposed by CDI so that developers like us can extend and enhance CDI through portable extensions.

We'll look at how to create a portable extension, what is the container lifecycle and how it can be utilized in creating additional functionality in CDI, how we can consume CDI beans from non-CDI beans, and some example extensions to provide an idea of what can be achieved.

What is a portable extension?

A portable extension is a Java Service Provider that is retrieved during container startup by Weld and notified at each stage of the container lifecycle, based on which events our portable extension needs to observe.

What a portable extension can achieve within CDI is only limited by our imagination and the current SPI that is available in a particular release!

As stated in the specification:

A portable extension may integrate with the container by:

· Providing its own beans, interceptors, and decorators to the container

· Injecting dependencies into its own objects using the dependency injection service

· Providing a context implementation for a custom scope

· Augmenting or overriding the annotation-based metadata with metadata from some other source

To create a portable extension, we only need to complete two steps:

1. Write a Java class that implements javax.enterprise.inject.spi.Extension as follows:

public class OurExtension implements Extension { ... }

2. Register the extension as a service provider by creating META-INF/services/javax.enterprise.inject.spi.Extension and setting the content of the file to the classname of our extension:

org.cdibook.chapter8.OurExtension

Note

Although an extension is not a CDI bean, it can be injected into any bean after the Weld initialization process has completed, just like any other bean.

A single instance of our extension is maintained by Weld for the lifetime of our application, so it is possible to interact with our extension from within our application as if it was @ApplicationScoped.

What is the CDI container lifecycle?

When an application is initialized, Weld fires an event at each stage of the container lifecycle that allows extensions to integrate with the initialization process of Weld.

The container lifecycle consists of the following event sequence:

1. The BeforeBeanDiscovery event is fired before Weld begins the process of discovering beans within our application. This event allows us to add scopes, annotated types, qualifiers, stereotypes, and interceptor bindings to our application. Adding an annotated type is the most powerful, as this allows us to take a class that would not be a bean and make it a bean within our application.

2. The AfterBeanDiscovery event is fired by Weld when it has fully completed the bean discovery process and verified that there are no definition errors with the beans that were discovered within our application. Our extension can observe this event to add new beans, observer methods, or context.

3. The AfterDeploymentValidation event is fired by Weld after verifying that there are no deployment problems, but before it creates any contexts or processes a request. Our extension can use this event to add a validation error that we are aware of, maybe through a custom bean of ours (causing Weld to abort deployment) or we can remove any temporary storage we may have created as part of previous events that is no longer required.

4. The BeforeShutdown event will be fired by Weld once it has finished processing all requests and destroyed all contexts.

Each of these events is fired once by Weld for our application as part of the container lifecycle.

Between BeforeBeanDiscovery and AfterBeanDiscovery, Weld fires the following events based on what was found in our bean archive during its scanning:

· The ProcessAnnotatedType event is fired for each type that is present within the bean archive. This occurs prior to the annotations on the type being read, allowing us to prevent the type from being used, and thus prevent any further initialization processing of it, or replace the AnnotatedType.

· The ProcessInjectionTarget event is fired for every bean and Java EE component, such as a servlet or Session Bean, that is the target of an injection point within our application.

· The ProcessBean event is fired for every bean, interceptor, or decorator within a bean archive, as long as the bean is not annotated with @New. The type of bean that was discovered by Weld will determine which one among ProcessManagedBean, ProcessSessionBean,ProcessProducerMethod, and ProcessProducerField is fired.

· The ProcessProducer event is fired for each producer, method, or field that is present on an enabled bean of our application.

· The ProcessObserverMethod event is fired for each observer method that is present on an enabled bean of our application.

Note

Any AnnotatedType that was added to Weld during the BeforeBeanDiscovery event will not have a ProcessAnnotatedType event fired for it. This will be fixed as part of CDI 1.1 in such a way that a ProcessAnnotatedType event is fired.

Here's a simple example of our extension modifying the metamodel by preventing a bean from being enabled, and thus present within our application, if it is annotated with @Disable:

public class OurExtension extends Extension {

public <T> void processAnnotatedType(

@Observes ProcessAnnotatedType<T> type) {

if (type.getAnnotatedType().isAnnotationPresent

(Disable.class)) {

type.veto();

}

}

}

If our extension needs to make use of the BeanManager when observing an event, it can be injected as follows:

public <T> void processAnnotatedType(

@Observes ProcessAnnotatedType<T> type,

BeanManager beanManager) {

...

}

BeanManager

The core piece in extending CDI with extensions is the BeanManager, as it provides programmatic access to a CDI container via an API. BeanManager provides many useful operations for developers writing an extension, some of which include the ability to obtain beans, interceptors, decorators, observers, and contexts.

Accessing the BeanManager is not restricted to injection into extension observer methods; we are able to inject it into any bean or other Java EE component that supports injection.

@Inject

BeanManager beanManager;

Note

There is no restriction on when the BeanManager methods can be called during the life of an application.

Injection into non-container managed instances

Sometimes we have a class that has CDI injection point on it but it isn't instantiated by the container, so it is not available for injection to be performed on it. An example of this is a portlet class that needs to inject beans into itself, but a portlet class is instantiated and controlled by a portlet container so Weld is unable to perform injection on it.

In our example, the portlet class would be classified as a non-container managed instance, as the instance of the portlet class is not created by Weld and therefore not managed by it either.

We can ask Weld to perform injection on our portlet class instance as follows:

// get the BeanManager, from JNDI in this example

BeanManager beanManager = (BeanManager)

new InitialContext().lookup("java:comp/BeanManager");

// create an AnnotatedType for our class

AnnotatedType<MyPortlet> portletType =

beanManager.createAnnotatedType(myPortlet.getClass());

// create an InjectionTarget

InjectionTarget<MyPortlet> injectionTarget =

beanManager.createInjectionTarget(portletType);

// create the CreationalContext that is required for each instance

CreationalContext ctx = beanManager.createCreationalContext(null);

// call initializer methods and perform injection on our myPortlet

// instance

injectionTarget.inject(myPortlet, ctx);

// call @PostConstruct

injectionTarget.postConstruct(myPortlet);

You may have noticed that we didn't call injectionTarget.produce() in our example. In this case, we already had an instance of our class that required injection to be performed, so we did not need to create a new instance of the class within Weld.

We can then clean up the objects in our example by using the following:

// Call @PreDestroy on our class

injectionTarget.preDestroy(myPortlet);

// Discard the instance

injectionTarget.dispose(myPortlet);

// Clean up the CreationalContext

ctx.release();

This example shows how we can perform injection into a portlet class that is managed by a portlet container, but the same principles apply to injecting beans into a third-party framework class too.

The main change that would be required is calling the following:

ThirdPartyComponent instance = injectionTarget.produce(ctx);

This will construct an instance of the ThirdPartyComponent class that is now managed by Weld.

Registering a bean

An extremely common use case for extension developers is to register one or more beans with Weld to make them available within an application. To make it possible to register new beans, CDI provides the Bean interface, which represents a bean within an application. For every bean in our application, including interceptors, decorators, and producers, there is an instance of Bean that is registered with the BeanManager.

Note

The AnnotatedType represents the metadata of a bean type, which is used by Weld to then construct a physical Bean instance from that metadata.

We will create a simplified example of how an extension can add a bean to a CDI application.

public class AddBeanExtension implements Extension {

public void afterBeanDiscovery

(@Observes AfterBeanDiscovery after, BeanManager beanMgr) {

// read the annotations of our class

AnnotatedType<MyClass> type =

beanMgr.createAnnotatedType(MyClass.class);

// instantiate class and inject dependencies

final InjectionTarget<MyClass> injectionTgt =

beanMgr.createInjectionTarget(type);

after.addBean( new Bean<MyClass>() { ... } );

}

}

Replacing annotations on a type via an extension

Assume we have an application that was written with plain POJOs but we want to migrate to CDI. If our POJOs already have a home-grown annotation, possibly for documentation purposes in the past, we can use that annotation as a stepping stone to CDI annotations.

Say our application currently has the following annotation defined, and it's used on all POJOs:

@Retention(RUNTIME)

@Target(TYPE)

public @interface PojoDoc {

String value();

ScopeEnum scope() default ScopeEnum.REQUEST;

}

Our application has the following two beans, as an example:

@PojoDoc("myRequestBean")

public class MyRequestBean { ... }

@PojoDoc(value = "userSession", scope = ScopeEnum.SESSION)

public class MySessionBean implements Serializable { ... }

And we have a new CDI bean that we want to use them in:

@RequestScoped

public class UsageBean {

@Inject

MyRequestBean requestBean;

@Inject

MySessionBean sessionBean;

}

As it currently stands, both requestBean and sessionBean will be dependent scoped instances. What if we want CDI to treat them as @PojoDoc defines them? We can achieve it with the following extension:

public class PojoDocExtension implements Extension {

<X> void processType(@Observes ProcessAnnotatedType<X> event) {

AnnotatedType<X> orig = event.getAnnotatedType();

if (orig.isAnnotationPresent(PojoDoc.class)) {

Annotation scopeAnnotation = null;

PojoDoc pojoDoc = orig.getAnnotation(PojoDoc.class);

switch (pojoDoc.scope()) {

case REQUEST:

scopeAnnotation = new RequestScopedLiteral();

break;

case SESSION:

scopeAnnotation = new SessionScopeLiteral();

break;

case APPLICATION:

scopeAnnotation = new ApplicationScopedLiteral();

break;

}

AnnotatedType<X> updated = new AnnotatedTypeBuilder<X>()

.readFromType(orig, true)

.addToClass(

new NamedLiteral(pojoDoc.value())

)

.addToClass(scopeAnnotation)

.removeFromClass(PojoDoc.class)

.create();

event.setAnnotatedType(updated);

}

}

}

What our extension does is listen for the ProcessAnnotatedType event and look for types that have an annotation of @PojoDoc set on them, as these will be our POJOs that are not specifically built for CDI.

We use the scope() value within the @PojoDoc instance from the type to determine whether we need to add a scope annotation for @RequestScoped, @SessionScoped, or @ApplicationScoped. This behavior is all handled within the switch statement.

Finally, we create a new AnnotatedType with the help of the AnnotatedTypeBuilde class from Apache DeltaSpike, by adding the scope annotation we created and @Named with the value retrieved from the @PojoDoc value. We also removed the @PojoDoc annotation from our bean, as it has no meaning for Weld, though we could just as easily have left it there. The new AnnotatedType we created is then set back onto the event, replacing the type that we received on the event so ours will be used to define the metadata for the bean instead.

For this example, we utilized Apache DeltaSpike for working with the AnnotatedType instances to make our work a bit simpler.

Note

At the time of publication, the latest Apache DeltaSpike release was 0.3-incubating.

Summary

We've barely scratched the surface of portable extensions in this chapter, as there are an enormous number of things that can be achieved when utilizing the CDI container lifecycle. So many, in fact, that this is one of those rare situations where your imagination is more likely to be an inhibiting factor, and not the available SPI of CDI.

We explained what a portable extension is and what is required of us in developing one and having Weld recognize it. We briefly went through the CDI container lifecycle, discussing the events that are fired by Weld at each phase, while providing some ideas about what can be done as part of each event. We looked at the BeanManager, a core part of any extension developer's toolkit, before we learned how to perform injection into an instance that is not managed by Weld. Then we showed how a new bean can be created and registered with Weld using the Bean interface.

At the end of the chapter, we showed a complete example of an extension that took an existing annotation from non-CDI designed POJOs and converted it into several CDI-specific annotations to provide the originally anticipated behavior within CDI.

Our example extension provides a good starting point for writing more complex extensions while also providing insight into what can be done. For instance, we could use the idea in our example to replace annotations or metadata on the POJOs of a framework to enable them as CDI beans.