/* vim: set ts=2 et sw=2 cindent fo=qroca: */
package com.globant.katari.quartz.application;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.List;
import java.util.ArrayList;
import org.apache.commons.lang.Validate;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import com.globant.katari.core.application.Command;
import com.globant.katari.core.application.JsonRepresentation;
import com.globant.katari.quartz.domain.ScheduledCommand;
/** Command to obtain the list of scheduled commands that quartz
* executes.
*/
public class ListTasksCommand implements Command<JsonRepresentation> {
/** The Quartz's scheduler, never null.
*/
private final Scheduler scheduler;
/** The date formatter, never null.
*
* This is initialized to an iso 8601 extended format.
*/
private final SimpleDateFormat dateFormatter;
/** Constructor.
*
* @param theScheduler the Quartz scheduler. It cannot be null.
*/
public ListTasksCommand(final Scheduler theScheduler) {
Validate.notNull(theScheduler, "The Scheduler cannot be null");
scheduler = theScheduler;
dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
}
/** Return the json representation of the Task.
*
* Example of the output json array below:
* <code>
* [
* {
* "groupName": "group-name",
* "jobName": "job-name",
* "progressPercent": "10",
* "friendlyName": "The Friendly Name",
* "isRunning": "true" / "false",
* "information": {...},
* "nextExecutionTime": "2010-10-19T20:00:00Z",
* "lastExecutionTime": "2010-10-19T14:00:00Z",
* }
* ]
* </code>
*
* The progressPercent, nextExecutionTime and lastExecutionTime are optional.
* If they are not known, then they are not sent to the client.
*
* Dates are in iso 8601, extended format. They are in utc, designated with
* the Z suffix. Precision is up to the second.
*
* @return a json representation, never null.
*/
public JsonRepresentation execute() {
try {
List<Task> tasks = getTasks();
JSONArray tasksJson = new JSONArray();
for (Task task : tasks) {
ScheduledCommand command = task.getCommand();
JSONObject taskJson = new JSONObject();
taskJson.put("groupName", task.getGroupName());
taskJson.put("jobName", task.getJobName());
taskJson.put("progressPercent", command.getProgressPercent());
taskJson.put("friendlyName", command.getDisplayName());
taskJson.put("isRunning", task.isRunning());
taskJson.put("information", command.getInformation());
taskJson.put("nextExecutionTime",
formatDate(task.getNextExecutionTime()));
taskJson.put("lastExecutionTime",
formatDate(task.getLastExecutionTime()));
tasksJson.put(taskJson);
}
return new JsonRepresentation(tasksJson);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
/** Formats the given date with the pattern built in the constructor.
*
* @param date the date. If it is null, then it returns null.
*
* @return the string representation of the formated Date or null if the
* given date is null.
*/
private String formatDate(final Date date) {
if (date == null) {
return null;
} else {
return dateFormatter.format(date);
}
}
/** Retrieves the list of Quartz tasks.
*
* @return the list of tasks, never null.
*/
@SuppressWarnings("unchecked")
public List<Task> getTasks() {
List<Task> tasks = new ArrayList<Task>();
try {
List<JobExecutionContext> runningJobs;
runningJobs = scheduler.getCurrentlyExecutingJobs();
String[] groupNames = scheduler.getJobGroupNames();
// Iterate over all group names.
for (String groupName : groupNames) {
String[] triggers = scheduler.getTriggerNames(groupName);
String[] jobNames = scheduler.getJobNames(groupName);
// Iterate over all job names.
for (String jobName : jobNames) {
JobDetail detail = scheduler.getJobDetail(jobName, groupName);
JobDataMap dataMap = detail.getJobDataMap();
Object mi = dataMap.get("methodInvoker");
if (mi instanceof MethodInvokingJobDetailFactoryBean) {
MethodInvokingJobDetailFactoryBean jobDetailFactoryBean;
jobDetailFactoryBean = (MethodInvokingJobDetailFactoryBean) mi;
Object targetObject = jobDetailFactoryBean.getTargetObject();
if (targetObject instanceof ScheduledCommand) {
for (String theTrigger : triggers) {
Trigger trigger = scheduler.getTrigger(theTrigger, groupName);
// What if one job has many triggers?
if (trigger.getJobName().equals(detail.getName())) {
Date nextExecutionTime = trigger.getNextFireTime();
Date lastExecutionTime = trigger.getPreviousFireTime();
boolean isJobRunning;
isJobRunning = isJobDetailInContext(runningJobs, detail);
Task task = new Task(groupName, jobName,
(ScheduledCommand) targetObject, isJobRunning,
nextExecutionTime, lastExecutionTime);
tasks.add(task);
}
}
}
}
}
}
return tasks;
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
}
/** Decides if the job detail is in the provided list of JobExecutionContext.
*
* @param executionContexts the list of job execution contexts.
*
* @param detail the job detail to search.
*
* @return true if the job detail was found in any of the job execution
* contexts.
*/
private boolean isJobDetailInContext(
final List<JobExecutionContext> executionContexts,
final JobDetail detail) {
for (JobExecutionContext context : executionContexts) {
if (context.getJobDetail().equals(detail)) {
return true;
}
}
return false;
}
}