001/*
002 * Copyright 2006-2007 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.Date;
020import java.util.HashMap;
021import java.util.Map;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025import org.springframework.batch.core.Job;
026import org.springframework.batch.core.JobExecution;
027import org.springframework.batch.core.JobParameter;
028import org.springframework.batch.core.JobParameters;
029import org.springframework.batch.core.Step;
030import org.springframework.batch.core.job.AbstractJob;
031import org.springframework.batch.core.job.SimpleJob;
032import org.springframework.batch.core.job.flow.FlowJob;
033import org.springframework.batch.core.launch.JobLauncher;
034import org.springframework.batch.core.repository.JobRepository;
035import org.springframework.batch.item.ExecutionContext;
036import org.springframework.beans.BeansException;
037import org.springframework.beans.factory.annotation.Autowired;
038import org.springframework.context.ApplicationContext;
039import org.springframework.context.ApplicationContextAware;
040
041/**
042 * <p>
043 * Base class for testing batch jobs. It provides methods for launching an
044 * entire {@link AbstractJob}, allowing for end to end testing of individual
045 * steps, without having to run every step in the job. Any test classes
046 * inheriting from this class should make sure they are part of an
047 * {@link ApplicationContext}, which is generally expected to be done as part of
048 * the Spring test framework. Furthermore, the {@link ApplicationContext} in
049 * which it is a part of is expected to have one {@link JobLauncher},
050 * {@link JobRepository}, and a single {@link AbstractJob} implementation.
051 * 
052 * <p>
053 * This class also provides the ability to run {@link Step}s from a
054 * {@link FlowJob} or {@link SimpleJob} individually. By launching {@link Step}s
055 * within a {@link Job} on their own, end to end testing of individual steps can
056 * be performed without having to run every step in the job.
057 * 
058 * <p>
059 * It should be noted that using any of the methods that don't contain
060 * {@link JobParameters} in their signature, will result in one being created
061 * with the current system time as a parameter. This will ensure restartability
062 * when no parameters are provided.
063 * 
064 * @author Lucas Ward
065 * @author Dan Garrette
066 * @since 2.0
067 * 
068 * @deprecated (from 2.1) use {@link JobLauncherTestUtils} instead
069 */
070@Deprecated
071public abstract class AbstractJobTests implements ApplicationContextAware {
072
073        /** Logger */
074        protected final Log logger = LogFactory.getLog(getClass());
075
076        @Autowired
077        private JobLauncher launcher;
078
079        @Autowired
080        private AbstractJob job;
081
082        @Autowired
083        private JobRepository jobRepository;
084
085        private StepRunner stepRunner;
086
087        private ApplicationContext applicationContext;
088
089        /**
090         * {@inheritDoc}
091         */
092    @Override
093        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
094                this.applicationContext = applicationContext;
095        }
096
097        /**
098         * @return the applicationContext
099         */
100        protected ApplicationContext getApplicationContext() {
101                return applicationContext;
102        }
103
104        /**
105         * @return the job repository which is autowired by type
106         */
107        public JobRepository getJobRepository() {
108                return jobRepository;
109        }
110
111        /**
112         * @return the job which is autowired by type
113         */
114        public AbstractJob getJob() {
115                return job;
116        }
117
118        /**
119         * @return the launcher
120         */
121        protected JobLauncher getJobLauncher() {
122                return launcher;
123        }
124
125        /**
126         * Launch the entire job, including all steps.
127         * 
128         * @return JobExecution, so that the test can validate the exit status
129         *
130         * @throws Exception is thrown if error occurs.
131         */
132        protected JobExecution launchJob() throws Exception {
133                return this.launchJob(this.getUniqueJobParameters());
134        }
135
136        /**
137         * Launch the entire job, including all steps
138         * 
139         * @param jobParameters parameters for the job
140         * @return JobExecution, so that the test can validate the exit status
141         *
142         * @throws Exception is thrown if error occurs.
143         */
144        protected JobExecution launchJob(JobParameters jobParameters) throws Exception {
145                return getJobLauncher().run(this.job, jobParameters);
146        }
147
148        /**
149         * @return a new JobParameters object containing only a parameter for the
150         * current timestamp, to ensure that the job instance will be unique.
151         */
152        protected JobParameters getUniqueJobParameters() {
153                Map<String, JobParameter> parameters = new HashMap<String, JobParameter>();
154                parameters.put("timestamp", new JobParameter(new Date().getTime()));
155                return new JobParameters(parameters);
156        }
157
158        /**
159         * Convenient method for subclasses to grab a {@link StepRunner} for running
160         * steps by name.
161         * 
162         * @return a {@link StepRunner}
163         */
164        protected StepRunner getStepRunner() {
165                if (this.stepRunner == null) {
166                        this.stepRunner = new StepRunner(getJobLauncher(), getJobRepository());
167                }
168                return this.stepRunner;
169        }
170
171        /**
172         * Launch just the specified step in the job. A unique set of JobParameters
173         * will automatically be generated. An IllegalStateException is thrown if
174         * there is no Step with the given name.
175         * 
176         * @param stepName The name of the step to launch
177         * @return JobExecution
178         */
179        public JobExecution launchStep(String stepName) {
180                return this.launchStep(stepName, this.getUniqueJobParameters(), null);
181        }
182
183        /**
184         * Launch just the specified step in the job. A unique set of JobParameters
185         * will automatically be generated. An IllegalStateException is thrown if
186         * there is no Step with the given name.
187         * 
188         * @param stepName The name of the step to launch
189         * @param jobExecutionContext An ExecutionContext whose values will be
190         * loaded into the Job ExecutionContext prior to launching the step.
191         * @return JobExecution
192         */
193        public JobExecution launchStep(String stepName, ExecutionContext jobExecutionContext) {
194                return this.launchStep(stepName, this.getUniqueJobParameters(), jobExecutionContext);
195        }
196
197        /**
198         * Launch just the specified step in the job. An IllegalStateException is
199         * thrown if there is no Step with the given name.
200         * 
201         * @param stepName The name of the step to launch
202         * @param jobParameters The JobParameters to use during the launch
203         * @return JobExecution
204         */
205        public JobExecution launchStep(String stepName, JobParameters jobParameters) {
206                return this.launchStep(stepName, jobParameters, null);
207        }
208
209        /**
210         * Launch just the specified step in the job. An IllegalStateException is
211         * thrown if there is no Step with the given name.
212         * 
213         * @param stepName The name of the step to launch
214         * @param jobParameters The JobParameters to use during the launch
215         * @param jobExecutionContext An ExecutionContext whose values will be
216         * loaded into the Job ExecutionContext prior to launching the step.
217         * @return JobExecution
218         */
219        public JobExecution launchStep(String stepName, JobParameters jobParameters, ExecutionContext jobExecutionContext) {
220                Step step = this.job.getStep(stepName);
221                if (step == null) {
222                        step = this.job.getStep(this.job.getName() + "." + stepName);
223                }
224                if (step == null) {
225                        throw new IllegalStateException("No Step found with name: [" + stepName + "]");
226                }
227                return getStepRunner().launchStep(step, jobParameters, jobExecutionContext);
228        }
229}