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.ExitStatus;
23 import org.springframework.batch.core.Job;
24 import org.springframework.batch.core.JobExecution;
25 import org.springframework.batch.core.JobParameters;
26 import org.springframework.batch.core.configuration.JobLocator;
27 import org.springframework.batch.core.converter.DefaultJobParametersConverter;
28 import org.springframework.batch.core.converter.JobParametersConverter;
29 import org.springframework.batch.core.launch.JobLauncher;
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.Assert;
35 import org.springframework.util.StringUtils;
36
37 /**
38 * <p>
39 * Basic launcher for starting jobs from the command line. In general, it is
40 * assumed that this launcher will primarily be used to start a job via a script
41 * from an Enterprise Scheduler. Therefore, exit codes are mapped to integers so
42 * that schedulers can use the returned values to determine the next course of
43 * action. The returned values can also be useful to operations teams in
44 * determining what should happen upon failure. For example, a returned code of
45 * 5 might mean that some resource wasn't available and the job should be
46 * restarted. However, a code of 10 might mean that something critical has
47 * happened and the issue should be escalated.
48 * </p>
49 *
50 * <p>
51 * With any launch of a batch job within Spring Batch, a Spring context
52 * containing the {@link Job} and some execution context has to be created. This
53 * command line launcher can be used to load the job and its context from a
54 * single location. All dependencies of the launcher will then be satisfied by
55 * autowiring by type from the combined application context. Default values are
56 * provided for all fields except the {@link JobLauncher} and {@link JobLocator}.
57 * Therefore, if autowiring fails to set it (it should be noted that dependency
58 * checking is disabled because most of the fields have default values and thus
59 * don't require dependencies to be fulfilled via autowiring) then an exception
60 * will be thrown. It should also be noted that even if an exception is thrown
61 * by this class, it will be mapped 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 contain only one instance of
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 Assert.notNull(launcher,"A JobLauncher must be provided. Please add one to the configuration.");
195
196 Job job;
197 if (jobLocator != null) {
198 job = jobLocator.getJob(jobName);
199 }
200 else {
201 job = (Job) context.getBean(jobName);
202 }
203
204 JobParameters jobParameters = jobParametersConverter.getJobParameters(StringUtils
205 .splitArrayElementsIntoProperties(parameters, "="));
206
207 JobExecution jobExecution = launcher.run(job, jobParameters);
208 return exitCodeMapper.intValue(jobExecution.getExitStatus().getExitCode());
209 }
210 catch (Throwable e) {
211 logger.error("Job Terminated in error:", e);
212 return exitCodeMapper.intValue(ExitStatus.FAILED.getExitCode());
213 }
214 finally {
215 if (context != null) {
216 context.close();
217 }
218 }
219 }
220
221 /**
222 * Launch a batch job using a {@link CommandLineJobRunner}. Creates a new
223 * Spring context for the job execution, and uses a common parent for all
224 * such contexts. No exception are thrown from this method, rather
225 * exceptions are logged and an integer returned through the exit status in
226 * a {@link JvmSystemExiter} (which can be overridden by defining one in the
227 * Spring context).<br/> Parameters can be provided in the form key=value,
228 * and will be converted using the injected {@link JobParametersConverter}.
229 *
230 * @param args
231 * <p>
232 * <ul>
233 * <li>jobPath: the xml application context containing a {@link Job}
234 * <li>jobName: the bean id of the job.
235 * <li>jobParameters: 0 to many parameters that will be used to launch a
236 * job.
237 * </ul>
238 * </p>
239 */
240 public static void main(String[] args) {
241
242 CommandLineJobRunner command = new CommandLineJobRunner();
243
244 if (args.length < 2) {
245 logger.error("At least 2 arguments are required: JobPath and JobName.");
246 command.exit(1);
247 }
248
249 String jobPath = args[0];
250 String jobName = args[1];
251 String[] parameters = new String[args.length - 2];
252 System.arraycopy(args, 2, parameters, 0, args.length - 2);
253
254 int result = command.start(jobPath, jobName, parameters);
255 command.exit(result);
256 }
257
258 }