Job Scheduler Tutorial in Java Struts

August 19, 2013

Recently, we have released a job scheduler demo in Java Struts2 that sends email alerts to notify the scheduler users of newly created and amended tasks.

As promised, we’ve developed a coherent tutorial to the demo.
 
It provides a step-by-step description of how a full-featured job scheduling system can be implemented in Java.
 
job-scheduler-java-demo

 

As a bonus to the tutorial, we’ve attached a ready demo sample that you can download and test on your local server right now.

The introduced job scheduler provides the following functionality:

  • Multiple scheduler views including day and month views to display tasks for each employee, and  timeline/units view to see the tasks assigned to all employees;
  • A convenient mini-calendar to the left of the job scheduler for fast selection of days;
  • A login form for the Manager and Employees;
  • A checkbox list of employees under the mini calendar;
  • Different user rights: tasks can be created/modified/deleted/assigned and reassigned by manager; manager can set task priority; employees can view all tasks and change status of their own tasks;
  • Status of task execution: pending (blue)/started (orange)/ completed (green);
  • Alert to manager’s email when the status of a task has changed;
  • Alert to employee’s email when a new task has been assigned to him or the existing task has been updated;
  • Possibility to set task urgency – high, medium, low;
  • Poping up tooltips for the tasks in the timeline and month views;
  • Customizable lightbox with task details, drop down lists to select task urgency, time period and employee name;
  • Localization to 28 languages.

The following steps explain how to add the above described functionality to our web control.

Step 1. Create a New Java Project

First of all, create a new dynamic web project in Eclipse and name it  ‘JobScheduler’.

Step 2. Add Struts and Hibernate JAR-files

We’ll build a job scheduler with the use of the Struts Framework and Hibernate library. That’s why we need to add the required Struts and Hibernate jar-files.
You can find all necessary jar-dependencies at the related Struts and Hibernate websites or use the jar-files we’ve selected for this scheduler project.
Add all the available Struts and Hibernate jar-files to the WebContent/WEB-INF/lib/ and refresh the project to apply the updates.

Step 3. Configure the Job Scheduler

To initialize  DHTMLX JavaPlanner, add javaplanner-1.2.jar and mysql-connector-java-5.1.23.jar to WebContent/WEB-INF/lib/ of the project and refresh it once again.
To configure the job scheduler look and feel, copy the codebase folder to the WebContent/. It contains all necessary job scheduler images together with the source code and all available locales, so that you could change the calendar language, when required.

Step 4. Configure Struts

Create WebContent/WEB-INF/web.xml to use the Struts application:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>JavaPlanner Job Scheduling Demo</display-name>
<welcome-file-list>
<welcome-file>01_simple_init.action</welcome-file>
</welcome-file-list>

<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

</web-app>

Right-click on the folder JavaResources/src/ in the Project Explorer and add a new configuration file struts.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
	<constant name="struts.devMode" value="true" />
	<constant name="struts.action.excludePattern" value=".*/static/.*" />
	<package name="test" extends="struts-default">

		<action name="index" class="org.apache.struts.demoapp_struts.action.JobDemoAction" method="javaplanner">
			<result name="success">/article.jsp</result>
			<result name="error" type="redirectAction">login.action</result>
		</action>
		<action name="events" class="org.apache.struts.demoapp_struts.action.JobDemoAction" method="events">
			<result name="success">/data.jsp</result>
		</action>

		<action name="" class="org.apache.struts.demoapp_struts.action.JobDemoAction" method="javaplanner">
			<result name="success">/article.jsp</result>
			<result name="error" type="redirectAction">login.action</result>
		</action>
		<action name="events" class="org.apache.struts.demoapp_struts.action.JobDemoAction" method="events">
			<result name="success">/data.jsp</result>
		</action>

		<action name="login" class="org.apache.struts.demoapp_struts.action.JobDemoAction" method="login">
			<result name="success">/login.jsp</result>
			<result name="login" type="redirectAction">index.action</result>
			<result name="error">login.jsp</result>
		</action>

		<action name="logout" class="org.apache.struts.demoapp_struts.action.JobDemoAction" method="logout">
			<result name="success" type="redirectAction">index.action</result>
		</action>

	</package>
</struts>

The configuration has definitions for 4 pages:

index.action – page is used to show the scheduler
success – user is logged, calendar can be shown
error – user is not signed in, redirect to login.action
events.action – page is used to load events into calendar
login.action – page with login form
success – render login form
login – login request is sent, user is authenticated, redirect to index.action
error – login request is sent, incorrect credentials
logout.action – page for sign out, redirect to login.action

Step 5. Configure Struts Classes

Add the following packages to /JavaResources/src:

org.apache.struts.demoapp_struts.action – a package with Struts Action classes
org.apache.struts.demoapp_struts.model – a package for Model classes
org.apache.struts.demoapp_struts.util – a package for Util classes

Step 6. Setup Database and Configure Hibernate

This step covers the creation of a new database and the establishing of the database connection with Hibernate configuration file.

6.1. To begin with, we need to create ‘User’ and ‘Event’ classes in the package org.apache.struts.demoapp_struts.model.

The ‘Event.java’ class should have the following code:

package org.apache.struts.demoapp_struts.model;

import com.dhtmlx.planner.DHXEvent;

public class Event extends DHXEvent {

	public String user;
	public String status;
	public String urgency;

	public String getUser() {
		return user;
	}
	public void setUser(String user) {
		this.user = user;
	}
	public String getStatus() {
		return status;
	}
	public void setStatus(String status) {
		this.status = status;
	}
	public String getUrgency() {
		return urgency;
	}
	public void setUrgency(String urgency) {
		this.urgency = urgency;
	}
}

6.2. Now add the below code in ‘User.java’ class:

package org.apache.struts.demoapp_struts.model;

public class User {

	public Integer id;
	public String email;
	public String name;
	public String password;
	public String type;

	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}

}

6.3. Go to MySQL localhost and create a new database ‘jobscheduler’. It should contain two tables “users” and “events” – one for User and the other for Event entities. To simplify the creation of the tables, use MySQL-dump provided below.

Run the following SQL query  to create the table ‘events’:

CREATE TABLE `events` (
`event_id` int(11) NOT NULL AUTO_INCREMENT,
`text` varchar(255) DEFAULT NULL,
`start_date` datetime DEFAULT NULL,
`end_date` datetime DEFAULT NULL,
`user` int(11) DEFAULT NULL,
`urgency` varchar(255) DEFAULT NULL,
`status` varchar(255) DEFAULT NULL,
PRIMARY KEY (`event_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 

 

The table ‘users’ is created in the same way:

CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
INSERT INTO `users` VALUES ('1', 'bob@gmail.com', 'Bob Clarson', 'password', 'manager');
INSERT INTO `users` VALUES ('2', 'mary@gmail.com', 'Mary Stevenson', 'password', 'employee');
INSERT INTO `users` VALUES ('3', 'john@gmail.com', 'John Perez', 'password', 'employee');
INSERT INTO `users` VALUES ('4', 'matthew@gmail.com', 'Matthew Hill', 'password', 'employee');
INSERT INTO `users` VALUES ('5', 'tony@gmail.com', 'Tony Stark', 'password', 'employee');
INSERT INTO `users` VALUES ('6', 'dave@gmail.com', 'Dave Gart', 'password', 'employee');
INSERT INTO `users` VALUES ('7', 'joseph@gmail.com', 'Joseph Brown', 'password', 'employee');
INSERT INTO `users` VALUES ('8', 'william@gmail.com', 'William Clark', 'password', 'employee');
INSERT INTO `users` VALUES ('9', 'ethan@gmail.com', 'Ethan Wilson', 'password', 'employee');
INSERT INTO `users` VALUES ('10', 'michael@gmail.com', 'Michael Harris', 'password', 'employee');

 

Populate your calendar with data by importing job.sql from the root folder of the downloaded package to the created database.

6.4. Create Hibernate definition files for the Event and User entities. Add src/Event.hbm.xml and update it as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="org.apache.struts.demoapp_struts.model.Event" table="events">
    <id column="event_id" name="id" type="java.lang.Integer">
		<generator class="increment"/>
	</id>
	<property column="start_date" name="start_date" type="timestamp"/>
	<property column="end_date" name="end_date" type="timestamp"/>
    <property column="text" name="text" type="java.lang.String"/>
    <property column="user" name="user" type="java.lang.String"/>
    <property column="status" name="status" type="java.lang.String"/>
    <property column="urgency" name="urgency" type="java.lang.String"/>

  </class>
</hibernate-mapping>

6.5. Create src/User.hbm.xml and copy paste this code:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="org.apache.struts.demoapp_struts.model.User" table="users">
    <id column="id" name="id" type="java.lang.Integer">
		<generator class="increment"/>
	</id>
    <property column="email" name="email" type="java.lang.String"/>
    <property column="name" name="name" type="java.lang.String"/>
    <property column="password" name="password" type="java.lang.String"/>
    <property column="type" name="type" type="java.lang.String"/>

  </class>
</hibernate-mapping>

6.6. To cache the database connections, configure connection pooling using a Hibernate XML configuration file.

At first, create src/hibernate.cfg.xml with the following code:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
 "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
 "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>

		<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="connection.url">jdbc:mysql://localhost/jobscheduler</property>
		<property name="connection.username">root</property>
		<property name="connection.password"></property>

		<property name="connection.pool_size">1</property>
		<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
		<property name="current_session_context_class">thread</property>
		<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
		<property name="show_sql">true</property>
		<property name="format_sql">true</property>
		<property name="use_sql_comments">true</property>
		<property name="hbm2ddl.auto">update</property>
		<mapping class="org.apache.struts.demoapp_struts.model" file="" jar="" package="hibernate.cfg.xml" resource="Event.hbm.xml" />
		<mapping class="org.apache.struts.demoapp_struts.model" file="" jar="" package="hibernate.cfg.xml" resource="User.hbm.xml" />

	</session-factory>
</hibernate-configuration>

Now add src/c3p0.properties file to configure the connection pool:

c3p0.testConnectionOnCheckout=true

6.7. Create class HibernateUtil in package org.apache.struts.demoapp_struts.util:

package org.apache.struts.demoapp_struts.util;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {
	private static final SessionFactory sessionFactory = buildSessionFactory();
	@SuppressWarnings("deprecation")
	private static SessionFactory buildSessionFactory() {
		try {
			return new Configuration().configure().buildSessionFactory();
		}
		catch (Throwable ex) {
			System.err.println("Initial SessionFactory creation failed." + ex);
			throw new ExceptionInInitializerError(ex);
		}
	}

	public static SessionFactory getSessionFactory() {
		return sessionFactory;
	}
}

 

Step 7. Create a Controller

To render the scheduler views, create a new class ‘JobDemoAction.java’ in the package org.apache.struts.demoapp_struts.action. It contains basic methods for implementing the required functionality:

package org.apache.struts.demoapp_struts.action;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.struts.demoapp_struts.model.CustomEventsManager;
import org.apache.struts.demoapp_struts.model.User;
import org.apache.struts.demoapp_struts.util.HibernateUtil;
import org.apache.struts2.ServletActionContext;
import org.hibernate.classic.Session;
import org.hibernate.criterion.Restrictions;
import com.dhtmlx.planner.DHXPlanner;
import com.dhtmlx.planner.DHXSkin;
import com.dhtmlx.planner.controls.DHXLightboxSelect;
import com.dhtmlx.planner.controls.DHXMiniCalendar;
import com.dhtmlx.planner.controls.DHXTimelineView;
import com.dhtmlx.planner.controls.DHXUnitsView;
import com.dhtmlx.planner.controls.DHXTimelineView.RenderModes;
import com.dhtmlx.planner.controls.DHXTimelineView.XScaleUnits;
import com.dhtmlx.planner.data.DHXDataFormat;
import com.dhtmlx.planner.extensions.DHXExtension;
import com.opensymphony.xwork2.ActionSupport;
public class JobDemoAction extends ActionSupport {
private static final long serialVersionUID = 1L;
private List<User> users;
private Boolean employee;
private String username;
private String userid;
private String planner;
private String events;

public List<User> getUsers() {
return users;
}
public Boolean getEmployee() {
return employee;
}
public String getUsername() {
return username;
}
public String getUserid() {
return userid;
}
public String getPlanner() {
return planner;
}
public String getEvents() {
return events;
}
public String javaplanner() throws Exception {
return SUCCESS;
}
public String events() throws Exception {
return SUCCESS;
}
public String login() throws Exception {
return SUCCESS;
}
public String logout() throws Exception {
return SUCCESS;
}
}

 

Step 8. Create Job Scheduler Pages

The created job scheduler should include login, article, data, header and footer pages. Let’s create a view for each page.

Add login.jsp to /WebContent to display a login form that redirects you to the job scheduler:

<%@ include file="header.jsp" %>
<body>
	<div class="header">
	<div class="overlay"></div>
		<div class="text">Job Scheduler</div>
	</div>
	<div class="content">
		<form class="login-form" method="post">
			<div class="right">
				Demo logins:<br>
				bob@gmail.com/password<br/>
				mary@gmail.com/password<br/>
				john@gmail.com/password<br/>
			</div>
			<label for="email">E-mail</label>
			<input type="text" id="email" name="email" value="bob@gmail.com" />
			<div class="clear"></div>
			<label for="password">Password</label>
			<input type="password" id="password" name="password" value="password" />
			<div class="clear"></div>
			<label></label>
			<input type="submit" value="Log in" />
			<div class="clear"></div>
		</form>
	</div>
<%@ include file="footer.jsp" %>

 

Create /WebContent/article.jsp to display the scheduler page:

<%@ include file="header.jsp" %>
  <body>
    <div class="header">
      <div class="center">
        <div class="text">
          Job Scheduler
        </div>
        <div class="user-info">
          <div class="logout">
            [ 
            <a href="logout.action">
              Log off
            </a>
            ]
          </div>
          <div class="username">
            Welcome, 
            <span class="bold">
              <s:property escape="false" value="username" />
            </span>
            !
          </div>
        </div>
      </div>
    </div>
    <div class="content">
      <div class="scheduler" id="scheduler">
        <div class="leftcol">
          <div class="minical" id="minical">
          </div>
          <div class="filter" id="filter">
            <div class="f_header">
              Employee
            </div>
            <div class="f_list">
              <table id="users">
                <s:iterator value="users" status="userStatus">
                  <tr class="
<s:if test="#userStatus.even == true">
even
</s:if>
<s:else>
odd
</s:else>
">
  <td class="checkbox">
    <input type="checkbox" id="user_
<s:property value="id"/>
" checked onchange="update_config();" />
  </td>
  <td class="last">
    <label for="user_
<s:property value="id" />
">
  <s:property value="name" />
  </label>
  </td>
  </tr>
  </s:iterator>
  </table>
  </div>
  </div>
  </div>
  <div class="rightcol">
    <s:property escape="false" value="planner" />
  </div>
  </div>
  </div>
  <%@ include file="footer.jsp" %>

 

To display tasks on the page, create /WebContent/data.jsp:

<%@ taglib prefix="s" uri="/struts-tags" %>
<s:property escape="false" value="events" />

Create a header of your job scheduler by adding /WebContent/header.jsp:

<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html>
<html>
<head>
	<link rel="stylesheet" type="text/css" href="./codebase/demo.css" " />
<title>Job Scheduler in Java</title>
</head>

Add a footer to your calendar by creating /WebContent/footer.jsp.

Step 9. Add CSS Classes

Update the stylesheet file WebContent/codebase/demo.css to display your job scheduler correctly on the page:

html, body {
	margin: 0px;
	padding: 0px;
}
.center {
	width: 1100px;
	margin: 0px auto;
	position: relative;
}
.header .text {
	height: 80px;
	width: 100%;
	line-height: 80px;
	color: #48694d;
	position: relative;
	z-index: 10;
	text-align: center;
	font-family: Tahoma;
	font-size: 26px;
}
.header {
	margin-bottom: 40px;
}
.content {
	width: 100%;
	margin-bottom: 190px;
}
.scheduler {
	width: 1100px;
	height: 494px;
	margin: 0 auto;
	background-color: #ffffff;
}
.footer {
	height: 142px;
	width: 100%;
	background-color: #67846C;
	background-repeat: repeat-x;
	position: fixed;
	bottom: 0px;
	left: 0px;
}
.user-info {
	position: absolute;
	right: 10px;
	top: 37px;
	z-index: 10;
	color: #ffffff;
	font-family: Helvetica;
}
.user-info div {
	float: right;
	margin-left: 10px;
}
.user-info .username {
	font-size: 14px;
}
.user-info div,
.user-info a,
.user-info a:hover {
	font-size: 14px;
	font-family: Helvetica;
	color: #48694D;
}
.user-info .bold {
	font-weight: bold;
}
.logout {
	line-height: 13px;
}
.logout a,
.logout a:hover,
.logout a:visited {
	color: #ABD95D;
}

.clear {
	clear: both;
}
.login-form {
	display: block;
	padding: 20px;
	background-color: #e9e9e9;
	border: 1px solid #cecece;
	border-radius: 10px;
	width: 400px;
	height: 244px;
	margin: 0px auto;
}
.login-form input,
.login-form label {
	height: 34px;
	border: 1px solid #CECECE;
	font-size: 20px;
	font-family: Arial;
	padding: 5px 12px;
	margin-bottom: 10px;
	display: block;
	float: left;
}
.login-form input {
	width: 254px;
}
.login-form input[type="submit"] {
	height: 42px;
	width: 280px;
	cursor: pointer;
	background-color: #48694d;
	color: #ffffff;
}
.login-form input[type="submit"]:hover {

}
.login-form label {
	width:92px;
	line-height: 34px;
	border: none;
}

.leftcol {
	float: left;
	width: 250px;
	height: 100%;
}
.rightcol {
	float: left;
	width: 840px;
	height: 100%;
	margin-left: 5px;
}

.filter {
	background-color: #FFF;
	border: 1px solid #CECECE;
	margin-top: 5px;
	font-size: 12px;
	font-family: Arial;
	border-bottom: none;
}
.filter .f_header {
	text-align: center;
	line-height: 32px;
}
.filter table {
	width: 100%;
	margin: 0px;
	padding: 0px;
	border-collapse: collapse;
}
.minical .dhx_cal_container.dhx_mini_calendar {
	box-shadow: none;
}
.filter table tr td {
	margin: 0px;
	height: 16px;
	line-height: 16px;
	border: 1px solid #CECECE;
}
.filter table tr.odd td {
	background-color: #F0EDE7;
}
.filter table tr td.checkbox {
	width: 22px;
	padding: 4px;
	border-left: none;
}
.filter table tr td.last {
	border-right: none;
}
.filter table tr td label {
	width: 100%;
	height: 100%;
	display: block;
	line-height: 26px;
	cursor: pointer;
	padding: 0px 6px;
}
.right {
	text-align: right;
	margin-bottom: 10px;
	margin-right: 7px;
}

.dhx_cal_date {
	padding-left: 80px;
}

.dhx_cal_container {
	border: 1px solid #CECECE;
}

 

Step 10. User Authentication

Let’s create a simple user authentication system.
NOTE This sign in system was designed for demo purposes only. Don’t use it in real projects.

Open JobDemoAction.java class to make the changes.

10.1. Check if a user is authenticated in the method javaplanner():

public String javaplanner() throws Exception {
		HttpServletRequest request = ServletActionContext.getRequest();
		HttpSession session = request.getSession(true);
		if (session.getAttribute("email") == null) {
			return ERROR;
		}
   	username = session.getAttribute("name").toString();

		return SUCCESS;
	}

10.2. Check user credentials and set flag in session, if they are valid in the method login():

public String login() throws Exception {
  HttpServletRequest request = ServletActionContext.getRequest();
  HttpSession session = request.getSession(true);
    if (session.getAttribute("email") != null)
return LOGIN;
  String email = request.getParameter("email");
  String password = request.getParameter("password");
    if (email == null || password == null)
return SUCCESS;
  Session s = HibernateUtil.getSessionFactory().openSession();
    List
    <User>
      users = s.createCriteria(User.class)
      .add(Restrictions.eq("email", email))
      .add(Restrictions.eq("password", password))
      .list();
    if (users.size() == 0)
  return ERROR;
  session.setAttribute("id", users.get(0).getId());
  session.setAttribute("email", users.get(0).getEmail());
  session.setAttribute("name", users.get(0).getName());
  session.setAttribute("type", users.get(0).getType());
  return LOGIN;
  }

10.3. Clear session to sign out in the method logout():

public String logout() throws Exception {
  HttpServletRequest request = ServletActionContext.getRequest();
  HttpSession session = request.getSession(true);
    session.removeAttribute("id");
    session.removeAttribute("email");
    session.removeAttribute("name");
    session.removeAttribute("type");
return SUCCESS;
}

Now you can test the created login form in your browser http://localhost:8080/JobScheduler/. It should look like this one:

login-form-java-job-scheduler

 At this stage we have only a login form ready. Let’s configure the job scheduler accordingly to make it viewable in a browser.

Step 11. Create the Job Scheduler Model

Create  a new class CustomEventsManager.java in org.apache.struts.demoapp_struts.model:

package org.apache.struts.demoapp_struts.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.struts.demoapp_struts.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import com.dhtmlx.planner.DHXEv;
import com.dhtmlx.planner.DHXEventsManager;
import com.dhtmlx.planner.DHXStatus;
import com.dhtmlx.planner.data.DHXCollection;

public class CustomEventsManager extends DHXEventsManager {

	public CustomEventsManager(HttpServletRequest request) {
		super(request);
	}

	public Iterable<DHXEv> getEvents() {
		Session session = HibernateUtil.getSessionFactory().openSession();
		List<DHXEv> evs = new ArrayList<DHXEv>();
		try {
			session = HibernateUtil.getSessionFactory().openSession();
			evs = session.createCriteria(Event.class).list();
		} catch (RuntimeException e) {
			e.printStackTrace();
		} finally{
			session.flush();
			session.close();
		}

    	return evs;
	}

	@Override
	public DHXStatus saveEvent(DHXEv event, DHXStatus status) {
		HttpSession s = getRequest().getSession(true);
		Boolean employee = !s.getAttribute("type").toString().equals("manager");

		Session session = HibernateUtil.getSessionFactory().openSession();
		try {
			session = HibernateUtil.getSessionFactory().openSession();
			session.beginTransaction();

			if (status == DHXStatus.UPDATE) {
				session.update(event);
			} else if (status == DHXStatus.DELETE) {
				if (employee) return DHXStatus.ERROR;
				session.delete(event);
			} else if (status == DHXStatus.INSERT) {
				if (employee) return DHXStatus.ERROR;
				session.save(event);
			}
			session.getTransaction().commit();
		} catch (RuntimeException e) {
			e.printStackTrace();
		} finally{
			session.flush();
			session.close();
		}
		return status;
	}

	@Override
	public DHXEv createEvent(String id, DHXStatus status) {
		return new Event();
	}

	@Override
	public HashMap<String, Iterable<DHXCollection>> getCollections() {
		List<User> users = getUsers();
		ArrayList<DHXCollection> users_list = new ArrayList<DHXCollection>();
		for (int i = 0; i < users.size(); i++) {
			users_list.add(new DHXCollection(users.get(i).getId().toString(), users.get(i).getName()));
		}

		HashMap<String,Iterable<DHXCollection>> c = new HashMap<String,Iterable<DHXCollection>>();
		c.put("users", users_list);

		ArrayList<DHXCollection> status = new ArrayList<DHXCollection>();
		status.add(new DHXCollection("pending", "Pending"));
		status.add(new DHXCollection("started", "Started"));
		status.add(new DHXCollection("completed", "Completed"));
		c.put("status", status);

		ArrayList<DHXCollection> urgency = new ArrayList<DHXCollection>();
		urgency.add(new DHXCollection("low", "Low"));
		urgency.add(new DHXCollection("medium", "Medium"));
		urgency.add(new DHXCollection("high", "High"));
		c.put("urgency", urgency);

		return c;
	}

	public List<User> getUsers() {
		Session session = HibernateUtil.getSessionFactory().openSession();
		List<User> users = new ArrayList<User>();
		try {
			session = HibernateUtil.getSessionFactory().openSession();
			users = session.createCriteria(User.class).add(Restrictions.eq("type", "employee")).list();
		} catch (RuntimeException e) {
			e.printStackTrace();
		} finally{
			session.flush();
			session.close();
		}
		return users;
	}

	public User getUser(String id) {
		Session session = HibernateUtil.getSessionFactory().openSession();
		List<User> users = new ArrayList<User>();
		try {
			session = HibernateUtil.getSessionFactory().openSession();
			users = session.createCriteria(User.class).add(Restrictions.eq("id", Integer.parseInt(id))).list();
		} catch (RuntimeException e) {
			e.printStackTrace();
		} finally{
			session.flush();
			session.close();
		}
		if (users.size() > 0)
			return users.get(0);
		else
			return null;
	}
}

Step 12. JavaPlanner Initialization

To initialize JavaPlanner, update JobDemoAction.java as follows:

DHXPlanner planner = new DHXPlanner("./codebase/", DHXSkin.TERRACE);
   	planner.setInitialDate(2013, 7, 15);
   	planner.setInitialView("units");
   	planner.config.setScrollHour(8);
   	planner.setWidth(844);
   	planner.setHeight(518);
   	planner.load("events", DHXDataFormat.JSON);
   	planner.data.dataprocessor.setURL("events");
   	planner.config.setDetailsOnCreate(true);
   	planner.config.setFirstHour(10);
   	planner.config.setLastHour(20);
   	planner.extensions.add(DHXExtension.TOOLTIP);

   	// create units view
   	DHXUnitsView units = new DHXUnitsView("units", "user", "Units");
   	units.setServerListLink("users");
   	units.setSkipIncorrect(true);
   	planner.views.add(units);

   	// create timeline view
   	DHXTimelineView timeline = new DHXTimelineView("timeline", "user", "Timeline");
   	timeline.setRenderMode(RenderModes.BAR);
   	timeline.setXScaleUnit(XScaleUnits.MINUTE);
   	timeline.setXStep(30);
   	timeline.setXSize(18);
   	timeline.setXStart(20);
   	timeline.setXLength(48);
   	timeline.setServerList("users");
   	planner.views.add(timeline);

		// adds sections in lightbox
   		DHXLightboxSelect sel = new DHXLightboxSelect("user", "Owner");
   	sel.setServerList("users");
   	planner.lightbox.add(sel);

   	DHXLightboxSelect status = new DHXLightboxSelect("status", "Status");
   	status.setServerList("status");
   	planner.lightbox.add(status);

   	DHXLightboxSelect urgency = new DHXLightboxSelect("urgency", "Urgency");
   	urgency.setServerList("urgency");
   		planner.lightbox.add(urgency);

   		// creates mini-calendar
   	DHXMiniCalendar cal = new DHXMiniCalendar("minical");
   	cal.setNavigation(true);
   	planner.calendars.add(cal);

   	users = (new CustomEventsManager(request)).getUsers();
   	employee = !session.getAttribute("type").toString().equals("manager");
   	username = session.getAttribute("name").toString();
   	userid = session.getAttribute("id").toString();
   	this.planner = planner.render();
		return SUCCESS;
	}

Add the following code to render events:

	public String events() throws Exception {
			CustomEventsManager evs = new CustomEventsManager(ServletActionContext.getRequest());
			events = evs.run();
			return SUCCESS;
}

The full code should look like this:

package org.apache.struts.demoapp_struts.action;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.struts.demoapp_struts.model.CustomEventsManager;
import org.apache.struts.demoapp_struts.model.User;
import org.apache.struts.demoapp_struts.util.HibernateUtil;
import org.apache.struts2.ServletActionContext;
import org.hibernate.classic.Session;
import org.hibernate.criterion.Restrictions;

import com.dhtmlx.planner.DHXPlanner;
import com.dhtmlx.planner.DHXSkin;
import com.dhtmlx.planner.controls.DHXLightboxSelect;
import com.dhtmlx.planner.controls.DHXMiniCalendar;
import com.dhtmlx.planner.controls.DHXTimelineView;
import com.dhtmlx.planner.controls.DHXUnitsView;
import com.dhtmlx.planner.controls.DHXTimelineView.RenderModes;
import com.dhtmlx.planner.controls.DHXTimelineView.XScaleUnits;
import com.dhtmlx.planner.data.DHXDataFormat;
import com.dhtmlx.planner.extensions.DHXExtension;
import com.opensymphony.xwork2.ActionSupport;

public class JobDemoAction extends ActionSupport {

	private static final long serialVersionUID = 1L;
	private List<User> users;
	private Boolean employee;
	private String username;
	private String userid;
	private String planner;
	private String events;

	public List<User> getUsers() {
		return users;
	}
	public Boolean getEmployee() {
		return employee;
	}
	public String getUsername() {
		return username;
	}
	public String getUserid() {
		return userid;
	}
	public String getPlanner() {
		return planner;
	}
	public String getEvents() {
		return events;
	}

	public String javaplanner() throws Exception {
		HttpServletRequest request = ServletActionContext.getRequest();
		HttpSession session = request.getSession(true);
		if (session.getAttribute("email") == null) {
			return ERROR;
		}

		DHXPlanner planner = new DHXPlanner("./codebase/", DHXSkin.TERRACE);
    	planner.setInitialDate(2013, 7, 15);
    	planner.setInitialView("units");
    	planner.config.setScrollHour(8);
    	planner.setWidth(844);
    	planner.setHeight(518);
    	planner.load("events", DHXDataFormat.JSON);
    	planner.data.dataprocessor.setURL("events");
    	planner.config.setDetailsOnCreate(true);
    	planner.config.setFirstHour(10);
    	planner.config.setLastHour(19);
    	planner.extensions.add(DHXExtension.TOOLTIP);

    	// create units view
    	DHXUnitsView units = new DHXUnitsView("units", "user", "Units");
    	units.setServerListLink("users");
    	units.setSkipIncorrect(true);
    	planner.views.add(units);

    	// create timeline view
    	DHXTimelineView timeline = new DHXTimelineView("timeline", "user", "Timeline");
    	timeline.setRenderMode(RenderModes.BAR);
    	timeline.setXScaleUnit(XScaleUnits.MINUTE);
    	timeline.setXStep(30);
    	timeline.setXSize(10);
    	timeline.setXStart(16);
    	timeline.setXLength(48);
    	timeline.setServerList("users");
    	planner.views.add(timeline);

		// adds sections in lightbox
    	DHXLightboxSelect sel = new DHXLightboxSelect("user", "Owner");
    	sel.setServerList("users");
    	planner.lightbox.add(sel);

    	DHXLightboxSelect status = new DHXLightboxSelect("status", "Status");
    	status.setServerList("status");
    	planner.lightbox.add(status);

    	DHXLightboxSelect urgency = new DHXLightboxSelect("urgency", "Urgency");
    	urgency.setServerList("urgency");
    	planner.lightbox.add(urgency);

    	// creates mini-calendar
    	DHXMiniCalendar cal = new DHXMiniCalendar("minical");
    	cal.setNavigation(true);
    	planner.calendars.add(cal);

    	users = (new CustomEventsManager(request)).getUsers();
    	employee = !session.getAttribute("type").toString().equals("manager");
    	username = session.getAttribute("name").toString();
    	userid = session.getAttribute("id").toString();
    	this.planner = planner.render();
		return SUCCESS;
	}

	public String events() throws Exception {
		CustomEventsManager evs = new CustomEventsManager(ServletActionContext.getRequest());
		events = evs.run();
		return SUCCESS;
	}

	public String login() throws Exception {
		HttpServletRequest request = ServletActionContext.getRequest();
		HttpSession session = request.getSession(true);
		if (session.getAttribute("email") != null)
			return LOGIN;

		String email = request.getParameter("email");
		String password = request.getParameter("password");
		if (email == null || password == null)
			return SUCCESS;

		Session s = HibernateUtil.getSessionFactory().openSession();
		List<User> users = s.createCriteria(User.class)
				.add(Restrictions.eq("email", email))
				.add(Restrictions.eq("password", password))
				.list();

		if (users.size() == 0)
			return ERROR;

		session.setAttribute("id", users.get(0).getId());
		session.setAttribute("email", users.get(0).getEmail());
		session.setAttribute("name", users.get(0).getName());
		session.setAttribute("type", users.get(0).getType());
		return LOGIN;
	}

	public String logout() throws Exception {
		HttpServletRequest request = ServletActionContext.getRequest();
		HttpSession session = request.getSession(true);
		session.removeAttribute("id");
		session.removeAttribute("email");
		session.removeAttribute("name");
		session.removeAttribute("type");
		return SUCCESS;
	}
}

Step 13. Add Checkbox List of Employees

To implement changing of scales in the units and timeline views with the users checkboxes, add the following script to article.jsp:

<script>
		function update_config() {
			var coll = [];
			var inputs = document.getElementById("users").getElementsByTagName("input");
			var labels = document.getElementById("users").getElementsByTagName("label");
			for (var i = 0; i < inputs.length; i++) {
				if (!inputs[i].checked) continue;

				var id = inputs[i].id.replace("user_", "");
				var name = labels[i].innerHTML;

				coll.push({key:id, label:name});
			}
			if (coll.length > 0) {
				scheduler._props.units.options = coll;
				scheduler.matrix.timeline.y_unit = coll;
				scheduler.callEvent("onOptionsLoad", []);
				scheduler.setCurrentView();
			}
		}
</script>

Modify the checkboxes to check and uncheck users and update the calendar scales:

<s:iterator value="users" status="userStatus">
<tr class="<s:if test="#userStatus.even == true">even</s:if><s:else>odd</s:else>">
<td class="checkbox">
   <input type="checkbox" id="user_<s:property value="id"/>" checked onchange="update_config();" />
</td>
	<td class="last">
<label for="user_<s:property value="id" />"><s:property value="name" /></label>
</td>
</tr>
</s:iterator>

Now the calendar scales are updated according to the checked users.

Let’s change the lightbox label to ‘Task manager’ and enable delete confirmation by adding the following script to article.jsp:

	scheduler.locale.labels.confirm_deleting = "Task will be deleted permanently, are you sure?";
	scheduler.locale.labels.new_event = "New task";

 

Step 14. Set Access Rights

At this step, we’ll configure access rights for the two type of users – Manager and Employee. The Manager can modify events, while an Employee can only view events and change status of his own events. Add the follow code in the end of article.jsp:

<s:if test="employee == true">
			scheduler.attachEvent("onBeforeLightbox", function(id) {
				var user = <s:property escape="false" value="userid" />;
				var event = scheduler.getEvent(id);

				if (event.user != user) {
					scheduler.config.buttons_left = ["dhx_cancel_btn"];
					scheduler.config.buttons_right = [];
				} else {
					scheduler.config.buttons_left = ["dhx_save_btn", "dhx_cancel_btn"];
					scheduler.config.buttons_right = [];
				}
				scheduler.resetLightbox();

				scheduler.formSection("description").control.disabled = true;
				var time =  scheduler.formSection("time").control;
				for (var i = 0; i < time.length; i++)
					time[i].disabled = true;
				scheduler.formSection("user").control.disabled = true;
				scheduler.formSection("urgency").control.disabled = true;
				scheduler.formSection("status").control.disabled = (event.user != user) ? true : false;

				return true;
			});
			scheduler.config.dblclick_create = false;
			scheduler.attachEvent("onBeforeDrag", function() { return false; });
			scheduler.attachEvent("onDblClick", function(id) { scheduler.showLightbox(id); return false; });
			scheduler.attachEvent("onClick", function() { return false; });
		</s:if>

 

Step 15. Add Tooltips to Tasks

Let’s add tooltips to the tasks in the month and timeline views. Update article.jsp as follows:

scheduler.attachEvent("onBeforeTooltip", function (id){
			var view = scheduler.getState().mode;
		    if (view == "timeline" || view == "month")
		    	return true;
		    return false;
		});
		scheduler.templates.tooltip_text = function(start,end,ev){
		    return  "<b>Task:</b> " + ev.text + "<br/>" +
		    		"<b>Start date:</b> " +  scheduler.templates.tooltip_date_format(start) + "<br/>" + 
    				"<b>End date:</b> " + scheduler.templates.tooltip_date_format(end) + "<br/>" +
	    			"<b>Owner:</b> " + scheduler.getLabel("user", ev.user) + "<br/>" +
		    		"<b>Status:</b> " + scheduler.getLabel("status", ev.status) + "<br/>" +
		    		"<b>Urgency:</b> " + scheduler.getLabel("urgency", ev.urgency) + "<br/>";
		};

 

Step 16. Configure Task Colors

To configure coloring of a task depending on its status – pending (blue), started (orange), completed (green) – add the following code to article.jsp:

<script>
scheduler.templates.event_class=function(start, end, event){
	return event.status;
};
</script>
<style>
/* pending event style */
.dhx_cal_event.pending div,
.dhx_cal_event_line.pending {
background-color: #1696AF !important;
color: #ffffff !important;
}
.dhx_cal_event_clear.pending {
color: #1696AF !important;
}

/* started event style*/
.dhx_cal_event.started div,
.dhx_cal_event_line.started {
background-color: #FD7511 !important;
color: #ffffff !important;
}
.dhx_cal_event_clear.started {
color: #FD7511 !important;
}

/* completed event style */
.dhx_cal_event.completed div,
.dhx_cal_event_line.completed {
background-color: #76B006 !important;
color: #ffffff !important;
}
.dhx_cal_event_clear.completed {
color: #76B006 !important;
}
</style>

That’s it. A job scheduler in Java is ready for use.

job-java-demo-sample

 

Step 17. Configure Email Notifications

To enable sending of email notifications  to the scheduler users, add the following methods to the file CustomEventsManager.java:

public User getManager() {
		Session session = HibernateUtil.getSessionFactory().openSession();
		List<User> users = new ArrayList<User>();
		try {
			session = HibernateUtil.getSessionFactory().openSession();
			users = session.createCriteria(User.class).add(Restrictions.eq("type", "manager")).list();
		} catch (RuntimeException e) {
			e.printStackTrace();
		} finally{
			session.flush();
			session.close();
		}
		if (users.size() > 0)
			return users.get(0);
		else
			return null;
	}

	private Boolean notify(Event ev) {
		HttpSession s = getRequest().getSession(true);
		User user = getUser(s.getAttribute("id").toString());
		User owner = getUser(ev.getUser());

		if (user.getType().equals("employee")) {
			User manager = getManager();
			email(manager.getEmail(), "Status of task was changed!", getManagerText(ev, owner, manager));
		} else {
			email(owner.getEmail(), "Task is updated!", getEmployeeText(ev, owner));
		}
		return true;
	}

	private String getEmployeeText(Event ev, User user) {
		String text = "Hi, " + user.getName() + "\n";
		text += "You have changes in your tasks:\n";
		text += ev.getText() + "\n";
		text += ev.getStart_date() + " - " + ev.getEnd_date() + "\n";
		text += "Status: " + ev.getStatus() + "\n";
		text += "Urgency: " + ev.getUrgency() + "\n";
		return text;
	}

	private String getManagerText(Event ev, User user, User manager) {
		String text = "Hi, " + manager.getName() + "\n";
		text += "Status of the following task was changed to '" + ev.getStatus() + "' by " + user.getName() + ":\n";
		text += "'" + ev.getText() + "'";
		return text;
	}

	protected Boolean email(String to, String subject, String body) {

		/*final String from = "javaplanner@javaplanner.com";
		final String smtp_host = "mail.javaplanner.com";
		final String smtp_port = "587";
		final String smtp_user = "javaplanner@javaplanner.com";
		final String smtp_pass = "smtp_password_is_here";

		Properties props = new Properties();
		props.put("mail.smtp.auth", "true");
		props.put("mail.smtp.host", smtp_host);
		props.put("mail.smtp.port", smtp_port);
		javax.mail.Session session = javax.mail.Session.getInstance(props, new javax.mail.Authenticator() {
			protected PasswordAuthentication getPasswordAuthentication() {
				return new PasswordAuthentication(smtp_user, smtp_pass);
			}
		});

		try {
			Message message = new MimeMessage(session);
			message.setFrom(new InternetAddress(from));
			message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
			message.setSubject(subject);
			message.setText(body);
 			Transport.send(message);
		} catch (MessagingException e) {
			return false;
		}
		*/
		return true;
	}

Finally, modify EventsManager.saveEvent() to call the notify() method:

...
Session session = HibernateUtil.getSessionFactory().openSession();
	try {
		session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();
		…
	session.getTransaction().commit();
	notify((Event) event);

	} catch (RuntimeException e) {
...

Sign up for our newsletter to get and evaluate the sample of job scheduler right now:


You are welcome to share your comments, suggestions and ideas on further development of our job scheduler in the comments below.

Comments (2)

  • lmc,

    Thanks for your amazing work. live demos of Job Scheduler Tutorial in Java Struts is not available. I would appreciate if you update the live demos. Thank you very much.

    Reply
    1. administrator,

      Hi, the live demos have been updated.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

− seven = 1

You may use these HTML tags and attributes:
<b></b> <i></i> <strike></strike> <a href=""></a>