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.util.ArrayList;
19  import java.util.Arrays;
20  import java.util.Collection;
21  import java.util.HashSet;
22  import java.util.LinkedHashSet;
23  import java.util.List;
24  import java.util.TimeZone;
25  
26  import javax.servlet.http.HttpServletRequest;
27  
28  import org.springframework.batch.admin.domain.JobExecutionInfo;
29  import org.springframework.batch.admin.domain.JobInfo;
30  import org.springframework.batch.admin.domain.support.JobParametersExtractor;
31  import org.springframework.batch.admin.service.JobService;
32  import org.springframework.batch.core.JobExecution;
33  import org.springframework.batch.core.JobInstance;
34  import org.springframework.batch.core.JobParameters;
35  import org.springframework.batch.core.JobParametersInvalidException;
36  import org.springframework.batch.core.launch.NoSuchJobException;
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.beans.factory.annotation.Qualifier;
42  import org.springframework.stereotype.Controller;
43  import org.springframework.ui.ModelMap;
44  import org.springframework.util.StringUtils;
45  import org.springframework.validation.Errors;
46  import org.springframework.web.bind.annotation.ModelAttribute;
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.util.HtmlUtils;
51  
52  /**
53   * Controller for listing and launching jobs.
54   * 
55   * @author Dave Syer
56   * @author Michael Minella
57   * 
58   */
59  @Controller
60  public class JobController {
61  
62  	private final JobService jobService;
63  
64  	private Collection<String> extensions = new HashSet<String>();
65  
66  	private TimeZone timeZone = TimeZone.getDefault();
67  
68  	private JobParametersExtractor jobParametersExtractor = new JobParametersExtractor();
69  
70  	/**
71  	 * A collection of extensions that may be appended to request urls aimed at
72  	 * this controller.
73  	 * 
74  	 * @param extensions the extensions (e.g. [rss, xml, atom])
75  	 */
76  	public void setExtensions(Collection<String> extensions) {
77  		this.extensions = new LinkedHashSet<String>(extensions);
78  	}
79  
80  	/**
81  	 * @param timeZone the timeZone to set
82  	 */
83  	@Autowired(required = false)
84  	@Qualifier("userTimeZone")
85  	public void setTimeZone(TimeZone timeZone) {
86  		this.timeZone = timeZone;
87  	}
88  
89  	@Autowired
90  	public JobController(JobService jobService) {
91  		super();
92  		this.jobService = jobService;
93  		extensions.addAll(Arrays.asList(".html", ".json", ".rss"));
94  	}
95  
96  	@ModelAttribute("jobName")
97  	public String getJobName(HttpServletRequest request) {
98  		String path = request.getPathInfo();
99  		int index = path.lastIndexOf("jobs/") + 5;
100 		if (index >= 0) {
101 			path = path.substring(index);
102 		}
103 		if (!path.contains(".")) {
104 			return path;
105 		}
106 		for (String extension : extensions) {
107 			if (path.endsWith(extension)) {
108 				path = StringUtils.stripFilenameExtension(path);
109 				// Only remove one extension so a job can be called job.html and
110 				// still be addressed
111 				break;
112 			}
113 		}
114 		return path;
115 	}
116 
117 	@RequestMapping(value = "/jobs/{jobName}", method = RequestMethod.POST)
118 	public String launch(ModelMap model, @ModelAttribute("jobName") String jobName,
119 			@ModelAttribute("launchRequest") LaunchRequest launchRequest, Errors errors,
120 			@RequestParam(defaultValue = "execution") String origin) {
121 
122 		launchRequest.setJobName(jobName);
123 		String params = launchRequest.jobParameters;
124 
125 		JobParameters jobParameters = jobParametersExtractor.fromString(params);
126 
127 		try {
128 			JobExecution jobExecution = jobService.launch(jobName, jobParameters);
129 			model.addAttribute(new JobExecutionInfo(jobExecution, timeZone));
130 		}
131 		catch (NoSuchJobException e) {
132 			errors.reject("no.such.job", new Object[] { jobName }, "No such job: " + jobName);
133 		}
134 		catch (JobExecutionAlreadyRunningException e) {
135 			errors.reject("job.already.running", "A job with this name and parameters is already running.");
136 		}
137 		catch (JobRestartException e) {
138 			errors.reject("job.could.not.restart", "The job was not able to restart.");
139 		}
140 		catch (JobInstanceAlreadyCompleteException e) {
141 			errors.reject("job.already.complete", "A job with this name and parameters already completed successfully.");
142 		}
143 		catch (JobParametersInvalidException e) {
144 			errors.reject("job.parameters.invalid", "The job parameters are invalid according to the configuration.");
145 		}
146 
147 		if (!"job".equals(origin)) {
148 			// if the origin is not specified we are probably not a UI client
149 			return "jobs/execution";
150 		}
151 		else {
152 			// In the UI we show the same page again...
153 			return details(model, jobName, errors, 0, 20);
154 		}
155 
156 		// Not a redirect because normally it is requested by an Ajax call so
157 		// there's less of a pressing need for one (the browser history won't
158 		// contain the request).
159 
160 	}
161 
162 	@RequestMapping(value = "/jobs/{jobName}", method = RequestMethod.GET)
163 	public String details(ModelMap model, @ModelAttribute("jobName") String jobName, Errors errors,
164 			@RequestParam(defaultValue = "0") int startJobInstance, @RequestParam(defaultValue = "20") int pageSize) {
165 
166 		boolean launchable = jobService.isLaunchable(jobName);
167 
168 		try {
169 
170 			Collection<JobInstance> result = jobService.listJobInstances(jobName, startJobInstance, pageSize);
171 			Collection<JobInstanceInfo> jobInstances = new ArrayList<JobInstanceInfo>();
172 			model.addAttribute("jobParameters", jobParametersExtractor.fromJobParameters(jobService.getLastJobParameters(jobName)));
173 
174 			for (JobInstance jobInstance : result) {
175 				Collection<JobExecution> jobExecutions = jobService.getJobExecutionsForJobInstance(jobName, jobInstance.getId());
176 				jobInstances.add(new JobInstanceInfo(jobInstance, jobExecutions, timeZone));
177 			}
178 
179 			model.addAttribute("jobInstances", jobInstances);
180 			int total = jobService.countJobInstances(jobName);
181 			TableUtils.addPagination(model, total, startJobInstance, pageSize, "JobInstance");
182 			int count = jobService.countJobExecutionsForJob(jobName);
183 			model.addAttribute("jobInfo", new JobInfo(jobName, count, launchable, jobService.isIncrementable(jobName)));
184 
185 		}
186 		catch (NoSuchJobException e) {
187 			errors.reject("no.such.job", new Object[] { jobName },
188 					"There is no such job (" + HtmlUtils.htmlEscape(jobName) + ")");
189 		}
190 
191 		return "jobs/job";
192 
193 	}
194 
195 	@RequestMapping(value = "/jobs", method = RequestMethod.GET)
196 	public void jobs(ModelMap model, @RequestParam(defaultValue = "0") int startJob,
197 			@RequestParam(defaultValue = "20") int pageSize) {
198 		int total = jobService.countJobs();
199 		TableUtils.addPagination(model, total, startJob, pageSize, "Job");
200 		Collection<String> names = jobService.listJobs(startJob, pageSize);
201 		List<JobInfo> jobs = new ArrayList<JobInfo>();
202 		for (String name : names) {
203 			int count = 0;
204 			try {
205 				count = jobService.countJobExecutionsForJob(name);
206 			}
207 			catch (NoSuchJobException e) {
208 				// shouldn't happen
209 			}
210 			boolean launchable = jobService.isLaunchable(name);
211 			boolean incrementable = jobService.isIncrementable(name);
212 			jobs.add(new JobInfo(name, count, null, launchable, incrementable));
213 		}
214 		model.addAttribute("jobs", jobs);
215 	}
216 
217 }