From 23587e08558deb2812df29e112cc7ba6a8aca07f Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Sat, 19 Mar 2011 16:14:50 +0000 Subject: [PATCH] BATCH-1396: added step scope to completion policy in chunk parser --- .../resources/META-INF/batch/footballJob.xml | 2 +- .../football/FootballJobIntegrationTests.java | 18 ++++++------- .../configuration/xml/ChunkElementParser.java | 26 ++++++++++++++----- .../StepExecutionSimpleCompletionPolicy.java | 9 +++++++ .../configuration/xml/spring-batch-2.1.xsd | 3 ++- .../xml/ChunkElementParserTests.java | 8 ++++++ 6 files changed, 49 insertions(+), 17 deletions(-) diff --git a/spring-batch-core-tests/src/main/resources/META-INF/batch/footballJob.xml b/spring-batch-core-tests/src/main/resources/META-INF/batch/footballJob.xml index 40342911c..c4f37849d 100644 --- a/spring-batch-core-tests/src/main/resources/META-INF/batch/footballJob.xml +++ b/spring-batch-core-tests/src/main/resources/META-INF/batch/footballJob.xml @@ -11,7 +11,7 @@ + commit-interval="#{jobParameters['commit.interval']}" /> diff --git a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/football/FootballJobIntegrationTests.java b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/football/FootballJobIntegrationTests.java index df47910ef..61bb5847f 100644 --- a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/football/FootballJobIntegrationTests.java +++ b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/football/FootballJobIntegrationTests.java @@ -37,10 +37,9 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.jdbc.SimpleJdbcTestUtils; - /** * @author Dave Syer - * + * */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/META-INF/batch/footballJob.xml" }) @@ -48,31 +47,32 @@ public class FootballJobIntegrationTests { /** Logger */ private final Log logger = LogFactory.getLog(getClass()); - + private SimpleJdbcTemplate simpleJdbcTemplate; @Autowired private JobLauncher jobLauncher; - + @Autowired private Job job; - + @Autowired public void setDataSource(DataSource dataSource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); } - + @Before public void clear() { SimpleJdbcTestUtils.deleteFromTables(simpleJdbcTemplate, "PLAYER_SUMMARY", "GAMES", "PLAYERS"); } - + @Test public void testLaunchJob() throws Exception { - JobExecution execution = jobLauncher.run(job, new JobParametersBuilder().toJobParameters()); + JobExecution execution = jobLauncher.run(job, new JobParametersBuilder().addLong("commit.interval", 10L) + .toJobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); for (StepExecution stepExecution : execution.getStepExecutions()) { - logger.info("Processed: "+stepExecution); + logger.info("Processed: " + stepExecution); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java index 80dad028e..e688bf527 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java @@ -18,6 +18,7 @@ package org.springframework.batch.core.configuration.xml; import java.util.List; import org.springframework.batch.core.listener.StepListenerMetaData; +import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -25,6 +26,7 @@ import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.ManagedMap; @@ -59,7 +61,8 @@ public class ChunkElementParser { private static final String ITEM_WRITER_ADAPTER_CLASS = "org.springframework.batch.item.adapter.ItemWriterAdapter"; - private static final StepListenerParser stepListenerParser = new StepListenerParser(StepListenerMetaData.itemListenerMetaData()); + private static final StepListenerParser stepListenerParser = new StepListenerParser( + StepListenerMetaData.itemListenerMetaData()); /** * @param element @@ -80,7 +83,17 @@ public class ChunkElementParser { String commitInterval = element.getAttribute(COMMIT_INTERVAL_ATTR); if (StringUtils.hasText(commitInterval)) { - propertyValues.addPropertyValue("commitInterval", commitInterval); + if (commitInterval.startsWith("#")) { + // It's a late binding expression, so we need step scope... + BeanDefinitionBuilder completionPolicy = BeanDefinitionBuilder + .genericBeanDefinition(SimpleCompletionPolicy.class); + completionPolicy.addConstructorArgValue(commitInterval); + completionPolicy.setScope("step"); + propertyValues.addPropertyValue("chunkCompletionPolicy", completionPolicy.getBeanDefinition()); + } + else { + propertyValues.addPropertyValue("commitInterval", commitInterval); + } } String completionPolicyRef = element.getAttribute(CHUNK_COMPLETION_POLICY_ATTR); @@ -144,7 +157,7 @@ public class ChunkElementParser { handleRetryListenersElement(element, propertyValues, parserContext, bd); handleStreamsElement(element, propertyValues, parserContext); - + stepListenerParser.handleListenersElement(element, bd, parserContext); } @@ -202,8 +215,8 @@ public class ChunkElementParser { propertyValues.addPropertyValue(propertyName, beanDefinitionHolder); } else if (refElements.size() == 1) { - propertyValues.addPropertyValue(propertyName, parserContext.getDelegate().parsePropertySubElement( - refElements.get(0), null)); + propertyValues.addPropertyValue(propertyName, + parserContext.getDelegate().parsePropertySubElement(refElements.get(0), null)); } handleAdapterMethodAttribute(propertyName, adapterClassName, propertyValues, element); @@ -251,7 +264,8 @@ public class ChunkElementParser { } @SuppressWarnings("unchecked") - private void handleRetryListenerElements(ParserContext parserContext, Element element, ManagedList beans, BeanDefinition enclosing) { + private void handleRetryListenerElements(ParserContext parserContext, Element element, ManagedList beans, + BeanDefinition enclosing) { List listenerElements = DomUtils.getChildElementsByTagName(element, "listener"); if (listenerElements != null) { for (Element listenerElement : listenerElements) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java index af1a0a508..3ed303631 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java @@ -27,12 +27,21 @@ import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; import org.springframework.util.Assert; /** + *

* A {@link CompletionPolicy} that picks up a commit interval from * {@link JobParameters} by listening to the start of a step. Use anywhere that * a {@link CompletionPolicy} can be used (usually at the chunk level in a * step), and inject as a {@link StepExecutionListener} into the surrounding * step. N.B. only after the step has started will the completion policy be * usable. + *

+ * + *

+ * It is easier and probably preferable to simply declare the chunk with a + * commit-interval that is a late-binding expression (e.g. + * #{jobParameters['commit.interval']}). That feature is available + * from of Spring Batch 2.1.7. + *

* * @author Dave Syer * 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 5593eba61..ed8f43660 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 @@ -823,7 +823,8 @@ ref" is not required, and only needs to be specified explicitly diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java index a2cfbd363..13f99084c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java @@ -65,6 +65,14 @@ public class ChunkElementParserTests { assertTrue("Wrong processor type", chunkProcessor instanceof SimpleChunkProcessor); } + @Test + public void testCommitIntervalLateBinding() throws Exception { + ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/ChunkElementCommitIntervalParserTests-context.xml"); + Step step = (Step) context.getBean("s1", Step.class); + assertNotNull("Step not parsed", step); + } + @Test public void testRetryPolicyAttribute() throws Exception { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(