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.ArrayList;
20  import java.util.Date;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.springframework.batch.core.Job;
28  import org.springframework.batch.core.JobExecution;
29  import org.springframework.batch.core.JobExecutionListener;
30  import org.springframework.batch.core.JobParameter;
31  import org.springframework.batch.core.JobParameters;
32  import org.springframework.batch.core.JobParametersInvalidException;
33  import org.springframework.batch.core.Step;
34  import org.springframework.batch.core.UnexpectedJobExecutionException;
35  import org.springframework.batch.core.job.SimpleJob;
36  import org.springframework.batch.core.launch.JobLauncher;
37  import org.springframework.batch.core.listener.JobExecutionListenerSupport;
38  import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
39  import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
40  import org.springframework.batch.core.repository.JobRepository;
41  import org.springframework.batch.core.repository.JobRestartException;
42  import org.springframework.batch.item.ExecutionContext;
43  
44  /**
45   * Utility class for executing steps outside of a {@link Job}. This is useful in
46   * end to end testing in order to allow for the testing of a step individually
47   * without running every Step in a job.
48   * 
49   * <ul>
50   * <li><b>launchStep(Step step)</b>: Launch the step with new parameters each
51   * time. (The current system time will be used)
52   * <li><b>launchStep(Step step, JobParameters jobParameters)</b>: Launch the
53   * specified step with the provided JobParameters. This may be useful if your
54   * step requires a certain parameter during runtime.
55   * </ul>
56   * 
57   * It should be noted that any checked exceptions encountered while running the
58   * Step will wrapped with RuntimeException. Any checked exception thrown will be
59   * due to a framework error, not the logic of the step, and thus requiring a
60   * throws declaration in clients of this class is unnecessary.
61   * 
62   * @author Dan Garrette
63   * @author Lucas Ward
64   * @since 2.0
65   * @see SimpleJob
66   */
67  public class StepRunner {
68  
69  	/** Logger */
70  	protected final Log logger = LogFactory.getLog(getClass());
71  
72  	private JobLauncher launcher;
73  
74  	private JobRepository jobRepository;
75  
76  	public StepRunner(JobLauncher launcher, JobRepository jobRepository) {
77  		this.launcher = launcher;
78  		this.jobRepository = jobRepository;
79  	}
80  
81  	/**
82  	 * Launch just the specified step as its own job. A unique set of
83  	 * JobParameters will automatically be generated. An IllegalStateException
84  	 * is thrown if there is no Step with the given name.
85  	 * 
86  	 * @param stepName The name of the step to launch
87  	 * @return JobExecution
88  	 */
89  	public JobExecution launchStep(Step step) {
90  		return this.launchStep(step, this.makeUniqueJobParameters(), null);
91  	}
92  
93  	/**
94  	 * Launch just the specified step as its own job. A unique set of
95  	 * JobParameters will automatically be generated. An IllegalStateException
96  	 * is thrown if there is no Step with the given name.
97  	 * 
98  	 * @param stepName The name of the step to launch
99  	 * @param jobExecutionContext An ExecutionContext whose values will be
100 	 * loaded into the Job ExecutionContext prior to launching the step.
101 	 * @return JobExecution
102 	 */
103 	public JobExecution launchStep(Step step, ExecutionContext jobExecutionContext) {
104 		return this.launchStep(step, this.makeUniqueJobParameters(), jobExecutionContext);
105 	}
106 
107 	/**
108 	 * Launch just the specified step as its own job. An IllegalStateException
109 	 * is thrown if there is no Step with the given name.
110 	 * 
111 	 * @param stepName The name of the step to launch
112 	 * @param jobParameters The JobParameters to use during the launch
113 	 * @return JobExecution
114 	 */
115 	public JobExecution launchStep(Step step, JobParameters jobParameters) {
116 		return this.launchStep(step, jobParameters, null);
117 	}
118 
119 	/**
120 	 * Launch just the specified step as its own job. An IllegalStateException
121 	 * is thrown if there is no Step with the given name.
122 	 * 
123 	 * @param stepName The name of the step to launch
124 	 * @param jobParameters The JobParameters to use during the launch
125 	 * @param jobExecutionContext An ExecutionContext whose values will be
126 	 * loaded into the Job ExecutionContext prior to launching the step.
127 	 * @return JobExecution
128 	 */
129 	public JobExecution launchStep(Step step, JobParameters jobParameters, final ExecutionContext jobExecutionContext) {
130 		//
131 		// Create a fake job
132 		//
133 		SimpleJob job = new SimpleJob();
134 		job.setName("TestJob");
135 		job.setJobRepository(this.jobRepository);
136 
137 		List<Step> stepsToExecute = new ArrayList<Step>();
138 		stepsToExecute.add(step);
139 		job.setSteps(stepsToExecute);
140 
141 		//
142 		// Dump the given Job ExecutionContext using a listener
143 		//
144 		if (jobExecutionContext != null && !jobExecutionContext.isEmpty()) {
145 			job.setJobExecutionListeners(new JobExecutionListener[] { new JobExecutionListenerSupport() {
146                 @Override
147 				public void beforeJob(JobExecution jobExecution) {
148 					ExecutionContext jobContext = jobExecution.getExecutionContext();
149 					for (Map.Entry<String, Object> entry : jobExecutionContext.entrySet()) {
150 						jobContext.put(entry.getKey(), entry.getValue());
151 					}
152 				}
153 			} });
154 		}
155 
156 		//
157 		// Launch the job
158 		//
159 		return this.launchJob(job, jobParameters);
160 	}
161 
162 	/**
163 	 * Launch the given job
164 	 * 
165 	 * @param job
166 	 * @param jobParameters
167 	 */
168 	private JobExecution launchJob(Job job, JobParameters jobParameters) {
169 		try {
170 			return this.launcher.run(job, jobParameters);
171 		}
172 		catch (JobExecutionAlreadyRunningException e) {
173 			throw new UnexpectedJobExecutionException("Step runner encountered exception.", e);
174 		}
175 		catch (JobRestartException e) {
176 			throw new UnexpectedJobExecutionException("Step runner encountered exception.", e);
177 		}
178 		catch (JobInstanceAlreadyCompleteException e) {
179 			throw new UnexpectedJobExecutionException("Step runner encountered exception.", e);
180 		}
181 		catch (JobParametersInvalidException e) {
182 			throw new UnexpectedJobExecutionException("Step runner encountered exception.", e);
183 		}
184 	}
185 
186 	/**
187 	 * @return a new JobParameters object containing only a parameter for the
188 	 * current timestamp, to ensure that the job instance will be unique
189 	 */
190 	private JobParameters makeUniqueJobParameters() {
191 		Map<String, JobParameter> parameters = new HashMap<String, JobParameter>();
192 		parameters.put("timestamp", new JobParameter(new Date().getTime()));
193 		return new JobParameters(parameters);
194 	}
195 }