Scheduling Jobs With Tapestry
Frequently web applications have a requirement to execute some business logic which is not bound to any HTTP request. For example, you might want to execute some periodic maintenance job to cleanup the database or a job sending messages to your users. In this article I’ll show you how to register jobs to be executed by Tapestry without blocking any incoming HTTP request.
Interval Jobs
Imagine you have a CleanupService service to be executed for database cleanup. The service’s interface is shown in the following example. The service implementation details are not interesting for this article as they are very specific to your application.
public interface CleanupService {
void clean();
}
Now let’s schedule a periodic execution of the cleanup at application’s startup. This is accomplished by contributing to the RegistryStartup service’s configuration, as shown in the following example.
public class AppModule {
@Startup
public static void scheduleJobs(
PeriodicExecutor executor,
final CleanupService cleanupService) {
executor.addJob(
new IntervalSchedule(300000L),
"Cleanup Job",
new Runnable() {
public void run() {
cleanupService.clean();
}
});
}
}
As of Tapestry 5.3 the PeriodicExecutor service can be used to schedule jobs. In the example above, this service is injected into a startup method. The service’s addJob method is used to register jobs to be executed.This method takes three parameters:
- Instance of Schedule:Â defines when to execute the next job
- Name used in debugging output related to the job
- Instance of Runnable that represents the work to be done
In the example above, a IntervalSchedule instance is used to schedule a job to be executed every 300000 milliseconds (5 minutes). Note that the Runnable is just a wrapper around the CleanupService.
Using CRON expressions
Instead of defining intervals for job executions, Tapestry also allows you to use CRON expressions, such as described here. Note that Tapestry uses the CRON parser from the Quartz Scheduler without depending on it.
The following example demonstrates how to use CRON expressions.
public class AppModule {
@Startup
public static void scheduleJobs(
PeriodicExecutor executor,
final CleanupService cleanupService) {
executor.addJob(
new CronSchedule("0 0/5 14 * * ?"),
"Cleanup Job",
new Runnable() {
public void run() {
cleanupService.clean();
}
});
}
}
In the example above, a job is registered to be executed every 5 minutes starting at 2pm and ending at 2:55pm, every day.
Happy scheduling.
Nice article, Igor. But you didn’t mention any reasons why one wouldn’t just use quartz instead. Any thoughts on that?
Thank you for the feedback, Bob. While Quartz is a sophisticated scheduling library, Tapestry’s scheduling features are only meant for simple use cases. For example, Quartz is able to catch up missed jobs, Quartz’s jobs can be persisted in the database, Quartz supports JTA transactions, etc. I think I should update the article.
In addition, the way Quartz operates, the jobs must be serializable which is a minor headache for Tapestry IoC code, where the Job often has dependencies on services. Service proxies are serializable, but it’s odd to have to make the Job itself serializable. Further, this approach allows services to implement Runnable (either as part of their service interface or as part of their service implementation) and register themselves for periodical callbacks as part of instance initialization.
Is nice to see all these features of tapestry. I’m also using quartz for job scheduling.
What about clustered environments? This approach seem not to take care of all service instance of all nodes. Have you a solution for these usual environments?