View Javadoc
1   /*
2    * Copyright 2013-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.batch.admin.web;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.List;
22  
23  import org.springframework.batch.admin.domain.JobExecutionInfo;
24  import org.springframework.batch.admin.domain.JobExecutionInfoResource;
25  import org.springframework.batch.admin.domain.NoSuchBatchJobException;
26  import org.springframework.batch.admin.domain.support.JobParametersExtractor;
27  import org.springframework.batch.core.BatchStatus;
28  import org.springframework.batch.core.Job;
29  import org.springframework.batch.core.JobExecution;
30  import org.springframework.batch.core.JobInstance;
31  import org.springframework.batch.core.JobParameters;
32  import org.springframework.batch.core.JobParametersInvalidException;
33  import org.springframework.batch.core.configuration.ListableJobLocator;
34  import org.springframework.batch.core.launch.JobExecutionNotRunningException;
35  import org.springframework.batch.core.launch.NoSuchJobException;
36  import org.springframework.batch.core.launch.NoSuchJobExecutionException;
37  import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
38  import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
39  import org.springframework.batch.core.repository.JobRestartException;
40  import org.springframework.beans.factory.annotation.Autowired;
41  import org.springframework.data.domain.Pageable;
42  import org.springframework.hateoas.ExposesResourceFor;
43  import org.springframework.hateoas.PagedResources;
44  import org.springframework.hateoas.PagedResources.PageMetadata;
45  import org.springframework.http.HttpStatus;
46  import org.springframework.web.bind.annotation.PathVariable;
47  import org.springframework.web.bind.annotation.RequestMapping;
48  import org.springframework.web.bind.annotation.RequestMethod;
49  import org.springframework.web.bind.annotation.RequestParam;
50  import org.springframework.web.bind.annotation.ResponseStatus;
51  import org.springframework.web.bind.annotation.RestController;
52  
53  /**
54   * Controller for batch job executions.
55   *
56   * @author Dave Syer
57   * @author Ilayaperumal Gopinathan
58   * @author Gunnar Hillert
59   * @since 2.0
60   */
61  @RestController
62  @RequestMapping("/batch/executions")
63  @ExposesResourceFor(JobExecutionInfoResource.class)
64  public class BatchJobExecutionsController extends AbstractBatchJobsController {
65  
66  	@Autowired
67  	private ListableJobLocator jobLocator;
68  
69  	/**
70  	 * List all job executions in a given range. If no pagination is provided,
71  	 * the default {@code PageRequest(0, 20)} is passed in. See {@link org.springframework.data.web.PageableHandlerMethodArgumentResolver}
72  	 * for details.
73  	 *
74  	 * @param pageable If not provided will default to page 0 and a page size of 20
75  	 * @return Collection of JobExecutionInfoResource
76  	 */
77  	@RequestMapping(value = { "" }, method = RequestMethod.GET, produces = "application/json")
78  	@ResponseStatus(HttpStatus.OK)
79  	public PagedResources<JobExecutionInfoResource> list(Pageable pageable) throws NoSuchJobException {
80  
81  		Collection<JobExecutionInfoResource> resources = new ArrayList<JobExecutionInfoResource>();
82  
83  		for (JobExecution jobExecution : jobService.listJobExecutions(pageable.getOffset(), pageable.getPageSize())) {
84  			Job job = jobLocator.getJob(jobExecution.getJobInstance().getJobName());
85  
86  			final JobExecutionInfoResource jobExecutionInfoResource = getJobExecutionInfoResource(jobExecution,
87  					job.isRestartable());
88  			resources.add(jobExecutionInfoResource);
89  		}
90  
91  		return new PagedResources<JobExecutionInfoResource>(resources,
92  				new PageMetadata(pageable.getPageSize(), pageable.getPageNumber(),
93  						jobService.countJobExecutions()));
94  	}
95  
96  	/**
97  	 * Return a paged collection of job executions for a given job.
98  	 *
99  	 * @param jobName name of the job
100 	 * @param startJobExecution start index for the job execution list
101 	 * @param pageSize page size for the list
102 	 * @return collection of JobExecutionInfo
103 	 */
104 	@RequestMapping(value = "", method = RequestMethod.GET, params = "jobname", produces = "application/json")
105 	@ResponseStatus(HttpStatus.OK)
106 	public Collection<JobExecutionInfoResource> executionsForJob(@RequestParam("jobname") String jobName,
107 			@RequestParam(defaultValue = "0") int startJobExecution,
108 			@RequestParam(defaultValue = "20") int pageSize) {
109 
110 		Collection<JobExecutionInfoResource> result = new ArrayList<JobExecutionInfoResource>();
111 		try {
112 			for (JobExecution jobExecution : jobService.listJobExecutionsForJob(jobName, startJobExecution, pageSize)) {
113 				result.add(jobExecutionInfoResourceAssembler.toResource(new JobExecutionInfo(jobExecution, timeZone)));
114 			}
115 		}
116 		catch (NoSuchJobException e) {
117 			throw new NoSuchBatchJobException(jobName);
118 		}
119 		return result;
120 	}
121 
122 	/**
123 	 * Send the request to launch Job. Job has to be deployed first.
124 	 *
125 	 * @param name the name of the job
126 	 * @param jobParameters the job parameters in comma delimited form
127 	 */
128 	@RequestMapping(value = "", method = RequestMethod.POST, params = "jobname")
129 	@ResponseStatus(HttpStatus.CREATED)
130 	public void launchJob(@RequestParam("jobname") String name, @RequestParam(required = false) String jobParameters) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, NoSuchJobException {
131 		JobParameters params = new JobParameters();
132 		if(jobParameters != null) {
133 			JobParametersExtractor extractor = new JobParametersExtractor();
134 			extractor.fromString(jobParameters);
135 		}
136 
137 		jobService.launch(name, params);
138 	}
139 
140 	/**
141 	 * Get all existing job definition names.
142 	 *
143 	 * @return the collection of job definition names
144 	 */
145 	private Collection<String> getJobDefinitionNames() {
146 		int totalJobs = jobService.countJobs();
147 		return jobService.listJobs(0, totalJobs);
148 	}
149 
150 	/**
151 	 * Check if the {@link org.springframework.batch.core.JobInstance} corresponds to the given {@link org.springframework.batch.core.JobExecution}
152 	 * has any of the JobExecutions in {@link org.springframework.batch.core.BatchStatus#COMPLETED} status
153 	 * @param jobExecution the jobExecution to check for
154 	 * @return boolean flag to set if this job execution can be restarted
155 	 */
156 	private boolean isJobExecutionRestartable(JobExecution jobExecution) {
157 		JobInstance jobInstance = jobExecution.getJobInstance();
158 		BatchStatus status = jobExecution.getStatus();
159 		try {
160 			List<JobExecution> jobExecutionsForJobInstance = (List<JobExecution>) jobService.getJobExecutionsForJobInstance(
161 					jobInstance.getJobName(), jobInstance.getId());
162 			for (JobExecution jobExecutionForJobInstance : jobExecutionsForJobInstance) {
163 				if (jobExecutionForJobInstance.getStatus() == BatchStatus.COMPLETED) {
164 					return false;
165 				}
166 			}
167 		}
168 		catch (NoSuchJobException e) {
169 			throw new NoSuchBatchJobException(jobInstance.getJobName());
170 		}
171 		return status.isGreaterThan(BatchStatus.STOPPING) && status.isLessThan(BatchStatus.ABANDONED);
172 	}
173 
174 	/**
175 	 * @param executionId Id of the {@link org.springframework.batch.core.JobExecution}
176 	 * @return JobExecutionInfo for the given job name
177 	 * @throws org.springframework.batch.core.launch.NoSuchJobExecutionException Thrown if the {@link org.springframework.batch.core.JobExecution} does not exist
178 	 */
179 	@RequestMapping(value = "/{executionId}", method = RequestMethod.GET)
180 	@ResponseStatus(HttpStatus.OK)
181 	public JobExecutionInfoResource getJobExecutionInfo(@PathVariable long executionId) throws NoSuchJobExecutionException {
182 
183 		final JobExecution jobExecution;
184 
185 		try {
186 			jobExecution = jobService.getJobExecution(executionId);
187 		}
188 		catch (org.springframework.batch.core.launch.NoSuchJobExecutionException e) {
189 			throw new NoSuchJobExecutionException(String.format("Could not find jobExecution with id %s", String.valueOf(executionId)));
190 		}
191 
192 		final Job job;
193 		String jobName = jobExecution.getJobInstance().getJobName();
194 
195 		try {
196 			job = jobLocator.getJob(jobName);
197 		}
198 		catch (NoSuchJobException e1) {
199 			throw new NoSuchBatchJobException("The job '" + jobName + "' does not exist.");
200 		}
201 
202 		return getJobExecutionInfoResource(jobExecution, job.isRestartable());
203 	}
204 
205 	private JobExecutionInfoResource getJobExecutionInfoResource(JobExecution jobExecution,
206 			boolean restartable) {
207 
208 		final JobExecutionInfoResource jobExecutionInfoResource = jobExecutionInfoResourceAssembler.toResource(new JobExecutionInfo(
209 				jobExecution,
210 				timeZone));
211 		if (restartable) {
212 			// Set restartable flag for the JobExecutionResource based on the actual JobInstance
213 			// If any one of the jobExecutions for the jobInstance is complete, set the restartable flag for
214 			// all the jobExecutions to false.
215 			if (jobExecution.getStatus() != BatchStatus.COMPLETED) {
216 				jobExecutionInfoResource.setRestartable(isJobExecutionRestartable(jobExecution));
217 			}
218 		}
219 		else {
220 			// Set false for this job execution irrespective its status.
221 			jobExecutionInfoResource.setRestartable(false);
222 		}
223 
224 		return jobExecutionInfoResource;
225 	}
226 
227 	/**
228 	 * Stop Job Execution by the given executionId.
229 	 *
230 	 * @param jobExecutionId the executionId of the job execution to stop
231 	 */
232 	@RequestMapping(value = { "/{executionId}" }, method = RequestMethod.PUT, params = "stop=true")
233 	@ResponseStatus(HttpStatus.OK)
234 	public void stopJobExecution(@PathVariable("executionId") long jobExecutionId) throws JobExecutionNotRunningException, NoSuchJobExecutionException {
235 		try {
236 			jobService.stop(jobExecutionId);
237 		}
238 		catch (org.springframework.batch.core.launch.JobExecutionNotRunningException e) {
239 			throw new JobExecutionNotRunningException(String.format("Job execution with executionId %s is not running.", String.valueOf(jobExecutionId)));
240 		}
241 		catch (org.springframework.batch.core.launch.NoSuchJobExecutionException e) {
242 			throw new NoSuchJobExecutionException(String.format("Could not find jobExecution with id %s", String.valueOf(jobExecutionId)));
243 		}
244 	}
245 
246 	/**
247 	 * Restart the Job Execution with the given executionId.
248 	 *
249 	 * @param jobExecutionId the executionId of the job execution to restart
250 	 */
251 	@RequestMapping(value = { "/{executionId}" }, method = RequestMethod.PUT, params = "restart=true")
252 	@ResponseStatus(HttpStatus.OK)
253 	public void restartJobExecution(@PathVariable("executionId") long jobExecutionId) throws NoSuchJobExecutionException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobInstanceAlreadyCompleteException, JobRestartException, NoSuchJobException {
254 
255 		final JobExecution jobExecution;
256 		try {
257 			jobExecution = jobService.getJobExecution(jobExecutionId);
258 		}
259 		catch (org.springframework.batch.core.launch.NoSuchJobExecutionException e) {
260 			throw new NoSuchJobExecutionException(String.format("Could not find jobExecution with id %s", String.valueOf(jobExecutionId)));
261 		}
262 
263 		if (jobExecution.isRunning()) {
264 			throw new JobExecutionAlreadyRunningException(
265 					"Job Execution for this job is already running: " + jobExecution.getJobInstance());
266 		}
267 
268 		final JobInstance lastInstance = jobExecution.getJobInstance();
269 		final JobParameters jobParameters = jobExecution.getJobParameters();
270 
271 		final Job job;
272 		try {
273 			job = jobLocator.getJob(lastInstance.getJobName());
274 		}
275 		catch (NoSuchJobException e1) {
276 			throw new NoSuchBatchJobException("The job '" + lastInstance.getJobName()
277 					+ "' does not exist.");
278 		}
279 		try {
280 			job.getJobParametersValidator().validate(jobParameters);
281 		}
282 		catch (JobParametersInvalidException e) {
283 			throw new JobParametersInvalidException(
284 					"The Job Parameters for Job Execution " + jobExecution.getId()
285 					+ " are invalid.");
286 		}
287 
288 		final BatchStatus status = jobExecution.getStatus();
289 
290 		if (status == BatchStatus.COMPLETED || status == BatchStatus.ABANDONED) {
291 			throw new JobInstanceAlreadyCompleteException(
292 					"Job Execution " + jobExecution.getId() + " is already complete.");
293 		}
294 
295 		if (!job.isRestartable()) {
296 			throw new JobRestartException(
297 					"The job '" + lastInstance.getJobName() + "' is not restartable.");
298 		}
299 
300 		jobService.launch(lastInstance.getJobName(), jobParameters);
301 	}
302 
303 	/**
304 	 * Stop all job executions.
305 	 */
306 	@RequestMapping(value = { "" }, method = RequestMethod.PUT, params = "stop=true")
307 	@ResponseStatus(HttpStatus.OK)
308 	public void stopAll() {
309 		jobService.stopAll();
310 	}
311 }