/*
* Copyright 2009-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.admin.web;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.admin.domain.JobExecutionInfo;
import org.springframework.batch.admin.domain.JobInfo;
import org.springframework.batch.admin.domain.StepExecutionInfo;
import org.springframework.batch.admin.service.JobService;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.launch.JobExecutionNotRunningException;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.launch.NoSuchJobExecutionException;
import org.springframework.batch.core.launch.NoSuchJobInstanceException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Controller for job executions.
*
* @author Dave Syer
*
*/
@Controller
public class JobExecutionController {
private static Log logger = LogFactory.getLog(JobExecutionController.class);
public static class StopRequest {
private Long jobExecutionId;
public Long getJobExecutionId() {
return jobExecutionId;
}
public void setJobExecutionId(Long jobExecutionId) {
this.jobExecutionId = jobExecutionId;
}
}
private JobService jobService;
private ObjectMapper objectMapper;
private TimeZone timeZone = TimeZone.getDefault();
/**
* @param timeZone the timeZone to set
*/
@Autowired(required = false)
@Qualifier("userTimeZone")
public void setTimeZone(TimeZone timeZone) {
this.timeZone = timeZone;
}
@Autowired
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Autowired
public JobExecutionController(JobService jobService) {
super();
this.jobService = jobService;
}
@RequestMapping(value = "/jobs/executions/{jobExecutionId}", method = RequestMethod.DELETE)
public String stop(Model model, @ModelAttribute("stopRequest") StopRequest stopRequest, Errors errors,
@PathVariable Long jobExecutionId) {
stopRequest.jobExecutionId = jobExecutionId;
try {
JobExecution jobExecution = jobService.stop(jobExecutionId);
model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
}
catch (NoSuchJobExecutionException e) {
errors.reject("no.such.job.execution", new Object[] { jobExecutionId }, "No job exection with id="
+ jobExecutionId);
}
catch (JobExecutionNotRunningException e) {
errors.reject("job.execution.not.running", "Job exection with id=" + jobExecutionId + " is not running.");
JobExecution jobExecution;
try {
jobExecution = jobService.getJobExecution(jobExecutionId);
model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
}
catch (NoSuchJobExecutionException e1) {
// safe
}
}
return "jobs/execution";
}
@RequestMapping(value = "/jobs/executions/{jobExecutionId}", method = RequestMethod.DELETE, params = "abandon")
public String abandon(Model model, @ModelAttribute("stopRequest") StopRequest stopRequest, Errors errors,
@PathVariable Long jobExecutionId) {
stopRequest.jobExecutionId = jobExecutionId;
try {
JobExecution jobExecution = jobService.abandon(jobExecutionId);
model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
}
catch (NoSuchJobExecutionException e) {
errors.reject("no.such.job.execution", new Object[] { jobExecutionId }, "No job exection with id="
+ jobExecutionId);
}
catch (JobExecutionAlreadyRunningException e) {
errors.reject("job.execution.running", "Job exection with id=" + jobExecutionId + " is running.");
JobExecution jobExecution;
try {
jobExecution = jobService.getJobExecution(jobExecutionId);
model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
}
catch (NoSuchJobExecutionException e1) {
// safe
}
}
return "jobs/execution";
}
@RequestMapping(value = { "/jobs/executions", "/jobs/executions.*" }, method = RequestMethod.GET)
public @ModelAttribute("jobExecutions")
Collection<JobExecutionInfo> list(ModelMap model, @RequestParam(defaultValue = "0") int startJobExecution,
@RequestParam(defaultValue = "20") int pageSize) {
int total = jobService.countJobExecutions();
TableUtils.addPagination(model, total, startJobExecution, pageSize, "JobExecution");
Collection<JobExecutionInfo> result = new ArrayList<JobExecutionInfo>();
for (JobExecution jobExecution : jobService.listJobExecutions(startJobExecution, pageSize)) {
result.add(new JobExecutionInfo(jobExecution, timeZone));
}
return result;
}
@RequestMapping(value = { "/jobs/{jobName}/{jobInstanceId}/executions", "/jobs/{jobName}/{jobInstanceId}" }, method = RequestMethod.GET)
public String listForInstance(Model model, @PathVariable String jobName, @PathVariable long jobInstanceId,
@ModelAttribute("date") Date date, Errors errors) {
JobInstance jobInstance = null;
try {
jobInstance = jobService.getJobInstance(jobInstanceId);
if (!jobInstance.getJobName().equals(jobName)) {
errors.reject("wrong.job.name", new Object[] { jobInstanceId, jobInstance.getJobName(), jobName },
"The JobInstance with id=" + jobInstanceId + " has the wrong name (" + jobInstance.getJobName()
+ " not " + jobName);
}
}
catch (NoSuchJobInstanceException e) {
errors.reject("no.such.job.instance", new Object[] { jobInstanceId }, "There is no such job instance ("
+ jobInstanceId + ")");
}
if (jobInstance != null && (errors == null || !errors.hasErrors())) {
Collection<JobExecutionInfo> result = new ArrayList<JobExecutionInfo>();
try {
Collection<JobExecution> jobExecutions = jobService.getJobExecutionsForJobInstance(jobName,
jobInstanceId);
for (JobExecution jobExecution : jobExecutions) {
result.add(new JobExecutionInfo(jobExecution, timeZone));
}
}
catch (NoSuchJobException e) {
errors.reject("no.such.job", new Object[] { jobName }, "There is no such job (" + jobName + ")");
}
model.addAttribute(new JobInfo(jobName, result.size(), jobInstanceId, jobService.isLaunchable(jobName), jobService.isIncrementable(jobName)));
model.addAttribute("jobExecutions", result);
}
return "jobs/executions";
}
@RequestMapping(value = "/jobs/{jobName}/{jobInstanceId}/executions", method = RequestMethod.POST)
public String restart(Model model, @PathVariable String jobName, @PathVariable long jobInstanceId,
@ModelAttribute("date") Date date, Errors errors) {
try {
Collection<JobExecution> jobExecutions = jobService.getJobExecutionsForJobInstance(jobName, jobInstanceId);
model.addAttribute(new JobInfo(jobName, jobExecutions.size() + 1));
JobExecution jobExecution = jobExecutions.iterator().next();
model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
Long jobExecutionId = jobExecution.getId();
try {
jobExecution = jobService.restart(jobExecutionId);
model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
}
catch (NoSuchJobExecutionException e) {
errors.reject("no.such.job.execution", new Object[] { jobExecutionId },
"There is no such job execution (" + jobExecutionId + ")");
}
catch (JobExecutionAlreadyRunningException e) {
errors.reject("job.execution.already.running", new Object[] { jobExecutionId },
"This job execution is already running (" + jobExecutionId + ")");
}
catch (JobRestartException e) {
errors.reject("job.restart.exception", new Object[] { jobName },
"There was a problem restarting the job (" + jobName + ")");
}
catch (JobInstanceAlreadyCompleteException e) {
errors.reject("job.instance.already.complete", new Object[] { jobName },
"The job instance is already complete for (" + jobName
+ "). Use different job parameters to launch it again.");
}
catch (JobParametersInvalidException e) {
errors.reject("job.parameters.invalid", new Object[] { jobName },
"The job parameters are invalid according to the job (" + jobName + ")");
}
}
catch (NoSuchJobException e) {
errors.reject("no.such.job", new Object[] { jobName }, "There is no such job (" + jobName + ")");
}
return "jobs/execution";
}
@RequestMapping(value = "/jobs/executions", method = RequestMethod.DELETE)
public @ModelAttribute("jobExecutions")
Collection<JobExecutionInfo> stopAll(ModelMap model, @RequestParam(defaultValue = "0") int startJobExecution,
@RequestParam(defaultValue = "20") int pageSize) {
model.addAttribute("stoppedCount", jobService.stopAll());
return list(model, startJobExecution, pageSize);
}
@RequestMapping(value = "/jobs/{jobName}/executions", method = RequestMethod.GET)
public String listForJob(ModelMap model, @PathVariable String jobName, @ModelAttribute("date") Date date,
Errors errors, @RequestParam(defaultValue = "0") int startJobExecution,
@RequestParam(defaultValue = "20") int pageSize) {
int total = startJobExecution;
try {
total = jobService.countJobExecutionsForJob(jobName);
}
catch (NoSuchJobException e) {
errors.reject("no.such.job", new Object[] { jobName }, "There is no such job (" + jobName + ")");
logger.warn("Could not locate Job with name=" + jobName);
return "jobs/executions";
}
TableUtils.addPagination(model, total, startJobExecution, pageSize, "JobExecution");
Collection<JobExecutionInfo> result = new ArrayList<JobExecutionInfo>();
try {
for (JobExecution jobExecution : jobService.listJobExecutionsForJob(jobName, startJobExecution, pageSize)) {
result.add(new JobExecutionInfo(jobExecution, timeZone));
}
int count = jobService.countJobExecutionsForJob(jobName);
model.addAttribute(new JobInfo(jobName, count, null, jobService.isLaunchable(jobName), jobService
.isIncrementable(jobName)));
model.addAttribute("jobExecutions", result);
}
catch (NoSuchJobException e) {
errors.reject("no.such.job", new Object[] { jobName }, "There is no such job (" + jobName + ")");
logger.warn("Could not locate Job with name=" + jobName);
}
return "jobs/executions";
}
@RequestMapping(value = "/jobs/executions/{jobExecutionId}", method = RequestMethod.GET)
public String detail(Model model, @PathVariable Long jobExecutionId, @ModelAttribute("date") Date date,
Errors errors) {
try {
JobExecution jobExecution = jobService.getJobExecution(jobExecutionId);
model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
String jobName = jobExecution.getJobInstance().getJobName();
// Collection<String> stepNames = new HashSet<String>(jobService.getStepNamesForJob(jobName));
// Collection<StepExecution> stepExecutions = new ArrayList<StepExecution>(jobExecution.getStepExecutions());
List<StepExecutionInfo> stepExecutionInfos = new ArrayList<StepExecutionInfo>();
for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
stepExecutionInfos.add(new StepExecutionInfo(stepExecution, timeZone));
}
// for (String name : stepNames) {
// boolean found = false;
// for (Iterator<StepExecution> iterator = stepExecutions.iterator(); iterator.hasNext();) {
// StepExecution stepExecution = iterator.next();
// if (stepExecution.getStepName().equals(name)) {
// stepExecutionInfos.add(new StepExecutionInfo(stepExecution, timeZone));
// iterator.remove();
// found = true;
// }
// }
// if (!found) {
// stepExecutionInfos.add(new StepExecutionInfo(jobName, jobExecutionId, name, timeZone));
// }
// }
Collections.sort(stepExecutionInfos, new Comparator<StepExecutionInfo>() {
@Override
public int compare(StepExecutionInfo o1, StepExecutionInfo o2) {
return o1.getId().compareTo(o2.getId());
}
});
model.addAttribute("stepExecutionInfos", stepExecutionInfos);
}
catch (NoSuchJobExecutionException e) {
errors.reject("no.such.job.execution", new Object[] { jobExecutionId }, "There is no such job execution ("
+ jobExecutionId + ")");
}
// catch (NoSuchJobException e) {
// errors.reject("no.such.job", new Object[] { jobExecutionId }, "There is no such job with exeuction id ("
// + jobExecutionId + ")");
// }
return "jobs/execution";
}
@RequestMapping(value = "/jobs/executions/{jobExecutionId}/execution-context", method = RequestMethod.GET)
public String getExecutionContext(Model model, @PathVariable Long jobExecutionId, @ModelAttribute("date") Date date,
Errors errors) {
try {
JobExecution jobExecution = jobService.getJobExecution(jobExecutionId);
Map<String, Object> executionMap = new HashMap<String, Object>();
for (Map.Entry<String, Object> entry : jobExecution.getExecutionContext().entrySet()) {
executionMap.put(entry.getKey(), entry.getValue());
}
model.addAttribute("jobExecutionContext", objectMapper.writeValueAsString(executionMap));
model.addAttribute("jobExecutionId", jobExecutionId);
}
catch (NoSuchJobExecutionException e) {
errors.reject("no.such.job.execution", new Object[] { jobExecutionId }, "There is no such job execution ("
+ jobExecutionId + ")");
} catch (IOException e) {
errors.reject("serialization.error", new Object[] { jobExecutionId }, "Error serializing execution context for job execution ("
+ jobExecutionId + ")");
}
return "jobs/executions/execution-context";
}
}