View Javadoc
1   /*
2    * Copyright 2009-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  package org.springframework.batch.admin.web;
17  
18  import java.io.IOException;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Comparator;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.TimeZone;
30  
31  import com.fasterxml.jackson.databind.ObjectMapper;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  
35  import org.springframework.batch.admin.domain.JobExecutionInfo;
36  import org.springframework.batch.admin.domain.JobInfo;
37  import org.springframework.batch.admin.domain.StepExecutionInfo;
38  import org.springframework.batch.admin.service.JobService;
39  import org.springframework.batch.core.JobExecution;
40  import org.springframework.batch.core.JobInstance;
41  import org.springframework.batch.core.JobParametersInvalidException;
42  import org.springframework.batch.core.StepExecution;
43  import org.springframework.batch.core.launch.JobExecutionNotRunningException;
44  import org.springframework.batch.core.launch.NoSuchJobException;
45  import org.springframework.batch.core.launch.NoSuchJobExecutionException;
46  import org.springframework.batch.core.launch.NoSuchJobInstanceException;
47  import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
48  import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
49  import org.springframework.batch.core.repository.JobRestartException;
50  import org.springframework.beans.factory.annotation.Autowired;
51  import org.springframework.beans.factory.annotation.Qualifier;
52  import org.springframework.stereotype.Controller;
53  import org.springframework.ui.Model;
54  import org.springframework.ui.ModelMap;
55  import org.springframework.validation.Errors;
56  import org.springframework.web.bind.annotation.ModelAttribute;
57  import org.springframework.web.bind.annotation.PathVariable;
58  import org.springframework.web.bind.annotation.RequestMapping;
59  import org.springframework.web.bind.annotation.RequestMethod;
60  import org.springframework.web.bind.annotation.RequestParam;
61  
62  /**
63   * Controller for job executions.
64   * 
65   * @author Dave Syer
66   * 
67   */
68  @Controller
69  public class JobExecutionController {
70  	private static Log logger = LogFactory.getLog(JobExecutionController.class);
71  
72  	public static class StopRequest {
73  		private Long jobExecutionId;
74  
75  		public Long getJobExecutionId() {
76  			return jobExecutionId;
77  		}
78  
79  		public void setJobExecutionId(Long jobExecutionId) {
80  			this.jobExecutionId = jobExecutionId;
81  		}
82  
83  	}
84  
85  	private JobService jobService;
86  	private ObjectMapper objectMapper;
87  	private TimeZone timeZone = TimeZone.getDefault();
88  
89  	/**
90  	 * @param timeZone the timeZone to set
91  	 */
92  	@Autowired(required = false)
93  	@Qualifier("userTimeZone")
94  	public void setTimeZone(TimeZone timeZone) {
95  		this.timeZone = timeZone;
96  	}
97  
98  	@Autowired
99  	public void setObjectMapper(ObjectMapper objectMapper) {
100 		this.objectMapper = objectMapper;
101 	}
102 
103 	@Autowired
104 	public JobExecutionController(JobService jobService) {
105 		super();
106 		this.jobService = jobService;
107 	}
108 
109 	@RequestMapping(value = "/jobs/executions/{jobExecutionId}", method = RequestMethod.DELETE)
110 	public String stop(Model model, @ModelAttribute("stopRequest") StopRequest stopRequest, Errors errors,
111 			@PathVariable Long jobExecutionId) {
112 
113 		stopRequest.jobExecutionId = jobExecutionId;
114 		try {
115 			JobExecution jobExecution = jobService.stop(jobExecutionId);
116 			model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
117 		}
118 		catch (NoSuchJobExecutionException e) {
119 			errors.reject("no.such.job.execution", new Object[] { jobExecutionId }, "No job exection with id="
120 					+ jobExecutionId);
121 		}
122 		catch (JobExecutionNotRunningException e) {
123 			errors.reject("job.execution.not.running", "Job exection with id=" + jobExecutionId + " is not running.");
124 			JobExecution jobExecution;
125 			try {
126 				jobExecution = jobService.getJobExecution(jobExecutionId);
127 				model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
128 			}
129 			catch (NoSuchJobExecutionException e1) {
130 				// safe
131 			}
132 		}
133 
134 		return "jobs/execution";
135 
136 	}
137 
138 	@RequestMapping(value = "/jobs/executions/{jobExecutionId}", method = RequestMethod.DELETE, params = "abandon")
139 	public String abandon(Model model, @ModelAttribute("stopRequest") StopRequest stopRequest, Errors errors,
140 			@PathVariable Long jobExecutionId) {
141 
142 		stopRequest.jobExecutionId = jobExecutionId;
143 		try {
144 			JobExecution jobExecution = jobService.abandon(jobExecutionId);
145 			model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
146 		}
147 		catch (NoSuchJobExecutionException e) {
148 			errors.reject("no.such.job.execution", new Object[] { jobExecutionId }, "No job exection with id="
149 					+ jobExecutionId);
150 		}
151 		catch (JobExecutionAlreadyRunningException e) {
152 			errors.reject("job.execution.running", "Job exection with id=" + jobExecutionId + " is running.");
153 			JobExecution jobExecution;
154 			try {
155 				jobExecution = jobService.getJobExecution(jobExecutionId);
156 				model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
157 			}
158 			catch (NoSuchJobExecutionException e1) {
159 				// safe
160 			}
161 		}
162 
163 		return "jobs/execution";
164 
165 	}
166 
167 	@RequestMapping(value = { "/jobs/executions", "/jobs/executions.*" }, method = RequestMethod.GET)
168 	public @ModelAttribute("jobExecutions")
169 	Collection<JobExecutionInfo> list(ModelMap model, @RequestParam(defaultValue = "0") int startJobExecution,
170 			@RequestParam(defaultValue = "20") int pageSize) {
171 
172 		int total = jobService.countJobExecutions();
173 		TableUtils.addPagination(model, total, startJobExecution, pageSize, "JobExecution");
174 
175 		Collection<JobExecutionInfo> result = new ArrayList<JobExecutionInfo>();
176 		for (JobExecution jobExecution : jobService.listJobExecutions(startJobExecution, pageSize)) {
177 			result.add(new JobExecutionInfo(jobExecution, timeZone));
178 		}
179 
180 		return result;
181 
182 	}
183 
184 	@RequestMapping(value = { "/jobs/{jobName}/{jobInstanceId}/executions", "/jobs/{jobName}/{jobInstanceId}" }, method = RequestMethod.GET)
185 	public String listForInstance(Model model, @PathVariable String jobName, @PathVariable long jobInstanceId,
186 			@ModelAttribute("date") Date date, Errors errors) {
187 
188 		JobInstance jobInstance = null;
189 		try {
190 			jobInstance = jobService.getJobInstance(jobInstanceId);
191 			if (!jobInstance.getJobName().equals(jobName)) {
192 				errors.reject("wrong.job.name", new Object[] { jobInstanceId, jobInstance.getJobName(), jobName },
193 						"The JobInstance with id=" + jobInstanceId + " has the wrong name (" + jobInstance.getJobName()
194 						+ " not " + jobName);
195 			}
196 		}
197 		catch (NoSuchJobInstanceException e) {
198 			errors.reject("no.such.job.instance", new Object[] { jobInstanceId }, "There is no such job instance ("
199 					+ jobInstanceId + ")");
200 		}
201 		if (jobInstance != null && (errors == null || !errors.hasErrors())) {
202 			Collection<JobExecutionInfo> result = new ArrayList<JobExecutionInfo>();
203 			try {
204 				Collection<JobExecution> jobExecutions = jobService.getJobExecutionsForJobInstance(jobName,
205 						jobInstanceId);
206 				for (JobExecution jobExecution : jobExecutions) {
207 					result.add(new JobExecutionInfo(jobExecution, timeZone));
208 				}
209 			}
210 			catch (NoSuchJobException e) {
211 				errors.reject("no.such.job", new Object[] { jobName }, "There is no such job (" + jobName + ")");
212 			}
213 			model.addAttribute(new JobInfo(jobName, result.size(), jobInstanceId, jobService.isLaunchable(jobName), jobService.isIncrementable(jobName)));
214 			model.addAttribute("jobExecutions", result);
215 		}
216 		return "jobs/executions";
217 
218 	}
219 
220 	@RequestMapping(value = "/jobs/{jobName}/{jobInstanceId}/executions", method = RequestMethod.POST)
221 	public String restart(Model model, @PathVariable String jobName, @PathVariable long jobInstanceId,
222 			@ModelAttribute("date") Date date, Errors errors) {
223 
224 		try {
225 
226 			Collection<JobExecution> jobExecutions = jobService.getJobExecutionsForJobInstance(jobName, jobInstanceId);
227 			model.addAttribute(new JobInfo(jobName, jobExecutions.size() + 1));
228 			JobExecution jobExecution = jobExecutions.iterator().next();
229 			model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
230 
231 			Long jobExecutionId = jobExecution.getId();
232 
233 			try {
234 
235 				jobExecution = jobService.restart(jobExecutionId);
236 				model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
237 
238 			}
239 			catch (NoSuchJobExecutionException e) {
240 				errors.reject("no.such.job.execution", new Object[] { jobExecutionId },
241 						"There is no such job execution (" + jobExecutionId + ")");
242 			}
243 			catch (JobExecutionAlreadyRunningException e) {
244 				errors.reject("job.execution.already.running", new Object[] { jobExecutionId },
245 						"This job execution is already running (" + jobExecutionId + ")");
246 			}
247 			catch (JobRestartException e) {
248 				errors.reject("job.restart.exception", new Object[] { jobName },
249 						"There was a problem restarting the job (" + jobName + ")");
250 			}
251 			catch (JobInstanceAlreadyCompleteException e) {
252 				errors.reject("job.instance.already.complete", new Object[] { jobName },
253 						"The job instance is already complete for (" + jobName
254 						+ "). Use different job parameters to launch it again.");
255 			}
256 			catch (JobParametersInvalidException e) {
257 				errors.reject("job.parameters.invalid", new Object[] { jobName },
258 						"The job parameters are invalid according to the job (" + jobName + ")");
259 			}
260 
261 		}
262 		catch (NoSuchJobException e) {
263 			errors.reject("no.such.job", new Object[] { jobName }, "There is no such job (" + jobName + ")");
264 		}
265 
266 		return "jobs/execution";
267 
268 	}
269 
270 	@RequestMapping(value = "/jobs/executions", method = RequestMethod.DELETE)
271 	public @ModelAttribute("jobExecutions")
272 	Collection<JobExecutionInfo> stopAll(ModelMap model, @RequestParam(defaultValue = "0") int startJobExecution,
273 			@RequestParam(defaultValue = "20") int pageSize) {
274 
275 		model.addAttribute("stoppedCount", jobService.stopAll());
276 		return list(model, startJobExecution, pageSize);
277 
278 	}
279 
280 	@RequestMapping(value = "/jobs/{jobName}/executions", method = RequestMethod.GET)
281 	public String listForJob(ModelMap model, @PathVariable String jobName, @ModelAttribute("date") Date date,
282 			Errors errors, @RequestParam(defaultValue = "0") int startJobExecution,
283 			@RequestParam(defaultValue = "20") int pageSize) {
284 
285 		int total = startJobExecution;
286 		try {
287 			total = jobService.countJobExecutionsForJob(jobName);
288 		}
289 		catch (NoSuchJobException e) {
290 			errors.reject("no.such.job", new Object[] { jobName }, "There is no such job (" + jobName + ")");
291 			logger.warn("Could not locate Job with name=" + jobName);
292 			return "jobs/executions";
293 		}
294 		TableUtils.addPagination(model, total, startJobExecution, pageSize, "JobExecution");
295 
296 		Collection<JobExecutionInfo> result = new ArrayList<JobExecutionInfo>();
297 		try {
298 
299 			for (JobExecution jobExecution : jobService.listJobExecutionsForJob(jobName, startJobExecution, pageSize)) {
300 				result.add(new JobExecutionInfo(jobExecution, timeZone));
301 			}
302 			int count = jobService.countJobExecutionsForJob(jobName);
303 			model.addAttribute(new JobInfo(jobName, count, null, jobService.isLaunchable(jobName), jobService
304 					.isIncrementable(jobName)));
305 			model.addAttribute("jobExecutions", result);
306 
307 		}
308 		catch (NoSuchJobException e) {
309 			errors.reject("no.such.job", new Object[] { jobName }, "There is no such job (" + jobName + ")");
310 			logger.warn("Could not locate Job with name=" + jobName);
311 		}
312 
313 		return "jobs/executions";
314 
315 	}
316 
317 	@RequestMapping(value = "/jobs/executions/{jobExecutionId}", method = RequestMethod.GET)
318 	public String detail(Model model, @PathVariable Long jobExecutionId, @ModelAttribute("date") Date date,
319 			Errors errors) {
320 
321 		try {
322 			JobExecution jobExecution = jobService.getJobExecution(jobExecutionId);
323 			model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
324 			String jobName = jobExecution.getJobInstance().getJobName();
325 //			Collection<String> stepNames = new HashSet<String>(jobService.getStepNamesForJob(jobName));
326 //			Collection<StepExecution> stepExecutions = new ArrayList<StepExecution>(jobExecution.getStepExecutions());
327 			List<StepExecutionInfo> stepExecutionInfos = new ArrayList<StepExecutionInfo>();
328 
329 			for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
330 				stepExecutionInfos.add(new StepExecutionInfo(stepExecution, timeZone));
331 			}
332 
333 //			for (String name : stepNames) {
334 //				boolean found = false;
335 //				for (Iterator<StepExecution> iterator = stepExecutions.iterator(); iterator.hasNext();) {
336 //					StepExecution stepExecution = iterator.next();
337 //					if (stepExecution.getStepName().equals(name)) {
338 //						stepExecutionInfos.add(new StepExecutionInfo(stepExecution, timeZone));
339 //						iterator.remove();
340 //						found = true;
341 //					}
342 //				}
343 //				if (!found) {
344 //					stepExecutionInfos.add(new StepExecutionInfo(jobName, jobExecutionId, name, timeZone));
345 //				}
346 //			}
347 
348 			Collections.sort(stepExecutionInfos, new Comparator<StepExecutionInfo>() {
349 				@Override
350 				public int compare(StepExecutionInfo o1, StepExecutionInfo o2) {
351 					return o1.getId().compareTo(o2.getId());
352 				}
353 			});
354 
355 			model.addAttribute("stepExecutionInfos", stepExecutionInfos);
356 		}
357 		catch (NoSuchJobExecutionException e) {
358 			errors.reject("no.such.job.execution", new Object[] { jobExecutionId }, "There is no such job execution ("
359 					+ jobExecutionId + ")");
360 		}
361 //		catch (NoSuchJobException e) {
362 //			errors.reject("no.such.job", new Object[] { jobExecutionId }, "There is no such job with exeuction id ("
363 //					+ jobExecutionId + ")");
364 //		}
365 
366 		return "jobs/execution";
367 
368 	}
369 
370 	@RequestMapping(value = "/jobs/executions/{jobExecutionId}/execution-context", method = RequestMethod.GET)
371 	public String getExecutionContext(Model model, @PathVariable Long jobExecutionId, @ModelAttribute("date") Date date,
372 									  Errors errors) {
373 		try {
374 			JobExecution jobExecution = jobService.getJobExecution(jobExecutionId);
375 			Map<String, Object> executionMap = new HashMap<String, Object>();
376 
377 			for (Map.Entry<String, Object> entry : jobExecution.getExecutionContext().entrySet()) {
378 				executionMap.put(entry.getKey(), entry.getValue());
379 			}
380 
381 			model.addAttribute("jobExecutionContext", objectMapper.writeValueAsString(executionMap));
382 			model.addAttribute("jobExecutionId", jobExecutionId);
383 		}
384 		catch (NoSuchJobExecutionException e) {
385 			errors.reject("no.such.job.execution", new Object[] { jobExecutionId }, "There is no such job execution ("
386 					+ jobExecutionId + ")");
387 		} catch (IOException e) {
388 			errors.reject("serialization.error", new Object[] { jobExecutionId }, "Error serializing execution context for job execution ("
389 					+ jobExecutionId + ")");
390 		}
391 
392 		return "jobs/executions/execution-context";
393 	}
394 }