/*
* Copyright 2013-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.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.batch.admin.domain.JobExecutionInfo;
import org.springframework.batch.admin.domain.JobExecutionInfoResource;
import org.springframework.batch.admin.domain.NoSuchBatchJobException;
import org.springframework.batch.admin.domain.support.JobParametersExtractor;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.configuration.ListableJobLocator;
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.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.data.domain.Pageable;
import org.springframework.hateoas.ExposesResourceFor;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
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;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* Controller for batch job executions.
*
* @author Dave Syer
* @author Ilayaperumal Gopinathan
* @author Gunnar Hillert
* @since 2.0
*/
@Controller
@RequestMapping("/batch/executions")
@ExposesResourceFor(JobExecutionInfoResource.class)
public class BatchJobExecutionsController extends AbstractBatchJobsController {
@Autowired
private ListableJobLocator jobLocator;
/**
* List all job executions in a given range. If no pagination is provided,
* the default {@code PageRequest(0, 20)} is passed in. See {@link org.springframework.data.web.PageableHandlerMethodArgumentResolver}
* for details.
*
* @param pageable If not provided will default to page 0 and a page size of 20
* @return Collection of JobExecutionInfoResource
*/
@RequestMapping(value = { "" }, method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public PagedResources<JobExecutionInfoResource> list(Pageable pageable) throws NoSuchJobException {
Collection<JobExecutionInfoResource> resources = new ArrayList<JobExecutionInfoResource>();
for (JobExecution jobExecution : jobService.listJobExecutions(pageable.getOffset(), pageable.getPageSize())) {
Job job = jobLocator.getJob(jobExecution.getJobInstance().getJobName());
final JobExecutionInfoResource jobExecutionInfoResource = getJobExecutionInfoResource(jobExecution,
job.isRestartable());
resources.add(jobExecutionInfoResource);
}
return new PagedResources<JobExecutionInfoResource>(resources,
new PageMetadata(pageable.getPageSize(), pageable.getPageNumber(),
jobService.countJobExecutions()));
}
/**
* Return a paged collection of job executions for a given job.
*
* @param jobName name of the job
* @param pageable If not provided will default to page 0 and a page size of 20
* @return Collection of JobExecutionInfo
*/
@RequestMapping(value = "", method = RequestMethod.GET, params = "jobname")
@ResponseStatus(HttpStatus.OK)
public PagedResources<JobExecutionInfoResource> executionsForJob(@RequestParam("jobname") String jobName,
Pageable pageable) {
Collection<JobExecutionInfoResource> result = new ArrayList<JobExecutionInfoResource>();
try {
for (JobExecution jobExecution : jobService.listJobExecutionsForJob(jobName, pageable.getOffset(), pageable.getPageSize())) {
result.add(jobExecutionInfoResourceAssembler.toResource(new JobExecutionInfo(jobExecution, timeZone)));
}
return new PagedResources<JobExecutionInfoResource>(result,
new PageMetadata(pageable.getPageSize(), pageable.getPageNumber(),
jobService.countJobExecutionsForJob(jobName)));
}
catch (NoSuchJobException e) {
throw new NoSuchBatchJobException(jobName);
}
}
/**
* Return a paged collection of job executions for a given job instance.
*
* @param jobName name of the job
* @return Collection of JobExecutionInfo
*/
@RequestMapping(value = "", method = RequestMethod.GET, params = {"jobinstanceid", "jobname"})
@ResponseStatus(HttpStatus.OK)
public Collection<JobExecutionInfoResource> executionsForJobInstance(@RequestParam("jobinstanceid") long jobInstanceId,
@RequestParam("jobname") String jobName) {
Collection<JobExecutionInfoResource> result = new ArrayList<JobExecutionInfoResource>();
try {
for (JobExecution jobExecution : jobService.getJobExecutionsForJobInstance(jobName, jobInstanceId)) {
result.add(jobExecutionInfoResourceAssembler.toResource(new JobExecutionInfo(jobExecution, timeZone)));
}
return result;
}
catch (NoSuchJobException e) {
throw new NoSuchBatchJobException(jobName);
}
}
/**
* Send the request to launch Job. Job has to be deployed first.
*
* @param name the name of the job
* @param jobParameters the job parameters in comma delimited form
*/
@RequestMapping(value = "", method = RequestMethod.POST, params = "jobname")
@ResponseStatus(HttpStatus.CREATED)
public void launchJob(@RequestParam("jobname") String name, @RequestParam(value = "jobParameters", required = false) String jobParameters) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, NoSuchJobException {
JobParameters params = new JobParameters();
if(jobParameters != null) {
JobParametersExtractor extractor = new JobParametersExtractor();
params = extractor.fromString(jobParameters);
}
jobService.launch(name, params);
}
/**
* Check if the {@link org.springframework.batch.core.JobInstance} corresponds to the given {@link org.springframework.batch.core.JobExecution}
* has any of the JobExecutions in {@link org.springframework.batch.core.BatchStatus#COMPLETED} status
* @param jobExecution the jobExecution to check for
* @return boolean flag to set if this job execution can be restarted
*/
private boolean isJobExecutionRestartable(JobExecution jobExecution) {
JobInstance jobInstance = jobExecution.getJobInstance();
BatchStatus status = jobExecution.getStatus();
try {
List<JobExecution> jobExecutionsForJobInstance = (List<JobExecution>) jobService.getJobExecutionsForJobInstance(
jobInstance.getJobName(), jobInstance.getId());
for (JobExecution jobExecutionForJobInstance : jobExecutionsForJobInstance) {
if (jobExecutionForJobInstance.getStatus() == BatchStatus.COMPLETED) {
return false;
}
}
}
catch (NoSuchJobException e) {
throw new NoSuchBatchJobException(jobInstance.getJobName());
}
return status.isGreaterThan(BatchStatus.STOPPING) && status.isLessThan(BatchStatus.ABANDONED);
}
/**
* @param executionId Id of the {@link org.springframework.batch.core.JobExecution}
* @return JobExecutionInfo for the given job name
* @throws org.springframework.batch.core.launch.NoSuchJobExecutionException Thrown if the {@link org.springframework.batch.core.JobExecution} does not exist
*/
@RequestMapping(value = "/{executionId}", method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public JobExecutionInfoResource getJobExecutionInfo(@PathVariable long executionId) throws NoSuchJobExecutionException {
final JobExecution jobExecution;
try {
jobExecution = jobService.getJobExecution(executionId);
}
catch (org.springframework.batch.core.launch.NoSuchJobExecutionException e) {
throw new NoSuchJobExecutionException(String.format("Could not find jobExecution with id %s", String.valueOf(executionId)));
}
final Job job;
String jobName = jobExecution.getJobInstance().getJobName();
try {
job = jobLocator.getJob(jobName);
}
catch (NoSuchJobException e1) {
throw new NoSuchBatchJobException("The job '" + jobName + "' does not exist.");
}
return getJobExecutionInfoResource(jobExecution, job.isRestartable());
}
private JobExecutionInfoResource getJobExecutionInfoResource(JobExecution jobExecution,
boolean restartable) {
final JobExecutionInfoResource jobExecutionInfoResource = jobExecutionInfoResourceAssembler.toResource(new JobExecutionInfo(
jobExecution,
timeZone));
if (restartable) {
// Set restartable flag for the JobExecutionResource based on the actual JobInstance
// If any one of the jobExecutions for the jobInstance is complete, set the restartable flag for
// all the jobExecutions to false.
if (jobExecution.getStatus() != BatchStatus.COMPLETED) {
jobExecutionInfoResource.setRestartable(isJobExecutionRestartable(jobExecution));
}
}
else {
// Set false for this job execution irrespective its status.
jobExecutionInfoResource.setRestartable(false);
}
return jobExecutionInfoResource;
}
/**
* Stop Job Execution by the given executionId.
*
* @param jobExecutionId the executionId of the job execution to stop
*/
@RequestMapping(value = { "/{executionId}" }, method = RequestMethod.PUT, params = "stop=true")
@ResponseStatus(HttpStatus.OK)
public void stopJobExecution(@PathVariable("executionId") long jobExecutionId) throws JobExecutionNotRunningException, NoSuchJobExecutionException {
try {
jobService.stop(jobExecutionId);
}
catch (org.springframework.batch.core.launch.JobExecutionNotRunningException e) {
throw new JobExecutionNotRunningException(String.format("Job execution with executionId %s is not running.", String.valueOf(jobExecutionId)));
}
catch (org.springframework.batch.core.launch.NoSuchJobExecutionException e) {
throw new NoSuchJobExecutionException(String.format("Could not find jobExecution with id %s", String.valueOf(jobExecutionId)));
}
}
/**
* Restart the Job Execution with the given executionId.
*
* @param jobExecutionId the executionId of the job execution to restart
*/
@RequestMapping(value = { "/{executionId}" }, method = RequestMethod.PUT, params = "restart=true")
@ResponseStatus(HttpStatus.OK)
public void restartJobExecution(@PathVariable("executionId") long jobExecutionId) throws NoSuchJobExecutionException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobInstanceAlreadyCompleteException, JobRestartException, NoSuchJobException {
final JobExecution jobExecution;
try {
jobExecution = jobService.getJobExecution(jobExecutionId);
}
catch (org.springframework.batch.core.launch.NoSuchJobExecutionException e) {
throw new NoSuchJobExecutionException(String.format("Could not find jobExecution with id %s", String.valueOf(jobExecutionId)));
}
if (jobExecution.isRunning()) {
throw new JobExecutionAlreadyRunningException(
"Job Execution for this job is already running: " + jobExecution.getJobInstance());
}
final JobInstance lastInstance = jobExecution.getJobInstance();
final JobParameters jobParameters = jobExecution.getJobParameters();
final Job job;
try {
job = jobLocator.getJob(lastInstance.getJobName());
}
catch (NoSuchJobException e1) {
throw new NoSuchBatchJobException("The job '" + lastInstance.getJobName()
+ "' does not exist.");
}
try {
job.getJobParametersValidator().validate(jobParameters);
}
catch (JobParametersInvalidException e) {
throw new JobParametersInvalidException(
"The Job Parameters for Job Execution " + jobExecution.getId()
+ " are invalid.");
}
final BatchStatus status = jobExecution.getStatus();
if (status == BatchStatus.COMPLETED || status == BatchStatus.ABANDONED) {
throw new JobInstanceAlreadyCompleteException(
"Job Execution " + jobExecution.getId() + " is already complete.");
}
if (!job.isRestartable()) {
throw new JobRestartException(
"The job '" + lastInstance.getJobName() + "' is not restartable.");
}
jobService.launch(lastInstance.getJobName(), jobParameters);
}
/**
* Stop all job executions.
*/
@RequestMapping(value = { "" }, method = RequestMethod.PUT, params = "stop=true")
@ResponseStatus(HttpStatus.OK)
public void stopAll() {
jobService.stopAll();
}
}