001/* 002 * Copyright 2006-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.batch.test; 018 019import java.util.HashMap; 020import java.util.Map; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024import org.springframework.batch.core.Job; 025import org.springframework.batch.core.JobExecution; 026import org.springframework.batch.core.JobParameter; 027import org.springframework.batch.core.JobParameters; 028import org.springframework.batch.core.Step; 029import org.springframework.batch.core.job.AbstractJob; 030import org.springframework.batch.core.job.SimpleJob; 031import org.springframework.batch.core.job.flow.FlowJob; 032import org.springframework.batch.core.launch.JobLauncher; 033import org.springframework.batch.core.repository.JobRepository; 034import org.springframework.batch.core.step.StepLocator; 035import org.springframework.batch.item.ExecutionContext; 036import org.springframework.beans.factory.annotation.Autowired; 037import org.springframework.context.ApplicationContext; 038import org.springframework.lang.Nullable; 039 040/** 041 * <p> 042 * Utility class for testing batch jobs. It provides methods for launching an 043 * entire {@link AbstractJob}, allowing for end to end testing of individual 044 * steps, without having to run every step in the job. Any test classes using 045 * this utility can set up an instance in the {@link ApplicationContext} as part 046 * of the Spring test framework. 047 * </p> 048 * 049 * <p> 050 * This class also provides the ability to run {@link Step}s from a 051 * {@link FlowJob} or {@link SimpleJob} individually. By launching {@link Step}s 052 * within a {@link Job} on their own, end to end testing of individual steps can 053 * be performed without having to run every step in the job. 054 * </p> 055 * 056 * <p> 057 * It should be noted that using any of the methods that don't contain 058 * {@link JobParameters} in their signature, will result in one being created 059 * with the current system time as a parameter. This will ensure restartability 060 * when no parameters are provided. 061 * </p> 062 * 063 * @author Lucas Ward 064 * @author Dan Garrette 065 * @author Dave Syer 066 * @author Mahmoud Ben Hassine 067 * @since 2.1 068 */ 069public class JobLauncherTestUtils { 070 071 private static final long JOB_PARAMETER_MAXIMUM = 1000000; 072 073 /** Logger */ 074 protected final Log logger = LogFactory.getLog(getClass()); 075 076 private JobLauncher jobLauncher; 077 078 private Job job; 079 080 private JobRepository jobRepository; 081 082 private StepRunner stepRunner; 083 084 /** 085 * The Job instance that can be manipulated (e.g. launched) in this utility. 086 * 087 * @param job the {@link AbstractJob} to use 088 */ 089 @Autowired 090 public void setJob(Job job) { 091 this.job = job; 092 } 093 094 /** 095 * The {@link JobRepository} to use for creating new {@link JobExecution} 096 * instances. 097 * 098 * @param jobRepository a {@link JobRepository} 099 */ 100 @Autowired 101 public void setJobRepository(JobRepository jobRepository) { 102 this.jobRepository = jobRepository; 103 } 104 105 /** 106 * @return the job repository 107 */ 108 public JobRepository getJobRepository() { 109 return jobRepository; 110 } 111 112 /** 113 * @return the job 114 */ 115 public Job getJob() { 116 return job; 117 } 118 119 /** 120 * A {@link JobLauncher} instance that can be used to launch jobs. 121 * 122 * @param jobLauncher a job launcher 123 */ 124 @Autowired 125 public void setJobLauncher(JobLauncher jobLauncher) { 126 this.jobLauncher = jobLauncher; 127 } 128 129 /** 130 * @return the job launcher 131 */ 132 public JobLauncher getJobLauncher() { 133 return jobLauncher; 134 } 135 136 /** 137 * Launch the entire job, including all steps. 138 * 139 * @return JobExecution, so that the test can validate the exit status 140 * @throws Exception thrown if error occurs launching the job. 141 */ 142 public JobExecution launchJob() throws Exception { 143 return this.launchJob(this.getUniqueJobParameters()); 144 } 145 146 /** 147 * Launch the entire job, including all steps 148 * 149 * @param jobParameters instance of {@link JobParameters}. 150 * @return JobExecution, so that the test can validate the exit status 151 * @throws Exception thrown if error occurs launching the job. 152 */ 153 public JobExecution launchJob(JobParameters jobParameters) throws Exception { 154 return getJobLauncher().run(this.job, jobParameters); 155 } 156 157 /** 158 * @return a new JobParameters object containing only a parameter for the 159 * current timestamp, to ensure that the job instance will be unique. 160 */ 161 public JobParameters getUniqueJobParameters() { 162 Map<String, JobParameter> parameters = new HashMap<String, JobParameter>(); 163 parameters.put("random", new JobParameter((long) (Math.random() * JOB_PARAMETER_MAXIMUM))); 164 return new JobParameters(parameters); 165 } 166 167 /** 168 * Convenient method for subclasses to grab a {@link StepRunner} for running 169 * steps by name. 170 * 171 * @return a {@link StepRunner} 172 */ 173 protected StepRunner getStepRunner() { 174 if (this.stepRunner == null) { 175 this.stepRunner = new StepRunner(getJobLauncher(), getJobRepository()); 176 } 177 return this.stepRunner; 178 } 179 180 /** 181 * Launch just the specified step in the job. A unique set of JobParameters 182 * will automatically be generated. An IllegalStateException is thrown if 183 * there is no Step with the given name. 184 * 185 * @param stepName The name of the step to launch 186 * @return JobExecution 187 */ 188 public JobExecution launchStep(String stepName) { 189 return this.launchStep(stepName, this.getUniqueJobParameters(), null); 190 } 191 192 /** 193 * Launch just the specified step in the job. A unique set of JobParameters 194 * will automatically be generated. An IllegalStateException is thrown if 195 * there is no Step with the given name. 196 * 197 * @param stepName The name of the step to launch 198 * @param jobExecutionContext An ExecutionContext whose values will be 199 * loaded into the Job ExecutionContext prior to launching the step. 200 * @return JobExecution 201 */ 202 public JobExecution launchStep(String stepName, ExecutionContext jobExecutionContext) { 203 return this.launchStep(stepName, this.getUniqueJobParameters(), jobExecutionContext); 204 } 205 206 /** 207 * Launch just the specified step in the job. An IllegalStateException is 208 * thrown if there is no Step with the given name. 209 * 210 * @param stepName The name of the step to launch 211 * @param jobParameters The JobParameters to use during the launch 212 * @return JobExecution 213 */ 214 public JobExecution launchStep(String stepName, JobParameters jobParameters) { 215 return this.launchStep(stepName, jobParameters, null); 216 } 217 218 /** 219 * Launch just the specified step in the job. An IllegalStateException is 220 * thrown if there is no Step with the given name. 221 * 222 * @param stepName The name of the step to launch 223 * @param jobParameters The JobParameters to use during the launch 224 * @param jobExecutionContext An ExecutionContext whose values will be 225 * loaded into the Job ExecutionContext prior to launching the step. 226 * @return JobExecution 227 */ 228 public JobExecution launchStep(String stepName, JobParameters jobParameters, @Nullable ExecutionContext jobExecutionContext) { 229 if (!(job instanceof StepLocator)) { 230 throw new UnsupportedOperationException("Cannot locate step from a Job that is not a StepLocator: job=" 231 + job.getName() + " does not implement StepLocator"); 232 } 233 StepLocator locator = (StepLocator) this.job; 234 Step step = locator.getStep(stepName); 235 if (step == null) { 236 step = locator.getStep(this.job.getName() + "." + stepName); 237 } 238 if (step == null) { 239 throw new IllegalStateException("No Step found with name: [" + stepName + "]"); 240 } 241 return getStepRunner().launchStep(step, jobParameters, jobExecutionContext); 242 } 243}