diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java index 515892678..c73075b38 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java @@ -15,11 +15,13 @@ */ package org.springframework.batch.core.listener; +import java.util.Collections; import java.util.List; import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.Job; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.flow.support.util.PatternMatcher; import org.springframework.batch.item.ExecutionContext; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; @@ -32,12 +34,17 @@ import com.sun.org.apache.xerces.internal.impl.xpath.XPath.Step; * end of a step. A list of keys should be provided that correspond to the items * in the {@link Step} {@link ExecutionContext} that should be promoted. * + * Additionally, an optional list of statuses can be set to indicate for which + * exit status codes the promotion should occur. These statuses will be checked + * using the {@link PatternMatcher}, so wildcards are allowed. + * * @author Dan Garrette * @since 2.0 */ public class ExecutionContextPromotionListener extends StepExecutionListenerSupport implements InitializingBean { private List keys = null; + private List statuses = Collections.singletonList(ExitStatus.FINISHED.getExitCode()); /* * (non-Javadoc) @@ -46,19 +53,52 @@ public class ExecutionContextPromotionListener extends StepExecutionListenerSupp * stepExecution) */ public ExitStatus afterStep(StepExecution stepExecution) { + if (statuses == null) { + this.performPromotion(stepExecution); + } + else { + String exitCode = stepExecution.getExitStatus().getExitCode(); + for (String statusPattern : statuses) { + if (PatternMatcher.match(statusPattern, exitCode)) { + this.performPromotion(stepExecution); + } + } + } + + return null; + } + + private void performPromotion(StepExecution stepExecution) { ExecutionContext stepContext = stepExecution.getExecutionContext(); ExecutionContext jobContext = stepExecution.getJobExecution().getExecutionContext(); for (String key : keys) { jobContext.put(key, stepContext.get(key)); } - return null; } public void afterPropertiesSet() throws Exception { Assert.notNull(this.keys, "The 'keys' property must be provided"); + Assert.notEmpty(this.statuses, "The 'keys' property must not be empty"); + Assert.notNull(this.statuses, "The 'statuses' property must be provided"); + Assert.notEmpty(this.statuses, "The 'statuses' property must not be empty"); } + /** + * @param keys + * A list of keys corresponding to items in the {@link Step} + * {@link ExecutionContext} that must be promoted. + */ public void setKeys(List keys) { this.keys = keys; } + + /** + * @param statuses + * A list of statuses for which the promotion should occur. + * Statuses can may contain wildcards recognizable by the + * {@link PatternMatcher} class. + */ + public void setStatuses(List statuses) { + this.statuses = statuses; + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ExecutionContextPromotionListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ExecutionContextPromotionListenerTests.java index 8802625cd..54b0719f4 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ExecutionContextPromotionListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ExecutionContextPromotionListenerTests.java @@ -5,6 +5,7 @@ import static org.junit.Assert.*; import java.util.Collections; import org.junit.Test; +import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.StepExecution; import org.springframework.util.Assert; @@ -14,34 +15,140 @@ import org.springframework.util.Assert; */ public class ExecutionContextPromotionListenerTests { - private ExecutionContextPromotionListener tested = new ExecutionContextPromotionListener(); - private static final String key = "testKey"; - private static final String value = "testValue"; + private static final String key2 = "testKey2"; + private static final String value2 = "testValue2"; + private static final String status = "COMPLETED WITH SKIPS"; + private static final String status2 = "FAILURE"; + private static final String statusWildcard = "COMPL*SKIPS"; + /** + * CONDITION: keys = {key1, key2}. statuses is not set (defaults to + * {COMPLETED}). + * + * EXPECTED: key is promoted. key2 is not. + */ @Test - public void promoteEntryFromStepToJobExectutionContext() throws Exception { + public void promoteEntry_nullStatuses() throws Exception { + ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); JobExecution jobExecution = new JobExecution(1L); StepExecution stepExecution = jobExecution.createStepExecution("step1"); + stepExecution.setExitStatus(ExitStatus.FINISHED); Assert.state(jobExecution.getExecutionContext().isEmpty()); Assert.state(stepExecution.getExecutionContext().isEmpty()); stepExecution.getExecutionContext().putString(key, value); + stepExecution.getExecutionContext().putString(key2, value2); - tested.setKeys(Collections.singletonList(key)); - tested.afterPropertiesSet(); + listener.setKeys(Collections.singletonList(key)); + listener.afterPropertiesSet(); - tested.afterStep(stepExecution); + listener.afterStep(stepExecution); assertEquals(value, jobExecution.getExecutionContext().getString(key)); + assertFalse(jobExecution.getExecutionContext().containsKey(key2)); } + /** + * CONDITION: keys = {key1, key2}. statuses = {status}. ExitStatus = status + * + * EXPECTED: key is promoted. key2 is not. + */ + @Test + public void promoteEntry_statusFound() throws Exception { + ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); + + JobExecution jobExecution = new JobExecution(1L); + StepExecution stepExecution = jobExecution.createStepExecution("step1"); + stepExecution.setExitStatus(new ExitStatus(status)); + + Assert.state(jobExecution.getExecutionContext().isEmpty()); + Assert.state(stepExecution.getExecutionContext().isEmpty()); + + stepExecution.getExecutionContext().putString(key, value); + stepExecution.getExecutionContext().putString(key2, value2); + + listener.setKeys(Collections.singletonList(key)); + listener.setStatuses(Collections.singletonList(status)); + listener.afterPropertiesSet(); + + listener.afterStep(stepExecution); + + assertEquals(value, jobExecution.getExecutionContext().getString(key)); + assertFalse(jobExecution.getExecutionContext().containsKey(key2)); + } + + /** + * CONDITION: keys = {key1, key2}. statuses = {status}. ExitStatus = status2 + * + * EXPECTED: no promotions. + */ + @Test + public void promoteEntry_statusNotFound() throws Exception { + ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); + + JobExecution jobExecution = new JobExecution(1L); + StepExecution stepExecution = jobExecution.createStepExecution("step1"); + stepExecution.setExitStatus(new ExitStatus(status2)); + + Assert.state(jobExecution.getExecutionContext().isEmpty()); + Assert.state(stepExecution.getExecutionContext().isEmpty()); + + stepExecution.getExecutionContext().putString(key, value); + stepExecution.getExecutionContext().putString(key2, value2); + + listener.setKeys(Collections.singletonList(key)); + listener.setStatuses(Collections.singletonList(status)); + listener.afterPropertiesSet(); + + listener.afterStep(stepExecution); + + assertFalse(jobExecution.getExecutionContext().containsKey(key)); + assertFalse(jobExecution.getExecutionContext().containsKey(key2)); + } + + /** + * CONDITION: keys = {key1, key2}. statuses = {statusWildcard}. ExitStatus = + * status + * + * EXPECTED: key is promoted. key2 is not. + */ + @Test + public void promoteEntry_statusWildcardFound() throws Exception { + ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); + + JobExecution jobExecution = new JobExecution(1L); + StepExecution stepExecution = jobExecution.createStepExecution("step1"); + stepExecution.setExitStatus(new ExitStatus(status)); + + Assert.state(jobExecution.getExecutionContext().isEmpty()); + Assert.state(stepExecution.getExecutionContext().isEmpty()); + + stepExecution.getExecutionContext().putString(key, value); + stepExecution.getExecutionContext().putString(key2, value2); + + listener.setKeys(Collections.singletonList(key)); + listener.setStatuses(Collections.singletonList(statusWildcard)); + listener.afterPropertiesSet(); + + listener.afterStep(stepExecution); + + assertEquals(value, jobExecution.getExecutionContext().getString(key)); + assertFalse(jobExecution.getExecutionContext().containsKey(key2)); + } + + /** + * CONDITION: keys = NULL + * + * EXPECTED: IllegalArgumentException + */ @Test(expected = IllegalArgumentException.class) public void keysMustBeSet() throws Exception { - // didn't set the keys, same as tested.setKeys(null); - tested.afterPropertiesSet(); + ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); + // didn't set the keys, same as listener.setKeys(null); + listener.afterPropertiesSet(); } }