Implementing additional functionality

Tutorial – CDI Extension Programming

MarkStruberg
Java-CDI1

Ronald Steininger, Arne Limburg, and Mark Struberg delve into the world of portable CDI extensions

In the old days of
Enterprise Java, a lot of frameworks were created, each with a very
clear technological goal. Unfortunately these quickly became
overloaded and stuffed with tons of features to fulfill each and
every need. The CDI Expert Group aimed to avoid this problem with
the CDI specification by strictly focusing on the core problems of
dependency injection and defining a very powerful extension
mechanism for implementing additional functionality on top of it.
The CDI extension mechanism quickly became popular and a lot of
high-quality extensions are already available.

This mechanism is
called ‘portable CDI extensions’ because it is based on a
well-specified SPI which must be supported by every certified
standalone CDI container and EE 6 server. Thus a CDI extension
written on one CDI container will also run on all others: JBoss
Weld, Apache OpenWebBeans, Caucho Resin CanDI, IBM WebSphere 8,
Oracle Glassfish 3, Oracle Weblogic 12c or Apache TomEE.

Because of the
portable, vendor-independent way these extensions are implemented,
they can be reused with all CDI provider implementations. Projects
like Apache MyFaces CODI and JBoss Seam 3 provide collections of
“important”, commonly-needed extensions, letting CDI users easily
augment their applications with the extensions they need without
weighing down the core CDI implementation.

How does a CDI
Extension work?

The CDI extension
mechanism provides ways to hook custom code into the lifecycle of
the CDI container. To leverage this power in our own CDI extensions
we must therefore know the very basics of the CDI container
lifecycle itself. We will first give you a quick overview of this
process and dive into the details later in the article.

The first task a CDI container performs is to load
all CDI extensions. Next, all of the classes contained within JAR
files that have a META-INF/beans.xml marker file will be scanned
also. For each of these classes an
AnnotatedType
will be constructed based on
the given class. This object contains metadata about all
annotations, constructors, methods, fields, etc. for the processed
class. This information can be modified by extensions. Later the
CDI container will expose this metadata in Bean<T> instances
used to manage contextual objects.

The container will
start all available contexts after all classes have been scanned
and all constraints have been verified.

Writing a CDI
Extension for a job scheduler

An example
integration of a job scheduling service will highlight what is
necessary to leverage the extension mechanism in a CDI environment.
In this article, we want to go through the steps needed to build
such scheduler integration by writing a CDI extension.

Creating a CDI extension is as simple as writing a
class that implements the interface

javax.enterprise.inject.spi.Extension. This class will contain the functionality of our
extension. We will see in later chapters how this class can
interact with the CDI container.

The Container will pick up our extension via the
Java
ServiceLoader mechanism. Simply place a file named
javax.enterprise.inject.spi.Extension (the same name as the interface) within the
META-INF/services folder of your JAR and ensure this file contains
the fully-qualified name of the Extension implementation. The
container will look up the name of the class at runtime and
instantiate it via the default constructor (therefore the class
needs a default constructor).

Before we delve
into the content of the extension, we will first look into Quartz,
the scheduler we want to integrate with, and how Quartz would be
used without our extension.

The classic way
to schedule jobs with Quartz

Quartz is an open-source job
scheduling service. The user defines “jobs” and lets Quartz know
when these jobs should be executed via so-called “triggers”. The
scheduler itself will then run these jobs following the given
rules.

The easiest way to use Quartz in a servlet
container is probably to use the

QuartzInitializerServlet Quartz provides. This load-on-startup servlet initializes
the scheduler automatically and adds a second servlet which
actually schedules the jobs. Listing 1 shows a typical code block
in that servlet which schedules a job named
MyJob to run every ten minutes. 

Listing 1

import static org.quartz.JobBuilder.*;2
import static org.quartz.TriggerBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
...
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler scheduler = schedFact.getScheduler();

JobDetail job = newJob(MyJob.class)
  .withIdentity("myJob")
  .build();
Trigger trigger = newTrigger()
   .withIdentity("myTrigger")
   .withSchedule(cronSchedule("0 0/10 * * * ?"))
   .forJob("myJob")
   .build();

scheduler.scheduleJob(job, trigger);

That’s a lot of
code just to schedule one job (and the code for that job is still
missing) but will work just fine for simple use cases and a small
number of jobs that don’t change much.

As soon as the jobs get a bit more complicated,
there’s a problem: the jobs will not be aware of the CDI container,
which means that they cannot use the features provided by CDI. This
will be no problem for very simple jobs but won’t cut it as soon as
the user wants to use a simple
@Inject in one of the jobs or a service that uses
CDI. Some sort of integration is needed to make all the features
provided by CDI available to the job, and the best way to
accomplish this is by writing an Extension.

Schedule jobs the
CDI way

Let’s see how the
jobs used with our extension will look: 

  1. The job implements
    the
    java.lang.Runnable interface rather than the Job interface provided by Quartz.
    This way, the code is not Quartz-specific and the scheduler used by
    our extension can easily switched later. The scheduler will call
    the
    run() method of this Runnable based on the schedule we
    define.
  2. It’s possible to use CDI features like injection or
    interceptors in these jobs.
  3. A @Scheduled annotation will tell our extension that this class
    defines a job that must be scheduled and what its schedule will be
    (Listing 2).

Listing 2

@Scheduled(“0 0/10 * * * ?”)
public class MyJob implements Runnable {
  
  @Inject
  private MyService service;

  @Override
  public void run() {
      service.doSomething();
  }
}

That’s all that is
needed to convey the same information as the code example
before.

For this example, @Scheduled
is a simple annotation used to
set the schedule:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scheduled {
 String value(); //the job’s schedule
}

It would be
trivial to change this annotation to enable more advanced features
like setting the schedule via dynamic configuration rather than
hard-coding it in the job’s source; the annotation needs only to
hold the information necessary to let our extension decide how to
configure the job. Now it’s time to look at the extension
itself.

Creating the CDI
Extension

A CDI extension class implements the
javax.enterprise.inject.spi.Extension interface. This is just a marker interface and
thus does not define any methods itself. Instead of using a fixed
set of methods statically defined in an interface, the container
will fire a series of CDI Events during the container lifecycle.
The Extension can interact with the CDI container by defining
observer methods for such events. An observer method is a
non-static non-final method which has an
@Observes
parameter annotation with the
type of the observed event. It can not only gather information from
the CDI container this way; it can also modify this information and
even transmit back new information to the container. As a rule of
thumb, all things possible via CDI annotations can also be
performed programmatically in an extension. You can very easily add
or modify scopes, Interceptors, Decorators,

InjectionPoints,
Producer methods,
ObserverMethods, etc. The defined container lifecycle events
are:

  • BeforeBeanDiscovery
  • ProcessAnnotatedType
  • ProcessInjectionTarget and ProcessProducer
  • ProcessBean and ProcessObserverMethod
  • AfterBeanDiscovery
  • AfterDeploymentValidation
  • BeforeShutdown

The first article of this series described how to
observe custom CDI events. Extensions can observe the container
system events in exactly the same way. To find every class that has
the
@Scheduled annotation, the extension should observe the
ProcessAnnotatedType event. This system event is fired for every class scanned
by the container during boot. A Quartz job and Quartz trigger can
be created and started after the container initialization is
complete for any such annotation found (Listing 3).

Listing 3

public void scheduleJob(@Observes ProcessAnnotatedType pat) {
 AnnotatedType t = pat.getAnnotatedType();
 Scheduled schedule = t.getAnnotation(Scheduled.class);
 if (schedule == null) {
   //no scheduled job, ignoring this class
   return;
 }
 Class<Runnable> jobClass
   = t.getJavaClass().asSubclass(Runnable.class);
 if (jobClass == null) {
   LOG.error("Can't schedule job " + t);
   return;
 }
 JobDetail job = newJob(CdiJob.class)
   .usingJobData(CdiJob.JOB_CLASS_NAME, jobClass.getName())
   .build();
 Trigger trigger = newTrigger()
   .withSchedule(cronSchedule(schedule.value()))
   .build();
 scheduler.scheduleJob(job, trigger);
}

An observer for the BeforeBeanDiscovery
event which is fired before the
scanning process starts can be used to initialize the
scheduler.

public void initScheduler(@Observes BeforeBeanDiscovery event) {
 scheduler = StdSchedulerFactory.getDefaultScheduler();
}

The scheduler also has to be started. This can be
done in an
AfterDeploymentValidation event observer. That event is fired after
the container has validated that there are no deployment problems,
so all jobs will be scheduled at this point. Additionally,
the
BeanManager can be stored in the Extension for later use (e.g. in
the
CdiJob class).

public void startScheduler(@Observes AfterDeploymentValidation event, 
                          BeanManager bm) {
 beanManager = bm;
 try {
   scheduler.start();
   LOG.info(“Started scheduler.”);
 } catch (SchedulerException se) {
   throw new RuntimeException(se);
 }
}

Finally, the shutdown()
method of the scheduler is
called in an observer of the
BeforeShutdown
event.

public void shutdownScheduler(@Observes BeforeShutdown event) {
 try {
   scheduler.shutdown(true);
 } catch (SchedulerException se) {
   throw new RuntimeException(se);
 }
}

The following class does the work needed, so that a CDI-managed
instance of the Runnable (our actual job) we defined is run as a
Quartz job. 

  1. It extracts the job class from the Quartz configuration.

  2. Before calling the run() method, it receives the actual CDI-managed
    instance from the
    BeanManager.

  3. After the run() method returns, the instance is destroyed (Listing
    4).

Our extension sets up all scheduled jobs to use this class,
so that all of this happens in a manner completely transparent to
the user. Keep in mind that contexts like the

RequestContext and SessionContext are not active for this job (there is no active
request nor session). If the injected services need these contexts
because they depend on beans like a
RequestScoped
EntityManager
, the
contexts can be started and stopped in

@PostConstruct and @PreDestroy methods of the job. This is container-specific at
the moment, but work is underway within the Apache DeltaSpike
project to make this possible in a vendor-independent
way.

Listing 4

public class CdiJob implements org.quartz.Job {

 public final static String JOB_CLASS_NAME = “CDI_JOB_CLASS_NAME”;

 @Override
 public void execute(JobExecutionContext context)
     throws JobExecutionException {
   JobDataMap jobData = context.getJobDetail().getJobDataMap();
   String className = jobData.getString(JOB_CLASS_NAME);
   Class<Runnable> jobClass;
   try {
     jobClass = Class.forName(className).asSubclass(Runnable.class);
   } catch (ClassNotFoundException e) {
     throw new JobExecutionException(e);
   }
   BeanManager bm = QuartzExtension.getBeanManager();
   Set<Bean<?>> jobBeans = bm.getBeans(jobClass);
   Bean<?> jobBean = bm.resolve(jobBeans);
   CreationalContext c = bm.createCreationalContext(jobBean);
   Runnable job = (Runnable) bm.getReference(jobBean, Runnable.class, c);
   try {
     job.run();
   } finally {
     jobBean.destroy(job, c);
   }
 }
}

Summary

One of the most important features of CDI is the possibility to
create extensions. With this mechanism, CDI can be extended in a
portable, vendor-independent way to provide new features (like
custom scopes), implement application-specific functionality (like
loading configuration from an external database) or to integrate
other technologies in a CDI-like fashion.

Author Info:

Ronald
Steininger
is a Senior Software Engineer for the Research
Group for Industrial Software (INSO) at the Vienna University of
Technology. He spent the last six years working with Java on both
rich clients and web applications. He and his colleagues use CDI
and Java EE 6 for all of their projects since late 2009. He spends
his spare time working on his master thesis about the software
architecture of campus management systems.

Arne
Limburg
is Enterprise Architect at open knowledge GmbH in
Oldenburg, Germany. He is an
experienced developer, architect and trainer in the Enterprise
environment and also regularly involved in Android development. He
frequently speaks at conferences and conducts workshops in those
fields. He is also an active participant in open source projects,
e.g. as a committer of Apache OpenWebBeans and as initiator and
Project Lead of JPA Security.

Mark
Struberg
is a software architect with over 20 years of
programming experience. He has been working with Java since 1996
and is actively involved in open source projects in the Java and
Linux area. He is Apache Software Foundation member and serves as
PMC and Committer for Apache OpenWebBeans, MyFaces, Maven, OpenJPA,
BVal, DeltaSpike and other projects. He is also a CDI Expert Group
member actively working on the specification. Mark works for the
Research Group for Industrial Software (INSO) at the Vienna
University of Technology.

This article originally appeared in Java Tech Journal: CDI. For
more articles of a CDI-nature, download that
issue here

Author
MarkStruberg
Mark Struberg is a software architect with over 20 years of programming experience. He has been working with Java since 1996 and is actively involved in open source projects in the Java and Linux area. He is Apache Software Foundation member and serves as PMC and Committer for Apache OpenWebBeans, MyFaces, Maven, OpenJPA, BVal, DeltaSpike and other projects. He is also a CDI Expert Group member actively working on the specification. Mark works for the Research Group for Industrial Software (INSO) at the Vienna University of Technology.
Comments
comments powered by Disqus