diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java index 41f8d8334..077b04f8f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java @@ -57,10 +57,18 @@ public abstract class AbstractStepParser { private static final String REF_ELE = "ref"; + private static final String REF_ATTR = "ref"; + private static final String TASKLET_ELE = "tasklet"; private static final String PARTITION_ELE = "partition"; + private static final String JOB_ELE = "job"; + + private static final String JOB_PARAMS_EXTRACTOR_ATTR = "job-parameters-extractor"; + + private static final String JOB_LAUNCHER_ATTR = "job-launcher"; + private static final String STEP_ATTR = "step"; private static final String PARTITIONER_ATTR = "partitioner"; @@ -118,6 +126,12 @@ public abstract class AbstractStepParser { parsePartition(stepElement, partitionElement, bd, parserContext, stepUnderspecified); } + Element jobElement = DomUtils.getChildElementByTagName(stepElement, JOB_ELE); + if (jobElement != null) { + boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); + parseJob(stepElement, jobElement, bd, parserContext, stepUnderspecified); + } + String parentRef = stepElement.getAttribute(PARENT_ATTR); if (StringUtils.hasText(parentRef)) { bd.setParentName(parentRef); @@ -187,6 +201,33 @@ public abstract class AbstractStepParser { } + private void parseJob(Element stepElement, Element jobElement, AbstractBeanDefinition bd, + ParserContext parserContext, boolean stepUnderspecified) { + + bd.setBeanClass(StepParserStepFactoryBean.class); + bd.setAttribute("isNamespaceStep", true); + String jobRef = jobElement.getAttribute(REF_ATTR); + + if (!StringUtils.hasText(jobRef)) { + parserContext.getReaderContext().error("You must specify a job", jobElement); + return; + } + + MutablePropertyValues propertyValues = bd.getPropertyValues(); + propertyValues.addPropertyValue("job", new RuntimeBeanReference(jobRef)); + + String jobParametersExtractor = jobElement.getAttribute(JOB_PARAMS_EXTRACTOR_ATTR); + String jobLauncher = jobElement.getAttribute(JOB_LAUNCHER_ATTR); + + if (StringUtils.hasText(jobParametersExtractor)) { + propertyValues.addPropertyValue("jobParametersExtractor", new RuntimeBeanReference(jobParametersExtractor)); + } + if (StringUtils.hasText(jobLauncher)) { + propertyValues.addPropertyValue("jobLauncher", new RuntimeBeanReference(jobLauncher)); + } + + } + private void parseTasklet(Element stepElement, Element taskletElement, AbstractBeanDefinition bd, ParserContext parserContext, boolean stepUnderspecified) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java index e8ef63dfd..8329ec52c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java @@ -21,11 +21,14 @@ import java.util.HashSet; import java.util.Map; import org.springframework.batch.classify.BinaryExceptionClassifier; +import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecutionListener; import org.springframework.batch.core.StepListener; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowStep; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.support.SimpleJobLauncher; import org.springframework.batch.core.partition.PartitionHandler; import org.springframework.batch.core.partition.support.PartitionStep; import org.springframework.batch.core.partition.support.Partitioner; @@ -35,6 +38,8 @@ import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.AbstractStep; import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean; import org.springframework.batch.core.step.item.SimpleStepFactoryBean; +import org.springframework.batch.core.step.job.JobParametersExtractor; +import org.springframework.batch.core.step.job.JobStep; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.batch.item.ItemProcessor; @@ -94,6 +99,15 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware { // private Flow flow; + // + // Job Elements + // + private Job job; + + private JobLauncher jobLauncher; + + private JobParametersExtractor jobParametersExtractor; + // // Partition Elements // @@ -196,6 +210,11 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware { configureFlowStep(ts); return ts; } + else if (job != null) { + JobStep ts = new JobStep(); + configureJobStep(ts); + return ts; + } else if (step != null) { PartitionStep ts = new PartitionStep(); configurePartitionStep(ts); @@ -379,6 +398,24 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware { } } + @SuppressWarnings("serial") + private void configureJobStep(JobStep ts) throws Exception { + configureAbstractStep(ts); + if (job != null) { + ts.setJob(job); + } + if (jobParametersExtractor != null) { + ts.setJobParametersExtractor(jobParametersExtractor); + } + if (jobLauncher == null) { + SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); + jobLauncher.setJobRepository(jobRepository); + jobLauncher.afterPropertiesSet(); + this.jobLauncher = jobLauncher; + } + ts.setJobLauncher(jobLauncher); + } + private void validateFaultTolerantSettings() { validateDependency("skippable-exception-classes", skippableExceptionClasses, "skip-limit", skipLimit, true); validateDependency("retryable-exception-classes", retryableExceptionClasses, "retry-limit", retryLimit, true); @@ -474,6 +511,25 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware { this.flow = flow; } + // ========================================================= + // Job Attributes + // ========================================================= + + /** + * @param flow the flow to set + */ + public void setJob(Job job) { + this.job = job; + } + + public void setJobParametersExtractor(JobParametersExtractor jobParametersExtractor) { + this.jobParametersExtractor = jobParametersExtractor; + } + + public void setJobLauncher(JobLauncher jobLauncher) { + this.jobLauncher = jobLauncher; + } + // ========================================================= // Partition Attributes // ========================================================= diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractor.java index 613fb642b..10ec4b4d4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractor.java @@ -40,6 +40,8 @@ public class DefaultJobParametersExtractor implements JobParametersExtractor { private Set keys = new HashSet(); + private boolean useAllParentParameters = true; + /** * The key names to pull out of the execution context or job parameters, if * they exist. If a key doesn't exist in the execution context then the job @@ -64,6 +66,11 @@ public class DefaultJobParametersExtractor implements JobParametersExtractor { JobParametersBuilder builder = new JobParametersBuilder(); Map jobParameters = stepExecution.getJobParameters().getParameters(); ExecutionContext executionContext = stepExecution.getExecutionContext(); + if (useAllParentParameters) { + for (String key : jobParameters.keySet()) { + builder.addParameter(key, jobParameters.get(key)); + } + } for (String key : keys) { if (key.endsWith("(long)")) { key = key.replace("(long)", ""); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobStep.java index 31d9331a2..77a931bb1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobStep.java @@ -80,10 +80,8 @@ public class JobStep extends AbstractStep { /** * The {@link JobParametersExtractor} is used to extract * {@link JobParametersExtractor} from the {@link StepExecution} to run the - * {@link Job}. By default an instance will be provided that simply creates - * empty {@link JobParameters}. This is unlikely to be very useful, since - * the {@link Job} normally cannot be run with empty parameters more than - * once. + * {@link Job}. By default an instance will be provided that simply copies + * the {@link JobParameters} from the parent job. * * @param jobParametersExtractor the {@link JobParametersExtractor} to set */ diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd index 5de23ebae..a359eb856 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd @@ -330,7 +330,7 @@ + type="java:org.springframework.core.task.TaskExecutor" /> @@ -403,6 +403,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobStepParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobStepParserTests.java new file mode 100644 index 000000000..53ca66e35 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobStepParserTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.configuration.xml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + + +/** + * @author Dave Syer + * + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class JobStepParserTests { + + @Autowired + @Qualifier("job1") + private Job job1; + + @Autowired + @Qualifier("job2") + private Job job2; + + @Autowired + private JobRepository jobRepository; + + @Autowired + private MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean; + + @Before + public void setUp() { + mapJobRepositoryFactoryBean.clear(); + } + + @Test + public void testFlowStep() throws Exception { + assertNotNull(job1); + JobExecution jobExecution = jobRepository.createJobExecution(job1.getName(), new JobParameters()); + job1.execute(jobExecution); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + List stepNames = getStepNames(jobExecution); + assertEquals(3, stepNames.size()); + assertEquals("[s1, job1.flow, s4]", stepNames.toString()); + } + + @Test + public void testFlowExternalStep() throws Exception { + assertNotNull(job2); + JobExecution jobExecution = jobRepository.createJobExecution(job2.getName(), new JobParameters()); + job2.execute(jobExecution); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + List stepNames = getStepNames(jobExecution); + assertEquals(3, stepNames.size()); + assertEquals("[job2.s1, job2.flow, job2.s4]", stepNames.toString()); + } + + private List getStepNames(JobExecution jobExecution) { + List list = new ArrayList(); + for (StepExecution stepExecution : jobExecution.getStepExecutions()) { + list.add(stepExecution.getStepName()); + } + return list; + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorJobParametersTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorJobParametersTests.java index b24ff5483..ef25ab1ff 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorJobParametersTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorJobParametersTests.java @@ -44,6 +44,14 @@ public class DefaultJobParametersExtractorJobParametersTests { assertEquals("{foo=bar}", jobParameters.toString()); } + @Test + public void testGetAllJobParameters() throws Exception { + StepExecution stepExecution = getStepExecution("foo=bar,spam=bucket"); + extractor.setKeys(new String[] {"foo", "bar"}); + JobParameters jobParameters = extractor.getJobParameters(null, stepExecution); + assertEquals("{spam=bucket, foo=bar}", jobParameters.toString()); + } + @Test public void testGetNamedLongStringParameters() throws Exception { StepExecution stepExecution = getStepExecution("foo=bar"); diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobStepParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobStepParserTests-context.xml new file mode 100644 index 000000000..527229927 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobStepParserTests-context.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file