1 | /* |
2 | * Copyright 2006-2008 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.core.launch.support; |
17 | |
18 | import java.util.Properties; |
19 | |
20 | import org.apache.commons.logging.Log; |
21 | import org.apache.commons.logging.LogFactory; |
22 | import org.springframework.batch.core.Job; |
23 | import org.springframework.batch.core.JobExecution; |
24 | import org.springframework.batch.core.JobParameters; |
25 | import org.springframework.batch.core.configuration.JobLocator; |
26 | import org.springframework.batch.core.converter.DefaultJobParametersConverter; |
27 | import org.springframework.batch.core.converter.JobParametersConverter; |
28 | import org.springframework.batch.core.launch.JobLauncher; |
29 | import org.springframework.batch.repeat.ExitStatus; |
30 | import org.springframework.beans.factory.BeanDefinitionStoreException; |
31 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory; |
32 | import org.springframework.context.ConfigurableApplicationContext; |
33 | import org.springframework.context.support.ClassPathXmlApplicationContext; |
34 | import org.springframework.util.StringUtils; |
35 | |
36 | /** |
37 | * <p> |
38 | * Basic launcher for starting jobs from the command line. In general, it is |
39 | * assumed that this launcher will primarily be used to start a job via a script |
40 | * from an Enterprise Scheduler. Therefore, exit codes are mapped to integers so |
41 | * that schedulers can use the returned values to determine the next course of |
42 | * action. The returned values can also be useful to operations teams in |
43 | * determining what should happen upon failure. For example, a returned code of |
44 | * 5 might mean that some resource wasn't available and the job should be |
45 | * restarted. However, a code of 10 might mean that something critical has |
46 | * happened and the issue should be escalated. |
47 | * </p> |
48 | * |
49 | * <p> |
50 | * With any launch of a batch job within Spring Batch, a Spring context |
51 | * containing the Job and the 'Execution Environment' has to be created. This |
52 | * command line launcher can be used to load that context from a single |
53 | * location. It can also load the job as well All dependencies of the launcher |
54 | * will then be satisfied by autowiring by type from the combined application |
55 | * context. Default values are provided for all fields except the |
56 | * {@link JobLauncher} and {@link JobLocator}. Therefore, if autowiring fails |
57 | * to set it (it should be noted that dependency checking is disabled because |
58 | * most of the fields have default values and thus don't require dependencies to |
59 | * be fulfilled via autowiring) then an exception will be thrown. It should also |
60 | * be noted that even if an exception is thrown by this class, it will be mapped |
61 | * to an integer and returned. |
62 | * </p> |
63 | * |
64 | * <p> |
65 | * Notice a property is available to set the {@link SystemExiter}. This class |
66 | * is used to exit from the main method, rather than calling System.exit() |
67 | * directly. This is because unit testing a class the calls System.exit() is |
68 | * impossible without kicking off the test within a new Jvm, which it is |
69 | * possible to do, however it is a complex solution, much more so than |
70 | * strategizing the exiter. |
71 | * </p> |
72 | * |
73 | * <p> |
74 | * The arguments to this class are roughly as follows: |
75 | * </p> |
76 | * |
77 | * <code> |
78 | * java jobPath jobName jobLauncherPath jobParameters... |
79 | * </code> |
80 | * |
81 | * <p> |
82 | * <ul> |
83 | * <li>jobPath: the xml application context containing a {@link Job} |
84 | * <li>jobName: the bean id of the job. |
85 | * <li>jobLauncherPath: the xml application context containing a |
86 | * {@link JobLauncher} |
87 | * <li>jobParameters: 0 to many parameters that will be used to launch a job. |
88 | * </ul> |
89 | * </p> |
90 | * |
91 | * <p> |
92 | * The combined application context must only contain one instance of a |
93 | * {@link JobLauncher}. The job parameters passed in to the command line will |
94 | * be converted to {@link Properties} by assuming that each individual element |
95 | * is one parameter that is separated by an equals sign. For example, |
96 | * "vendor.id=290232". Below is an example arguments list: " |
97 | * |
98 | * <p> |
99 | * <code> |
100 | * java org.springframework.batch.execution.bootstrap.support.CommandLineJobRunner testJob.xml |
101 | * testJob schedule.date=2008/01/24 vendor.id=3902483920 |
102 | * <code></p> |
103 | * |
104 | * <p>Once arguments have been successfully parsed, autowiring will be used to set |
105 | * various dependencies. The {@JobLauncher} for example, will be loaded this way. If |
106 | * none is contained in the bean factory (it searches by type) then a |
107 | * {@link BeanDefinitionStoreException} will be thrown. The same exception will also |
108 | * be thrown if there is more than one present. Assuming the JobLauncher has been |
109 | * set correctly, the jobName argument will be used to obtain an actual {@link Job}. |
110 | * If a {@link JobLocator} has been set, then it will be used, if not the beanFactory |
111 | * will be asked, using the jobName as the bean id.</p> |
112 | * |
113 | * @author Dave Syer |
114 | * @author Lucas Ward |
115 | * @since 1.0 |
116 | */ |
117 | public class CommandLineJobRunner { |
118 | |
119 | protected static final Log logger = LogFactory.getLog(CommandLineJobRunner.class); |
120 | |
121 | private ExitCodeMapper exitCodeMapper = new SimpleJvmExitCodeMapper(); |
122 | |
123 | private JobLauncher launcher; |
124 | |
125 | private JobLocator jobLocator; |
126 | |
127 | private SystemExiter systemExiter = new JvmSystemExiter(); |
128 | |
129 | private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter(); |
130 | |
131 | /** |
132 | * Injection setter for the {@link JobLauncher}. |
133 | * |
134 | * @param launcher the launcher to set |
135 | */ |
136 | public void setLauncher(JobLauncher launcher) { |
137 | this.launcher = launcher; |
138 | } |
139 | |
140 | /** |
141 | * Injection setter for the {@link ExitCodeMapper}. |
142 | * |
143 | * @param exitCodeMapper the exitCodeMapper to set |
144 | */ |
145 | public void setExitCodeMapper(ExitCodeMapper exitCodeMapper) { |
146 | this.exitCodeMapper = exitCodeMapper; |
147 | } |
148 | |
149 | /** |
150 | * Injection setter for the {@link SystemExiter}. |
151 | * |
152 | * @param systemExitor |
153 | */ |
154 | public void setSystemExiter(SystemExiter systemExitor) { |
155 | this.systemExiter = systemExitor; |
156 | } |
157 | |
158 | /** |
159 | * Injection setter for {@link JobParametersConverter}. |
160 | * |
161 | * @param jobParametersConverter |
162 | */ |
163 | public void setJobParametersConverter(JobParametersConverter jobParametersConverter) { |
164 | this.jobParametersConverter = jobParametersConverter; |
165 | } |
166 | |
167 | /** |
168 | * Delegate to the exiter to (possibly) exit the VM gracefully. |
169 | * |
170 | * @param status |
171 | */ |
172 | public void exit(int status) { |
173 | systemExiter.exit(status); |
174 | } |
175 | |
176 | public void setJobLocator(JobLocator jobLocator) { |
177 | this.jobLocator = jobLocator; |
178 | } |
179 | |
180 | /* |
181 | * Start a job by obtaining a combined classpath using the job launcher and |
182 | * job paths. If a JobLocator has been set, then use it to obtain an actual |
183 | * job, if not ask the context for it. |
184 | */ |
185 | int start(String jobPath, String jobName, String[] parameters) { |
186 | |
187 | ConfigurableApplicationContext context = null; |
188 | |
189 | try { |
190 | context = new ClassPathXmlApplicationContext(jobPath); |
191 | context.getAutowireCapableBeanFactory().autowireBeanProperties(this, |
192 | AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); |
193 | |
194 | Job job; |
195 | if (jobLocator != null) { |
196 | job = jobLocator.getJob(jobName); |
197 | } |
198 | else { |
199 | job = (Job) context.getBean(jobName); |
200 | } |
201 | |
202 | JobParameters jobParameters = jobParametersConverter.getJobParameters(StringUtils |
203 | .splitArrayElementsIntoProperties(parameters, "=")); |
204 | |
205 | JobExecution jobExecution = launcher.run(job, jobParameters); |
206 | return exitCodeMapper.intValue(jobExecution.getExitStatus().getExitCode()); |
207 | } |
208 | catch (Throwable e) { |
209 | logger.error("Job Terminated in error:", e); |
210 | return exitCodeMapper.intValue(ExitStatus.FAILED.getExitCode()); |
211 | } |
212 | finally { |
213 | if (context != null) { |
214 | context.close(); |
215 | } |
216 | } |
217 | } |
218 | |
219 | /** |
220 | * Launch a batch job using a {@link CommandLineJobRunner}. Creates a new |
221 | * Spring context for the job execution, and uses a common parent for all |
222 | * such contexts. No exception are thrown from this method, rather |
223 | * exceptions are logged and an integer returned through the exit status in |
224 | * a {@link JvmSystemExiter} (which can be overridden by defining one in the |
225 | * Spring context).<br/> Parameters can be provided in the form key=value, |
226 | * and will be converted using the injected {@link JobParametersConverter}. |
227 | * |
228 | * @param args |
229 | * <p> |
230 | * <ul> |
231 | * <li>jobPath: the xml application context containing a {@link Job} |
232 | * <li>jobName: the bean id of the job. |
233 | * <li>jobParameters: 0 to many parameters that will be used to launch a |
234 | * job. |
235 | * </ul> |
236 | * </p> |
237 | */ |
238 | public static void main(String[] args) { |
239 | |
240 | CommandLineJobRunner command = new CommandLineJobRunner(); |
241 | |
242 | if (args.length < 2) { |
243 | logger.error("At least 2 arguments are required: JobPath and JobName."); |
244 | command.exit(1); |
245 | } |
246 | |
247 | String jobPath = args[0]; |
248 | String jobName = args[1]; |
249 | String[] parameters = new String[args.length - 2]; |
250 | System.arraycopy(args, 2, parameters, 0, args.length - 2); |
251 | |
252 | int result = command.start(jobPath, jobName, parameters); |
253 | command.exit(result); |
254 | } |
255 | |
256 | } |