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 |
120 | .getLog(CommandLineJobRunner.class); |
121 | |
122 | private ExitCodeMapper exitCodeMapper = new SimpleJvmExitCodeMapper(); |
123 | |
124 | private JobLauncher launcher; |
125 | |
126 | private JobLocator jobLocator; |
127 | |
128 | private SystemExiter systemExiter = new JvmSystemExiter(); |
129 | |
130 | private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter(); |
131 | |
132 | /** |
133 | * Injection setter for the {@link JobLauncher}. |
134 | * |
135 | * @param launcher |
136 | * the launcher to set |
137 | */ |
138 | public void setLauncher(JobLauncher launcher) { |
139 | this.launcher = launcher; |
140 | } |
141 | |
142 | /** |
143 | * Injection setter for the {@link ExitCodeMapper}. |
144 | * |
145 | * @param exitCodeMapper |
146 | * the exitCodeMapper to set |
147 | */ |
148 | public void setExitCodeMapper(ExitCodeMapper exitCodeMapper) { |
149 | this.exitCodeMapper = exitCodeMapper; |
150 | } |
151 | |
152 | /** |
153 | * Injection setter for the {@link SystemExiter}. |
154 | * |
155 | * @param systemExitor |
156 | */ |
157 | public void setSystemExiter(SystemExiter systemExitor) { |
158 | this.systemExiter = systemExitor; |
159 | } |
160 | |
161 | /** |
162 | * Delegate to the exiter to (possibly) exit the VM gracefully. |
163 | * |
164 | * @param status |
165 | */ |
166 | public void exit(int status) { |
167 | systemExiter.exit(status); |
168 | } |
169 | |
170 | public void setJobLocator(JobLocator jobLocator) { |
171 | this.jobLocator = jobLocator; |
172 | } |
173 | |
174 | /* |
175 | * Start a job by obtaining a combined classpath using the job launcher and |
176 | * job paths. If a JobLocator has been set, then use it to obtain an actual |
177 | * job, if not ask the context for it. |
178 | */ |
179 | int start(String jobPath, String jobName, String[] parameters) { |
180 | |
181 | ConfigurableApplicationContext context = null; |
182 | |
183 | try { |
184 | context = new ClassPathXmlApplicationContext(jobPath); |
185 | context.getAutowireCapableBeanFactory().autowireBeanProperties( |
186 | this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); |
187 | |
188 | Job job; |
189 | if (jobLocator != null) { |
190 | job = jobLocator.getJob(jobName); |
191 | } else { |
192 | job = (Job) context.getBean(jobName); |
193 | } |
194 | |
195 | JobParameters jobParameters = jobParametersConverter |
196 | .getJobParameters(StringUtils |
197 | .splitArrayElementsIntoProperties(parameters, "=")); |
198 | |
199 | JobExecution jobExecution = launcher.run(job, jobParameters); |
200 | return exitCodeMapper.intValue(jobExecution.getExitStatus() |
201 | .getExitCode()); |
202 | } catch (Throwable e) { |
203 | logger.error("Job Terminated in error:", e); |
204 | return exitCodeMapper.intValue(ExitStatus.FAILED.getExitCode()); |
205 | } finally { |
206 | if (context!=null) { |
207 | context.close(); |
208 | } |
209 | } |
210 | } |
211 | |
212 | /** |
213 | * Launch a batch job using a {@link CommandLineJobRunner}. Creates a new |
214 | * Spring context for the job execution, and uses a common parent for all |
215 | * such contexts. No exception are thrown from this method, rather |
216 | * exceptions are logged and an integer returned through the exit status in |
217 | * a {@link JvmSystemExiter} (which can be overridden by defining one in the |
218 | * Spring context). |
219 | * |
220 | * @param args |
221 | * <p> |
222 | * <ul> |
223 | * <li>jobPath: the xml application context containing a |
224 | * {@link Job} |
225 | * <li>jobName: the bean id of the job. |
226 | * <li>jobLauncherPath: the xml application context containing a |
227 | * {@link JobLauncher} |
228 | * <li>jobParameters: 0 to many parameters that will be used to |
229 | * launch a job. |
230 | * </ul> |
231 | * </p> |
232 | */ |
233 | public static void main(String[] args) { |
234 | |
235 | CommandLineJobRunner command = new CommandLineJobRunner(); |
236 | |
237 | if (args.length < 2) { |
238 | logger |
239 | .error("At least 2 arguments are required: JobPath and JobName."); |
240 | command.exit(1); |
241 | } |
242 | |
243 | String jobPath = args[0]; |
244 | String jobName = args[1]; |
245 | String[] parameters = new String[args.length - 2]; |
246 | System.arraycopy(args, 2, parameters, 0, args.length - 2); |
247 | |
248 | int result = command.start(jobPath, jobName, parameters); |
249 | command.exit(result); |
250 | } |
251 | |
252 | } |