Scheduler Module

Overview

The Scheduler module provides simple integration with Quartz v2 (default) or any other scheduler that supports cron-expressions for job-classes.

Project Setup

The configuration information provided here is for Maven-based projects and it assumes that you have already declared the DeltaSpike version and DeltaSpike Core module for your projects, as detailed in Configure DeltaSpike in Your Projects. For Maven-independent projects, see Configure DeltaSpike in Maven-independent Projects.

1. Declare Scheduler Module Dependencies

Add the Scheduler module to the list of dependencies in the project pom.xml file using this code snippet:

<dependency>
    <groupId>org.apache.deltaspike.modules</groupId>
    <artifactId>deltaspike-scheduler-module-api</artifactId>
    <version>${deltaspike.version}</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <groupId>org.apache.deltaspike.modules</groupId>
    <artifactId>deltaspike-scheduler-module-impl</artifactId>
    <version>${deltaspike.version}</version>
    <scope>runtime</scope>
</dependency>

Or if you’re using Gradle, add these dependencies to your build.gradle:

     runtime 'org.apache.deltaspike.modules:deltaspike-scheduler-module-impl'
     compile 'org.apache.deltaspike.modules:deltaspike-scheduler-module-api'

2. Declare External Dependencies

By default, the Scheduler module looks to integrate with Quartz. If this is the scheduler you would like to use, add Quartz 2.x to the list of project dependencies using this code snippet:

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
</dependency>

Or if you’re using Gradle, add these dependencies to your build.gradle:

     compile 'org.quartz-scheduler:quartz'

3. Declare Container Control dependency

Scheduled jobs can have built-in CDI contexts started for the duration of their execution using @Scheduled#startScopes which internally uses the Container Control module. The dependency on the API and the appropriate implementation needs to be declared manually even if the feature is not used. An example for the Weld implementation:

<dependency>
    <groupId>org.apache.deltaspike.cdictrl</groupId>
    <artifactId>deltaspike-cdictrl-api</artifactId>
    <version>${deltaspike.version}</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <groupId>org.apache.deltaspike.cdictrl</groupId>
    <artifactId>deltaspike-cdictrl-weld</artifactId>
    <version>${deltaspike.version}</version>
    <scope>runtime</scope>
</dependency>

Of ir you’re using Gradle:

dependencies {
    runtime 'org.apache.deltaspike.cdictrl:deltaspike-cdictrl-weld'
    compile 'org.apache.deltaspike.cdictrl:deltaspike-cdictrl-api'
}

@Scheduled with org.quartz.Job or java.lang.Runnable

Just annotate your Quartz-Jobs with @Scheduled and they will get picked up and passed to the scheduler automatically (during the bootstrapping process).

Scheduled task based on org.quartz.Job
@Scheduled(cronExpression = "0 0/10 * * * ?")
public class CdiAwareQuartzJob implements org.quartz.Job
{
    @Inject
    private MyService service;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException
    {
        //...
    }
}

As an alternative it’s possible to annotate an implementation of java.lang.Runnable (since DeltaSpike v1.5.3):

Scheduled task based on java.lang.Runnable
@Scheduled(cronExpression = "0 0/10 * * * ?")
public class CdiAwareRunnableJob implements java.lang.Runnable
{
    @Inject
    private MyService service;

    @Override
    public void run()
    {
        //...
    }
}

Behind the scenes DeltaSpike registers an adapter for Quartz which just delegates to the run-method once the adapter gets called by Quartz. Technically you end up with almost the same, just with a reduced API for implementing (all) your scheduled jobs. Therefore the main difference is that your code is independent of Quartz-classes. However, you need to select org.quartz.Job or java.lang.Runnable for all your scheduled-tasks, bot not both!

In such scheduled-tasks CDI based dependency-injection is enabled. Furthermore, the request- and session-scope get started (and stopped) per job-execution. Therefore, the container-control module (of DeltaSpike) is required. That can be controlled via @Scheduled#startScopes (possible values: all scopes supported by the container-control module as well as {} for 'no scopes').

With 'false' for @Scheduled#onStartup, it is even possible to schedule/install jobs dynamically.

The following example shows how to use it, if you are using org.quartz.Job (and not java.lang.Runnable).

Example
@ApplicationScoped
public class ProjectStageAwareSchedulerController
{
    @Inject
    private Scheduler<Job> jobScheduler;

    @Inject
    private ProjectStage projectStage;

    public void registerJobs()
    {
        if (ProjectStage.Production.equals(this.projectStage))
        {
            //see 'false' for @Scheduled#onStartup
            this.jobScheduler.registerNewJob(ManualCdiAwareQuartzJob.class);
        }
    }

    @Scheduled(cronExpression = "0 0/10 * * * ?", onStartup = false)
    public class ManualCdiAwareQuartzJob implements org.quartz.Job
    {
        @Inject
        private MyService service;

        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException
        {
            //...
        }
    }
}

Configurable CRON expressions

In some cases it might be useful to configure a cron-expression e.g. per Project-Stage. Therefore, DeltaSpike (v1.6.0+) allows to use keys instead of hardcoded expressions.

In the previous examples we had e.g. @Scheduled(cronExpression = "0 0/10 * * * ?"). Instead of hardcoding it that way, it’s possible to use e.g. @Scheduled(cronExpression = "{myCronExpression}") and in one of the active config-sources used by DeltaSpike a concrete expression can be defined e.g. via myCronExpression=0 0/10 * * * ?. Using e.g. myCronExpression.Development=0 0/5 * * * ? would allow to change the configured expression for Project-Stage development.

Manual Scheduler Control

The SPI allows to control the scheduler (or integrate any other compatible scheduler as an alternative to Quartz2)

Via standard injection like

@Inject
private Scheduler<Job> jobScheduler;

it is possible to manually start/stop the scheduler, pause/resume/interrupt/check scheduled jobs, register jobs manually or start a job once (without registering it permanently).

Attention: To use a typed injection-point and avoid deployment failure with some versions of Weld, you must use

public class QuartzSchedulerProducer
{
    @Produces
    @ApplicationScoped
    protected Scheduler<Job> produceScheduler(Scheduler scheduler)
    {
        return scheduler;
    }
}

or

<alternatives>
  <class>org.apache.deltaspike.scheduler.impl.QuartzSchedulerProducer</class>
</alternatives>

Manual scheduling

If the SPI provided by org.apache.deltaspike.scheduler.spi.Scheduler doesn’t provide a method you are looking for, you can use #unwrap to access the underlying scheduler. Per default DeltaSpike uses an implementation of org.quartz.Scheduler. Therefore, it’s possible to inject org.apache.deltaspike.scheduler.spi.Scheduler and use it like in the following example:

public class ManualJobScheduler
{
    @Inject
    private Scheduler<Job> scheduler;

    @Override
    public void scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException
    {
        this.scheduler.unwrap(org.quartz.Scheduler.class).scheduleJob(jobDetail, trigger);
    }
}

With that it’s e.g. possible to schedule quartz-jobs based on the same quartz-job(-class), but with different triggers,…​ Also manually scheduled jobs benefit from DeltaSpike features like the support of @Inject in the job-instances.

Execute java.lang.Runnable with ManagedExecutorService

If you would like to use e.g. the ManagedExecutorService (with EE7+) to run the jobs, you can provide a custom adapter by adding e.g. deltaspike.scheduler.runnable-adapter-class=mypackage.DelegatingJobRunnableAdapter to META-INF/apache-deltaspike.properties. Such an adapter just needs to implement org.quartz.Job and in case of EE7+ inject e.g. ManagedExecutorService as shown in the following example:

public class DelegatingJobRunnableAdapter implements java.lang.Runnable
{
    @Resource
    private ManagedExecutorService managedExecutorService;

    @Override
    public void run()
    {
        Class<? extends Runnable> jobClass =
            ClassUtils.tryToLoadClassForName(context.getJobDetail().getKey().getName(), Runnable.class);

        Runnable runnableBean = BeanProvider.getContextualReference(jobClass);
        managedExecutorService.execute(runnableBean);
    }
}

Custom Scheduler

It is possible to replace the default integration with Quartz. Any scheduler that supports cron-expressions for job-classes can be used. For more information, see Scheduler javadoc.