1 /*
2 * Copyright 2006-2007 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
17 package org.springframework.batch.test;
18
19 import java.util.HashMap;
20 import java.util.Map;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24 import org.springframework.batch.core.Job;
25 import org.springframework.batch.core.JobExecution;
26 import org.springframework.batch.core.JobParameter;
27 import org.springframework.batch.core.JobParameters;
28 import org.springframework.batch.core.Step;
29 import org.springframework.batch.core.job.AbstractJob;
30 import org.springframework.batch.core.job.SimpleJob;
31 import org.springframework.batch.core.job.flow.FlowJob;
32 import org.springframework.batch.core.launch.JobLauncher;
33 import org.springframework.batch.core.repository.JobRepository;
34 import org.springframework.batch.core.step.StepLocator;
35 import org.springframework.batch.item.ExecutionContext;
36 import org.springframework.beans.factory.annotation.Autowired;
37 import org.springframework.context.ApplicationContext;
38
39 /**
40 * <p>
41 * Utility class for testing batch jobs. It provides methods for launching an
42 * entire {@link AbstractJob}, allowing for end to end testing of individual
43 * steps, without having to run every step in the job. Any test classes using
44 * this utility can set up an instance in the {@link ApplicationContext} as part
45 * of the Spring test framework.
46 * </p>
47 *
48 * <p>
49 * This class also provides the ability to run {@link Step}s from a
50 * {@link FlowJob} or {@link SimpleJob} individually. By launching {@link Step}s
51 * within a {@link Job} on their own, end to end testing of individual steps can
52 * be performed without having to run every step in the job.
53 * </p>
54 *
55 * <p>
56 * It should be noted that using any of the methods that don't contain
57 * {@link JobParameters} in their signature, will result in one being created
58 * with the current system time as a parameter. This will ensure restartability
59 * when no parameters are provided.
60 * </p>
61 *
62 * @author Lucas Ward
63 * @author Dan Garrette
64 * @author Dave Syer
65 * @since 2.1
66 */
67 public class JobLauncherTestUtils {
68
69 private static final long JOB_PARAMETER_MAXIMUM = 1000000;
70
71 /** Logger */
72 protected final Log logger = LogFactory.getLog(getClass());
73
74 private JobLauncher jobLauncher;
75
76 private Job job;
77
78 private JobRepository jobRepository;
79
80 private StepRunner stepRunner;
81
82 /**
83 * The Job instance that can be manipulated (e.g. launched) in this utility.
84 *
85 * @param job the {@link AbstractJob} to use
86 */
87 @Autowired
88 public void setJob(Job job) {
89 this.job = job;
90 }
91
92 /**
93 * The {@link JobRepository} to use for creating new {@link JobExecution}
94 * instances.
95 *
96 * @param jobRepository a {@link JobRepository}
97 */
98 @Autowired
99 public void setJobRepository(JobRepository jobRepository) {
100 this.jobRepository = jobRepository;
101 }
102
103 /**
104 * @return the job repository
105 */
106 public JobRepository getJobRepository() {
107 return jobRepository;
108 }
109
110 /**
111 * @return the job
112 */
113 public Job getJob() {
114 return job;
115 }
116
117 /**
118 * A {@link JobLauncher} instance that can be used to launch jobs.
119 *
120 * @param jobLauncher a job launcher
121 */
122 @Autowired
123 public void setJobLauncher(JobLauncher jobLauncher) {
124 this.jobLauncher = jobLauncher;
125 }
126
127 /**
128 * @return the job launcher
129 */
130 public JobLauncher getJobLauncher() {
131 return jobLauncher;
132 }
133
134 /**
135 * Launch the entire job, including all steps.
136 *
137 * @return JobExecution, so that the test can validate the exit status
138 * @throws Exception
139 */
140 public JobExecution launchJob() throws Exception {
141 return this.launchJob(this.getUniqueJobParameters());
142 }
143
144 /**
145 * Launch the entire job, including all steps
146 *
147 * @param jobParameters
148 * @return JobExecution, so that the test can validate the exit status
149 * @throws Exception
150 */
151 public JobExecution launchJob(JobParameters jobParameters) throws Exception {
152 return getJobLauncher().run(this.job, jobParameters);
153 }
154
155 /**
156 * @return a new JobParameters object containing only a parameter for the
157 * current timestamp, to ensure that the job instance will be unique.
158 */
159 public JobParameters getUniqueJobParameters() {
160 Map<String, JobParameter> parameters = new HashMap<String, JobParameter>();
161 parameters.put("random", new JobParameter((long) (Math.random() * JOB_PARAMETER_MAXIMUM)));
162 return new JobParameters(parameters);
163 }
164
165 /**
166 * Convenient method for subclasses to grab a {@link StepRunner} for running
167 * steps by name.
168 *
169 * @return a {@link StepRunner}
170 */
171 protected StepRunner getStepRunner() {
172 if (this.stepRunner == null) {
173 this.stepRunner = new StepRunner(getJobLauncher(), getJobRepository());
174 }
175 return this.stepRunner;
176 }
177
178 /**
179 * Launch just the specified step in the job. A unique set of JobParameters
180 * will automatically be generated. An IllegalStateException is thrown if
181 * there is no Step with the given name.
182 *
183 * @param stepName The name of the step to launch
184 * @return JobExecution
185 */
186 public JobExecution launchStep(String stepName) {
187 return this.launchStep(stepName, this.getUniqueJobParameters(), null);
188 }
189
190 /**
191 * Launch just the specified step in the job. A unique set of JobParameters
192 * will automatically be generated. An IllegalStateException is thrown if
193 * there is no Step with the given name.
194 *
195 * @param stepName The name of the step to launch
196 * @param jobExecutionContext An ExecutionContext whose values will be
197 * loaded into the Job ExecutionContext prior to launching the step.
198 * @return JobExecution
199 */
200 public JobExecution launchStep(String stepName, ExecutionContext jobExecutionContext) {
201 return this.launchStep(stepName, this.getUniqueJobParameters(), jobExecutionContext);
202 }
203
204 /**
205 * Launch just the specified step in the job. An IllegalStateException is
206 * thrown if there is no Step with the given name.
207 *
208 * @param stepName The name of the step to launch
209 * @param jobParameters The JobParameters to use during the launch
210 * @return JobExecution
211 */
212 public JobExecution launchStep(String stepName, JobParameters jobParameters) {
213 return this.launchStep(stepName, jobParameters, null);
214 }
215
216 /**
217 * Launch just the specified step in the job. An IllegalStateException is
218 * thrown if there is no Step with the given name.
219 *
220 * @param stepName The name of the step to launch
221 * @param jobParameters The JobParameters to use during the launch
222 * @param jobExecutionContext An ExecutionContext whose values will be
223 * loaded into the Job ExecutionContext prior to launching the step.
224 * @return JobExecution
225 */
226 public JobExecution launchStep(String stepName, JobParameters jobParameters, ExecutionContext jobExecutionContext) {
227 if (!(job instanceof StepLocator)) {
228 throw new UnsupportedOperationException("Cannot locate step from a Job that is not a StepLocator: job="
229 + job.getName() + " does not implement StepLocator");
230 }
231 StepLocator locator = (StepLocator) this.job;
232 Step step = locator.getStep(stepName);
233 if (step == null) {
234 step = locator.getStep(this.job.getName() + "." + stepName);
235 }
236 if (step == null) {
237 throw new IllegalStateException("No Step found with name: [" + stepName + "]");
238 }
239 return getStepRunner().launchStep(step, jobParameters, jobExecutionContext);
240 }
241 }