How to Create a Scheduler Module in a Java EE 6 Application with TimerService
Join the DZone community and get the full member experience.
Join For FreeMany a time, in a Java EE application, besides the user-triggered transactions via the UI (e.g. from the JSF), there's a need for a mechanism to execute long running jobs triggered over time, e.g., batch jobs. Although in the EJB specs there's a Timer service, where Session Beans can be scheduled to run at intervals through annotations as well as programmatically, the schedule and intervals to execute the jobs have to be pre-determined during development time and Glassfish does not provide the framework and the means to do that out-of-the-box. So it is left to the developer to code that functionality or to choose a 3rd party product to do that.
In one of my previous projects using a different application server, I implemented a scheduler module for the application. So with that experience, I will discuss in this article how to create a simple scheduler called SchedulerApp in NetBeans IDE 6.8 that can be deployed in Glassfish v3. The example comes with a framework and the JSF2 PrimeFaces-based UI to schedule and manage (CRUD) your batch jobs implemented by Stateless Session Beans without having to pre-determine the time and interval to execute them during development time. Below is the Class Diagram to give you an overview of the application:
Through this exercise, I also hope that you will have a better understanding of the Timer Service in the EJB specs and how you can use it in your projects.
Note: If you cannot get your copy running, not to worry, you can get a working copy here.
Tutorial Requirements
Before we proceed, make sure you review the requirements in this section.
Prerequisites
This tutorial assumes that you have some basic knowledge of, or programming experience with, the following technologies.
- JavaServer Faces (JSF) with Facelets
- Enterprise Java Beans (EJB) 3/3.1 esp. the Timer Service
- Basic knowledge of using NetBeans IDE will help to reduce the time required to do this tutorial
Software needed for this Tutorial
Before you begin, you need to download and install the following software on your computer:
- NetBeans IDE 6.8 (Java pack), http://www.netbeans.org
- Glassfish Enterprise Server v3, https://glassfish.dev.java.net
- PrimeFaces Component Library, http://www.primefaces.org
Notes:
- The Glassfish Enterprise Server is included in the Java pack of NetBeans IDE, however, Glassfish can be installed separately from the IDE and added later into Servers services in the IDE.
- A copy of the working solution is included here if needed.
Creating the Enterprise Projects
The approach for developing the demo app, SchedulerApp, will be from the back end, i.e., the artifacts and services needed by the front-end UI will be created first, then working forward to the User Interface, i.e., the Ajax-based Web UI will be done last.
The first step in creating the application is to create the necessary projects in NetBeans IDE.
- Choose "File > New Project" to open the New Project Wizard. Under Categories, select Java EE; under Projects select Enterprise Application. Click Next.
- Select the project location and name the project, SchedulerApp, and click Next.
- Select the installed Glassfish v3 as the server, and Java EE 6 as the Java EE Version, and click Finish.
The above steps will create 3 projects, namely SchedulerApp (Enterprise Application project), SchedulerApp-ejb (EJB project), and SchedulerApp-war (Web project).
Creating the Session Beans
Before creating the necessary session bean classes, let's look at one of the main classes, JobInfo, which will be heavily used in the application both at the front-end and back. Basically this is a Value Object class that stores information required to configure the timer. Below is an abstract of the class:
package com.schedulerapp.common;
Notice the class holds the information about the job and its schedule. Create the above class in the EJB project, SchedulerApp-ejb with the package name, com.schedulerapp.common.
public class JobInfo implements java.io.Serializable
{
private static SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
private static SimpleDateFormat sdf2 = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
private String jobId;
private String jobName;
private String jobClassName;
private String description;
//Details required by the SchedulerExpression
private Date startDate;
private Date endDate;
private String second;
private String minute;
private String hour;
private String dayOfWeek;
private String dayOfMonth;
private String month;
private String year;
private Date nextTimeout;
public JobInfo()
{
this("<Job ID>", "<Job Name>", "java:module/");
}
public JobInfo(String jobId, String jobName, String jobClassName)
{
this.jobId = jobId;
this.jobName = jobName;
this.jobClassName = jobClassName;
this.description = "";
//Default values, everyday midnight
this.startDate = new Date();
this.endDate = null;
this.second = "0";
this.minute = "0";
this.hour = "0";
this.dayOfMonth = "*"; //Every Day
this.month = "*"; //Every Month
this.year = "*"; //Every Year
this.dayOfWeek = "*"; //Every Day of Week (Sun-Sat)
}
//Getter and Setter methods for the above attributes...
/*
* Expression of the schedule set in the object
*/
public String getExpression()
{
return "sec=" + second + ";min=" + minute + ";hour=" + hour
+ ";dayOfMonth=" + dayOfMonth + ";month=" + month + ";year=" + year
+ ";dayOfWeek=" + dayOfWeek;
}
@Override
public boolean equals(Object anotherObj)
{
if (anotherObj instanceof JobInfo)
{
return jobId.equals(((JobInfo) anotherObj).jobId);
}
return false;
}
@Override
public String toString()
{
return jobId + "-" + jobName + "-" + jobClassName;
}
}
After creating this class, we are ready to create the session beans.
Creating the BatchJob Session Beans
In this demo, we will be creating THREE batch jobs, namely: BatchJobA, BatchJobB and BatchJobC, where each is a Stateless Session Bean that implements a Local Interface, BatchJobInterface. The Interface will have a method, executeJob(javax.ejb.Timer timer), so each of the batch job session bean will need to implement it and this becomes the starting point for the batch jobs. Let's proceed to create them and you will see what I mean.
- In the Projects window, right-click on the SchedulerApp-ejb project and select "New > Session Bean..."
- In the New Session Bean dialog, specify the EJB Name as BatchJobA, the package as "com.schedulerapp.batchjob", Session Type as Stateless and select Local for Create Interface option
Notice 2 files are created: BatchJobA (Implementation class) and BatchJobALocal (Local Interface). Here I want to rename the Interface so that it has a generic name like BatchJobInterface - In the project view, navigate to the BatchJobALocal file. Right-click on the item and select "Refactor > Rename...", and change the name to BatchJobInterface.
- Open the renamed file, BatchJobInterface in the editor, and add the method:
@Local
Notice the file, BatchJobA becomes errorneous after the above is performed.
public interface BatchJobInterface
{
public void executeJob(javax.ejb.Timer timer);
} - Open the file, BatchJobA and you should see the error hint (lightbulb with exclamation icon) on the left side of the editor. Click on the icon and select "Implement all abstract methods" and edit the file so that it looks like this:
@Stateless
As you can see, the executeJob method does nothing but just sleeps for 30 sec to simulate a long running job. And because of that, it is made an asynchronous method thru the @Asynchronous annotation so that it doesn't block the calling Session Bean. Notice also that the JobInfo object is extracted from the Timer object so that you have the information to execute your job. We will see later how the JobInfo object got into the Timer object.
public class BatchJobA implements BatchJobInterface
{
static Logger logger = Logger.getLogger("BatchJobA");
@Asynchronous
public void executeJob(Timer timer)
{
logger.info("Start of BatchJobA at " + new Date() + "...");
JobInfo jobInfo = (JobInfo) timer.getInfo();
try
{
logger.info("Running job: " + jobInfo);
Thread.sleep(30000); //Sleep for 30 seconds
}
catch (InterruptedException ex)
{
}
logger.info("End of BatchJobA at " + new Date());
}
}
We will next create the other 2 batch job session beans: BatchJobA and BatchJobB using the Copy/Paste and Refactor features of NB6.8. - In the project view, navigate to the file, BatchJobA. Right-click on the item and select "Copy"
- In the same view, right-click the package, "com.schedulerapp.batchjob" and select "Paste > Refactor Copy..."
- In the Copy Class dialog, enter "BatchJobB" for the New Name field and click on the Refactor button.
Notice the new Session Bean, BatchJobB is created with a few easy clicks of a button. The only thing to change in the new class is the print statements, where "BatchJobA" will be changed to "BatchJobB". - Repeat the above steps to create BatchJobC session bean.
So we now have THREE batch job session beans: BatchJobA, BatchJobB and BatchJobC that implements the Local Interface, BatchJobInterface. We will next create the last Session Bean for this project.
Creating the Job Session Bean
Here, we will create the Job Session Bean whose main responsibility is to provide the necessary services to the front-end UI to manage (CRUD) the jobs and also provide the timeout method for the TimerService.
- In the Projects window, right-click on the SchedulerApp-ejb project and select "New > Session Bean..."
- In the New Session Bean dialog, specify the EJB Name as JobSessionBean, the package as "com.schedulerapp.ejb", Session Type as Stateless and leave Create Interface unchecked, i.e. no Interface (New in EJB 3.1), and click Finish.
- Open the newly created file, JobSessionBean in the editor and edit the content so that it looks like the following:
@Stateless
Take note of the followings in the above code:
@LocalBean
public class JobSessionBean
{
@Resource
TimerService timerService; //Resource Injection
static Logger logger = Logger.getLogger("JobSessionBean");
/*
* Callback method for the timers. Calls the corresponding Batch Job Session Bean based on the JobInfo
* bounded to the timer
*/
@Timeout
public void timeout(Timer timer)
{
System.out.println("###Timer <" + timer.getInfo() + "> timeout at " + new Date());
try
{
JobInfo jobInfo = (JobInfo) timer.getInfo();
BatchJobInterface batchJob = (BatchJobInterface) InitialContext.doLookup(
jobInfo.getJobClassName());
batchJob.executeJob(timer); //Asynchronous method
}
catch (NamingException ex)
{
logger.log(Level.SEVERE, null, ex);
}
catch (Exception ex1)
{
logger.severe("Exception caught: " + ex1);
}
}
/*
* Returns the Timer object based on the given JobInfo
*/
private Timer getTimer(JobInfo jobInfo)
{
Collection<Timer> timers = timerService.getTimers();
for (Timer t : timers)
{
if (jobInfo.equals((JobInfo) t.getInfo()))
{
return t;
}
}
return null;
}
/*
* Creates a timer based on the information in the JobInfo
*/
public JobInfo createJob(JobInfo jobInfo)
throws Exception
{
//Check for duplicates
if (getTimer(jobInfo) != null)
{
throw new DuplicateKeyException("Job with the ID already exist!");
}
TimerConfig timerAConf = new TimerConfig(jobInfo, true);
ScheduleExpression schedExp = new ScheduleExpression();
schedExp.start(jobInfo.getStartDate());
schedExp.end(jobInfo.getEndDate());
schedExp.second(jobInfo.getSecond());
schedExp.minute(jobInfo.getMinute());
schedExp.hour(jobInfo.getHour());
schedExp.dayOfMonth(jobInfo.getDayOfMonth());
schedExp.month(jobInfo.getMonth());
schedExp.year(jobInfo.getYear());
schedExp.dayOfWeek(jobInfo.getDayOfWeek());
logger.info("### Scheduler expr: " + schedExp.toString());
Timer newTimer = timerService.createCalendarTimer(schedExp, timerAConf);
logger.info("New timer created: " + newTimer.getInfo());
jobInfo.setNextTimeout(newTimer.getNextTimeout());
return jobInfo;
}
/*
* Returns a list of JobInfo for the active timers
*/
public List<JobInfo> getJobList()
{
logger.info("getJobList() called!!!");
ArrayList<JobInfo> jobList = new ArrayList<JobInfo>();
Collection<Timer> timers = timerService.getTimers();
for (Timer t : timers)
{
JobInfo jobInfo = (JobInfo) t.getInfo();
jobInfo.setNextTimeout(t.getNextTimeout());
jobList.add(jobInfo);
}
return jobList;
}
/*
* Returns the updated JobInfo from the timer
*/
public JobInfo getJobInfo(JobInfo jobInfo)
{
Timer t = getTimer(jobInfo);
if (t != null)
{
JobInfo j = (JobInfo) t.getInfo();
j.setNextTimeout(t.getNextTimeout());
return j;
}
return null;
}
/*
* Updates a timer with the given JobInfo
*/
public JobInfo updateJob(JobInfo jobInfo)
throws Exception
{
Timer t = getTimer(jobInfo);
if (t != null)
{
logger.info("Removing timer: " + t.getInfo());
t.cancel();
return createJob(jobInfo);
}
return null;
}
/*
* Remove a timer with the given JobInfo
*/
public void deleteJob(JobInfo jobInfo)
{
Timer t = getTimer(jobInfo);
if (t != null)
{
t.cancel();
}
}
}- Timer Service is made available thru Resource Injection near the top of the class
- The callback method for the timers created is timeout thru the use of the @Timeout annotation
- Notice how the JobInfo object gets into the timer thru the TimerConfig object in the createJob method
- Notice how the Batch Job session beans are being lookup and accessed in the timeout method. The job class name will be the Portable JNDI name provided by the user in the UI later
At this point, we are done with the EJB project, and will now move on to the Web project.
Creating the Web UI using JSF 2.0 with PrimeFaces
At the time of writing this tutorial, there are not many choices of Ajax-based frameworks that works with JSF 2.0 as it is still quite new. But I have found PrimeFaces to be the most complete and suitable for this demo as it has implemented the dataTable UI component and it seems to be the easiest to integrate into the NetBeans IDE.
Preparing the Web project to use JSF 2.0 and PrimeFaces
Before creating the web pages, ensure the JavaServer Faces framework is added to the Web project, SchedulerApp-war.
- In the Project view, right-click on the Web project, SchedulerApp-war, and select Properties (last item).
- Under the Categories items, select Frameworks, and ensure the JavaServer Faces is added to the Used Frameworks list:
Before we are able to use PrimeFaces components in our facelets, we need to include its library in NetBeans IDE and set up a few things.
- Download the PrimeFaces library (primefaces-2.0.0.RC.jar) from http://www.primefaces.org/downloads.html [13] and store it somewhere on the local disk.
- To allow future projects to use PrimeFaces, I chose to create a Global library in NetBeans for PrimeFaces.
- Select "Tools > Libraries" from the NetBeans IDE main menu.
- In the Library Manager dialog, choose "New Library" and provide a name for the library, e.g. "PrimeFaces2".
- With the new "PrimeFaces2" library selected, click on the "Add JAR/Folder..." button and select the jar file that was downloaded earlier and click OK to complete:
- Next, we need to add the newly created library, PrimeFaces2 to the Web project:
- Select the Web project, SchedulerApp-war, from the Project window, right-click and select "Properties".
- Under the Libraries category, click on the "Add Library..." button (on the right), and choose the PrimeFaces2 library and click OK to complete:
- Because we will be using Facelets in our demo, we will update the XHTML template in NetBeans so that all the XHTML files created subsequently will already have the required namespaces and resources needed for the development.
- Choose "Tools > Templates" from the NetBeans menu.
- In the Template Manager dialog, select "Web > XHTML" and click the "Open in Editor" button.
- Edit the content of the file so that it looks like this:
<?xml version="1.0" encoding="${encoding}"?>
<#assign licenseFirst = "<!--">
<#assign licensePrefix = "">
<#assign licenseLast = "-->">
<#include "../Licenses/license-${project.license}.txt">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.prime.com.tr/ui">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=${encoding}"/>
<title>TODO supply a title</title>
<p:resources />
</h:head>
<h:body>
<p>
TODO write content
</p>
</h:body>
</html> - Lastly, we need to add the following statements in the web.xml file of the Web project for the PrimeFaces components to work properly:
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Resource Servlet</servlet-name>
<servlet-class>
org.primefaces.resource.ResourceServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Resource Servlet</servlet-name>
<url-pattern>/primefaces_resource/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>com.sun.faces.allowTextChildren</param-name>
<param-value>true</param-value>
</context-param>
At this point, we are done setting up and configuring the environment for PrimeFaces to work in NetBeans. In the sections below, we will create the JSF pages to present the screens to perform the CRUD functions. To achieve this, we will be creating THREE web pages:
- JobList - listing of all the active Jobs/Timers created in a tabular form
- JobDetails - view/update/delete the selected Job
- JobNew - create a new Job
Creating the Backing Beans for the JSF pages
Before creating the actual JSF pages, we first need to create the backing beans that provides the properties and action handlers for the JSF pages (XHTML). Here we will create TWO backing beans:
- JobList - RequestScoped backing bean for the Job Listing page
- JobMBean - SessionScoped backing bean for the rest of the JSF pages
Steps to create the beans:
- In the Project view, right-click on the Web project, SchedulerApp-war, and select "New > JSF Managed Bean...", specify JobList as the Class Name, "com.schedulerapp.web" as the Package Name, and the scope to be request
- Repeat the steps to create the second backing bean, name it JobMBean and set the scope to be session instead.
- Edit the class, JobList, so that it looks like this:
@ManagedBean(name = "JobList")
@RequestScoped
public class JobList implements java.io.Serializable
{
@EJB
private JobSessionBean jobSessionBean;
private List<JobInfo> jobList = null;
/** Creates a new instance of JobList */
public JobList()
{
}
@PostConstruct
public void initialize()
{
jobList = jobSessionBean.getJobList();
}
/*
* Returns a list of active Jobs/Timers
*/
public List<JobInfo> getJobs()
{
return jobList;
}
} - Edit the class, JobMBean, so that it looks like this:
@ManagedBean(name = "JobMBean")
@SessionScoped
public class JobMBean implements java.io.Serializable
{
@EJB
private JobSessionBean jobSessionBean;
private JobInfo selectedJob;
private JobInfo newJob;
/** Creates a new instance of JobMBean */
public JobMBean()
{
}
/*
* Getter method for the newJob property
*/
public JobInfo getNewJob()
{
return newJob;
}
/*
* Setter method for the newJob property
*/
public void setNewJob(JobInfo newJob)
{
this.newJob = newJob;
}
/*
* Getter method for the selectedJob property
*/
public JobInfo getSelectedJob()
{
return selectedJob;
}
/*
* Setter method for the selectedJob property
*/
public String setSelectedJob(JobInfo selectedJob)
{
this.selectedJob = jobSessionBean.getJobInfo(selectedJob);
return "JobDetails";
}
/*
* Action handler for back to Listing Page
*/
public String gotoListing()
{
return "JobList";
}
/*
* Action handler for New Job button
*/
public String gotoNew()
{
System.out.println("gotoNew() called!!!");
newJob = new JobInfo();
return "JobNew";
}
/*
* Action handler for Duplicate button in the Details page
*/
public String duplicateJob()
{
newJob = selectedJob;
newJob.setJobId("<Job ID>");
return "JobNew";
}
/*
* Action handler for Update button in the Details page
*/
public String updateJob()
{
FacesContext context = FacesContext.getCurrentInstance();
try
{
selectedJob = jobSessionBean.updateJob(selectedJob);
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
"Success", "Job successfully updated!"));
}
catch (Exception ex)
{
Logger.getLogger(JobMBean.class.getName()).log(Level.SEVERE, null, ex);
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Failed", ex.getCause().getMessage()));
}
return null;
}
/*
* Action handler for Delete button in the Details page
*/
public String deleteJob()
{
jobSessionBean.deleteJob(selectedJob);
return "JobList";
}
/*
* Action handler for Create button in the New page
*/
public String createJob()
{
FacesContext context = FacesContext.getCurrentInstance();
try
{
selectedJob = jobSessionBean.createJob(newJob);
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
"Sucess", "Job successfully created!"));
return "JobDetails";
}
catch (Exception ex)
{
Logger.getLogger(JobMBean.class.getName()).log(Level.SEVERE, null, ex);
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Failed", ex.getCause().getMessage()));
}
return null;
}
}
Creating the JSF pages
Finally, we are ready to create the THREE JSF pages: JobList, JobDetails and JobNew.
- In the Project view, right-click on the Web project, SchedulerApp-war, and select "New > XHTML...", specify JobList as the File Name.
Note: If the item "XHTML..." doesn't appear in your menu list, select "New > Others..." instead, then in the New File dialog, select Web under Categories and you should be able to see the XHTML file type on the right.
- Repeat the above step for JobDetails and JobNew.
- Edit the file, JobList.xhtml to look like this:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.prime.com.tr/ui">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Job List</title>
<p:resources />
</h:head>
<h:body>
<h2>Job List</h2>
<h:form>
<p:dataTable value="#{JobList.jobs}" var="item" paginator="true"
rows="10" id="jobList">
<p:column>
<f:facet name="header">
<h:outputText value="Job ID"/>
</f:facet>
<h:commandLink action="#{JobMBean.setSelectedJob(item)}" value="#{item.jobId}"/>
</p:column>
<p:column>
<f:facet name="header">
<h:outputText value="Name"/>
</f:facet>
<h:outputText value="#{item.jobName}"/>
</p:column>
<p:column>
<f:facet name="header">
<h:outputText value="Class"/>
</f:facet>
<h:outputText value="#{item.jobClassName}"/>
</p:column>
<p:column>
<f:facet name="header">
<h:outputText value="Start Date"/>
</f:facet>
<h:outputText value="#{item.startDateStr}"/>
</p:column>
<p:column>
<f:facet name="header">
<h:outputText value="End Date"/>
</f:facet>
<h:outputText value="#{item.endDateStr}"/>
</p:column>
<p:column>
<f:facet name="header">
<h:outputText value="Next Timeout"/>
</f:facet>
<h:outputText value="#{item.nextTimeoutStr}"/>
</p:column>
<p:column>
<f:facet name="header">
<h:outputText value="Expression"/>
</f:facet>
<h:outputText value="#{item.expression}"/>
</p:column>
</p:dataTable>
<br/>
<h:commandButton action="#{JobMBean.gotoNew}" value="New Job"/>
</h:form>
</h:body>
</html> - Edit the file, JobDetails.xhtml to look like this:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.prime.com.tr/ui">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Job Details</title>
<p:resources />
</h:head>
<h:body>
<h2><h:outputText value="Job Details"/></h2>
<h:form>
<h:panelGrid columns="2" bgcolor="#eff5fa">
<h:outputText value="Job ID:"/>
<h:inputText id="jobId" value="#{JobMBean.selectedJob.jobId}" readonly="true"/>
<h:outputText value="Job Name:"/>
<h:inputText id="jobName" value="#{JobMBean.selectedJob.jobName}"/>
<h:outputText value="Job Class Name:"/>
<h:inputText id="jobClass" value="#{JobMBean.selectedJob.jobClassName}" size="40"/>
<h:outputText value="Start Date:"/>
<p:calendar id="startDate" value="#{JobMBean.selectedJob.startDate}"/>
<h:outputText value="End Date:"/>
<p:calendar id="endDate" value="#{JobMBean.selectedJob.endDate}"/>
<h:outputText value="Second:"/>
<h:inputText id="second" value="#{JobMBean.selectedJob.second}"/>
<h:outputText value="Minute:"/>
<h:inputText id="minute" value="#{JobMBean.selectedJob.minute}"/>
<h:outputText value="Hour:"/>
<h:inputText id="hour" value="#{JobMBean.selectedJob.hour}"/>
<h:outputText value="Day of Month:"/>
<h:inputText id="dayOfMonth" value="#{JobMBean.selectedJob.dayOfMonth}"/>
<h:outputText value="Month:"/>
<h:inputText id="month" value="#{JobMBean.selectedJob.month}"/>
<h:outputText value="Year:"/>
<h:inputText id="year" value="#{JobMBean.selectedJob.year}"/>
<h:outputText value="Day Of Week:"/>
<h:inputText id="dayOfWeek" value="#{JobMBean.selectedJob.dayOfWeek}"/>
<h:outputText value="Job Description:"/>
<h:inputTextarea cols="40" rows="4" id="description" value="#{JobMBean.selectedJob.description}"/>
<h:outputText value="Next Timeout:"/>
<h:inputText id="nextTimeout" value="#{JobMBean.selectedJob.nextTimeoutStr}" readonly="true"/>
</h:panelGrid>
<h:commandButton id="back" value="Back" action="#{JobMBean.gotoListing}"/>
<h:commandButton id="update" value="Update" action="#{JobMBean.updateJob}"/>
<h:commandButton id="delete" value="Delete" action="#{JobMBean.deleteJob}"/>
<h:commandButton id="duplicate" value="Duplicate" action="#{JobMBean.duplicateJob}"/>
<h:outputLink id="help" value="http://java.sun.com/javaee/6/docs/tutorial/doc/bnboy.html" target="help">Help</h:outputLink>
</h:form>
<p:messages showDetail="true"/>
</h:body>
</html> - Edit the file, JobNew.xhtml to look like this:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.prime.com.tr/ui">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>New Job Details</title>
<p:resources />
</h:head>
<h:body>
<h2><h:outputText value="New Job Details"/></h2>
<h:form>
<h:panelGrid columns="2" bgcolor="#eff5fa" >
<h:outputText value="Job ID:"/>
<h:inputText id="jobId" value="#{JobMBean.newJob.jobId}"/>
<h:outputText value="Job Name:"/>
<h:inputText id="jobName" value="#{JobMBean.newJob.jobName}"/>
<h:outputText value="Job Class Name:"/>
<h:inputText id="jobClass" value="#{JobMBean.newJob.jobClassName}" size="40"/>
<h:outputText value="Start Date:"/>
<p:calendar id="startDate" value="#{JobMBean.newJob.startDate}"/>
<h:outputText value="End Date:"/>
<p:calendar id="endDate" value="#{JobMBean.newJob.endDate}" navigator="true"/>
<h:outputText value="Second:"/>
<h:inputText id="second" value="#{JobMBean.newJob.second}"/>
<h:outputText value="Minute:"/>
<h:inputText id="minute" value="#{JobMBean.newJob.minute}"/>
<h:outputText value="Hour:"/>
<h:inputText id="hour" value="#{JobMBean.newJob.hour}"/>
<h:outputText value="Day of Month:"/>
<h:inputText id="dayOfMonth" value="#{JobMBean.newJob.dayOfMonth}"/>
<h:outputText value="Month:"/>
<h:inputText id="month" value="#{JobMBean.newJob.month}"/>
<h:outputText value="Year:"/>
<h:inputText id="year" value="#{JobMBean.newJob.year}"/>
<h:outputText value="Day Of Week:"/>
<h:inputText id="dayOfWeek" value="#{JobMBean.newJob.dayOfWeek}"/>
<h:outputText value="Job Description:"/>
<h:inputTextarea cols="40" rows="4" id="description" value="#{JobMBean.newJob.description}"/>
</h:panelGrid>
<h:commandButton id="back" value="Back" action="#{JobMBean.gotoListing}"/>
<h:commandButton id="create" value="Create" action="#{JobMBean.createJob}"/>
<h:outputLink id="help" value="http://java.sun.com/javaee/6/docs/tutorial/doc/bnboy.html" target="help">Help</h:outputLink>
</h:form>
<p:messages showDetail="true"/>
</h:body>
</html>
Testing the application
Here, we will do a simple test to verify that the application is working. We will schedule 3 jobs as follows:
- Job 1 - run BatchJobA every 2 minutes (just to make sure we see the job running)
- Job 2 - run BatchJobB everyday at 11pm
- Job 3 - run BatchJobC every Sunday at 1am
Steps to create the jobs:
- Go to the listing page, http://localhost:8080/SchedulerApp-war/JobList.jsf and you should see the following screen:
Click on the "New Job" button below the table. - Enter the details for Job 1 as follows and click on the "Create" button
Click on the "Duplicate" button below to create a new Job using the current information. - Enter the details for Job 2 as follows and click on the "Create" button
Click on the "Duplicate" button below to create a new Job using the current information. - Enter the details for Job 3 as follows and click on the "Create" button
At this point, we are done creating the jobs, click on the "Back" button to see the listing. - The Job List page should consists of 3 jobs that was created in the above steps
Things to Note
- The Portable JNDI syntax for accessing the Session Beans: BatchJobA, BatchJobB and BatchJobC
- The "*" in the text fields represents "Every", see Java EE 6 Tutorial for details
- You should be able see in the log file, server.log, that BatchJobA now runs every 2 minutes
- The timers(jobs) are persistent, i.e. they will survive server restarts. Try restarting ther server and view the Job list again
Try out the other functions of the CRUD and schedule your own jobs to see it in action.
Summary
Congratulations! You now have a simple scheduler to schedule your long running jobs in your application. With this framework and the GUI, you can have the flexibility and full control over the jobs you want to manage without having to pre-determine the time and interval to run them during Design and Development phase.
Although the timers are persistent, the server may remove them when changes, such as new deployments, are detected. As such, you can further extend the scheduler to persist information in the database in a more dynamic and complex environment, e.g., a cluster.
Good luck and have fun using the Scheduler. If you cannot get your copy running, not to worry, you can get a working copy here.
See Also
For other related resources, see the following:
- Develop Java EE 5 application with Visual JSF, EJB3 and JPA
- Securing Java EE 6 application with JEE Security and LDAP
- How to Create a Java EE 6 Application with JSF 2, EJB 3.1, JPA, and NetBeans IDE 6.8
Opinions expressed by DZone contributors are their own.
Comments