TimeMachine is an open source Java scheduler that can run high volume of jobs with many different type of schedules. It supports repeating schedule on fixed interval, CRON based expression, or even custom schedules.
The scheduler can manage job executions with thread pools, and it can persist job data into different storage. Users may run the scheduler as stand-alone server, or deploy as web application. The scheduler engine is using a stack-based service container that is easy to configure and extend by developers.
The following table summarizes TimeMachine core features
* A stand-alone scheduler server with simple Properties configuration file.
* A web application (war) to run and manage the scheduler.
* Support job implementation in either Java or scripting language (Eg: Groovy)
* Support multiple thread pools for isolated jobs execution.
* Support in-memory or database data store.
* Support scheduler clustering – multiple scheduler nodes with single logical scheduler.
* Support job history recording.
* Built-in JobTask: LoggerJobTask, ScriptingJobTask and OsCommandJobTask.
* Built-in Schedule: RepeatSchedule, CronSchedule and DateListSchedule.
* Built-in Service: Crontab that’s similar to Linux/Unix OS crontab service.
* Built-in Service: JobLoader to load any job and schedule with simple properties file.
The [JBossAS] is a full JEE 6 application server. It provides many ways for you to write your own service and running on the server. The easiest way to write and deploy a service in JEE environment is to create a `war` application and have your service implements the `javax.servlet.ServletContextListener`. Then you add this
service in the `web.xml` and then your service will be started or stopped during `war` application lifecycle during deployment. Deploying `war` application in JBossAS is easy by copying the file into it’s deployments directory.
Actually the TimeMachine Scheduler already comes with a [scheduler-web.war] application, and you may drop it into `$JBOSS_HOME/standalone/deployments` to have a full running scheduler immediately! The built-in web application will give you a basic UI to view jobs and edit configuration online.
However, in this article, we will explore more on how to integrate the scheduler with JBossAS modules and use it from the `war` application instead. In another words, we want to deploy the scheduler jars separately from the `war` package. This provide better re-use of scheduler jars in the JBossAS server. Other services in the server may also use scheduler, and this makes the `war` much thinner.
Creating JBossAS module for TimeMachine Scheduler
JBossAS modules is a way to load jar files in separate space in the server. Instead of dumping all jars into a `lib` directory, JBoss modules let you define more clearly what a component and its dependencies are. Once you build up many modules, then reusing them as dependencies would simply take a declaration descriptor. Better yet, the JBossAS server already comes with many modules pre-defined, and many of them already satisfy what TimeMachine Scheduler needs!
Here is how you can quickly create a module in JBossAS. I assume you already have `$JBOSS_HOME` defined, if not just
replace it with path to wherever you installed it. I will show commands used in a Linux OS console in these examples.
$ cd $JBOSS_HOME
$ mkdir -p modules/timemachine/scheduler/main
And here’s the module definition:
<module xmlns="urn:jboss:module:1.1" name="timemachine.scheduler"> <resources> <resource-root path="timemachine-scheduler-1.2.0.jar"/> <resource-root path="timemachine-demo-jobs.jar"/> </resources> <dependencies> <module name="org.slf4j"/> <module name="org.apache.commons.lang"/> <module name="org.apache.commons.io"/> </dependencies> </module>
We’re going to build the `timemachine-demo-jobs.jar` in a moment, but let’s go over other few items first. You would need to download the [timemachine-scheduler-1.2.0.jar] and add to `main` directory. As noted also the scheduler has few dependencies also, and we simply declare them. These are already available in JBossAS server distribution.
Creating HelloJob for testing TimeMachine Scheduler
We need to create a job task class for TimeMachine scheduler, and we need to package it as a jar and load by JBossAS module. We do this because we are loading the scheduler classes in this module, and in order for it to find the job class, it needs the jar to be in same classloader space as well. (Note: TimeMachine Scheduler actually allows you to specify custom classloader, but let us not focus on that here.)
package demo; import timemachine.scheduler.*; public class HelloJob implements JobTask { @Override public void run(JobContext jobContext) { System.out.println("Hello World! - " + new java.util.Date()); } }
$ cd $JBOSS_HOME/modules/timemachine/scheduler/main
$ javac -d . -cp "*" HelloJob.java
$ jar cvf timemachine-demo-jobs.jar demo
As you can see, our demo job will not do much other than echo a msg out to STDOUT. A `JobTask` implementation is how you would write custom job that runs by the TimeMachine Scheduler. You will see how we will use this later in the article.
Creating a SchedulerSercice to start/stop TimeMachine Scheduler
Now we have setup our scheduler module, we want to create a `SchedulerService` to start up a scheduler. As mentioned before, the easy way to create a service is using `war` application. So let us do that.
$ cd $JBOSS_HOME/standalone/deployments
$ mkdir -p timemachine-demo.war/WEB-INF/classes
$ touch timemachine-demo.war.dodeploy
$ cd timemachine-demo.war/WEB-INF
We will add under WEB-INF a file jboss-deployment-structure.xml which declares the dependency to the timemachine.scheduler module:
<jboss-deployment-structure> <deployment> <dependencies> <module name="timemachine.scheduler" /> </dependencies> </deployment> </jboss-deployment-structure>
We will declare as well a listener to the SchedulerService into the web.xml file:
<web-app> <listener> <listener-class>demo.SchedulerService</listener-class> </listener> </web-app>
This is the SchedulerService Listener:
package demo; import javax.servlet.*; import timemachine.scheduler.*; public class SchedulerService implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { // Creating and starting TimeMachine Scheduler Scheduler scheduler = new SchedulerFactory().createScheduler(); scheduler.start(); //Let's save scheduler into context. sce.getServletContext().setAttribute("scheduler", scheduler); } @Override public void contextDestroyed(ServletContextEvent sce) { // Shutdown scheduler. Scheduler scheduler = (Scheduler)sce.getServletContext().getAttribute("scheduler"); scheduler.destroy(); } }
$ javac -d classes -cp "$JBOSS_HOME/modules/timemachine/scheduler/main/*:
$JBOSS_HOME/modules/javax/servlet/api/main/*" SchedulerService.java
$ $JBOSS_HOME/bin/standalone.sh
With above setup, the `timemachine-demo.war` application should be deployed and the scheduler started. Here is an example
of server console output:
22:38:45,104 INFO [org.jboss.as.server.deployment] (MSC service thread 1-7) JBAS015876: Starting deployment of “timemachine-demo.war”
22:38:45,337 INFO [timemachine.scheduler.service.SchedulerEngine] (MSC service thread 1-5) TimeScheduler system services initialized: [
scheduler: SchedulerData[id=1, name=TimeMachineScheduler],
schedulerNode: SchedulerNode[nodeId=1, name=localhost, ip=127.0.0.1],
configProps: null,
dataStore: MemoryDataStore[name=47865958],
scheduleRunner: PollingScheduleRunner[name=1644750266],
classLoader: SimpleClassLoaderService[name=450712280],
jobTaskFactory: SimpleJobTaskFactory[name=1892449427],
jobTaskPoolNameResolver: SimpleJobTaskPoolNameResolver[name=521698068],
jobTaskThreadPool: DynamicThreadPool[name=jobTaskThreadPool.DEFAULT],
]
22:38:45,345 INFO [timemachine.scheduler.service.SchedulerEngine] (MSC service thread 1-5) Scheduler[id=1, nodeId=1, nodeIp=127.0.0.1] initialized. Version=1.2.0.071620122220
22:38:45,346 INFO [timemachine.scheduler.service.SchedulerEngine] (MSC service thread 1-5) Scheduler[id=1, nodeId=1, nodeIp=127.0.0.1] started.
22:38:45,397 INFO [org.jboss.web] (MSC service thread 1-5) JBAS018210: Registering web context: /timemachine-demo
22:38:45,403 INFO [org.jboss.as] (MSC service thread 1-2) JBAS015951: Admin console listening on http://127.0.0.1:9990
22:38:45,403 INFO [org.jboss.as] (MSC service thread 1-2) JBAS015874: JBoss AS 7.1.1.Final “Brontes” started in 2226ms – Started 174 of 251 services (76 services are passive or on-demand)
22:38:45,434 INFO [org.jboss.as.server] (DeploymentScanner-threads – 2) JBAS018559: Deployed “timemachine-demo.war”
Creating new Jobs on TimeMachine Scheduler
Above shows the scheduler is up and running, but how do we add new job into it? Since we already have a `war` application, the easiest thing would be to create a servlet to do this. Here is an example :
package demo; import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.annotation.*; import javax.servlet.http.*; import timemachine.scheduler.*; import demo.*; @WebServlet("/jobs") public class Jobs extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler"); // Adding a sample job that runs every second if (request.getParameter("add") != null) { JobDef jobDef = new JobDef().setJobTaskClass(HelloJob.class); jobDef.addSchedule(Schedules.secondly(1)); scheduler.schedule(jobDef); } // Print all jobs in scheduler. List<Schedule> schedules = scheduler.findSchedules(); PrintWriter writer = response.getWriter(); writer.println("<html><table>"); writer.println("<h1>" + schedules.size() + " Scheduled Jobs Found</h1>"); for (Schedule schedule : schedules) { writer.println("<tr><td>" + schedule.toString() + ", nextRun=" + schedule.getNextRun() + "</td></tr>"); } writer.println("</table></html>"); writer.flush(); } }
$ cd $JBOSS_HOME/standalone/deployments/timemachine-demo.war/WEB-INF
$ javac -d classes -cp "$JBOSS_HOME/modules/timemachine/scheduler/main/*:
$JBOSS_HOME/modules/javax/servlet/api/main/*" Jobs.java
$ $JBOSS_HOME/bin/standalone.sh
Now visit your browser on `http://localhost:8080/timemachine-demo/jobs`. You should see zero job listed. Try again with with url `http://localhost:8080/timemachine-demo/jobs?add` should create a new job.
There are a lot more you can do with the TimeMachine Scheduler. Please visit the [project documentations] for details. If you want to further explore above examples with JBossAS, such as with Hibernate data store etc, you may see a more complete here .