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 step 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 step 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 step 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 step 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 | } |