1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
64
65
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
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
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
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
326
327 List<StepExecutionInfo> stepExecutionInfos = new ArrayList<StepExecutionInfo>();
328
329 for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
330 stepExecutionInfos.add(new StepExecutionInfo(stepExecution, timeZone));
331 }
332
333
334
335
336
337
338
339
340
341
342
343
344
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
362
363
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 }