View Javadoc

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 }