diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java index 048686c41..947596d8f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java @@ -151,11 +151,25 @@ public class SimpleFlowFactoryBean implements FactoryBean, InitializingBean { } String stateName = prefix + oldName; if (state instanceof StepState) { - return new StepState(stateName, ((StepState) state).getStep(oldName)); + return createNewStepState(state, oldName, stateName); } return new DelegateState(stateName, state); } + /** + * Provides an extension point to provide alternative {@link StepState} + * implementations within a {@link SimpleFlow} + * + * @param state The state that will be used to create the StepState + * @param oldName The name to be replaced + * @param stateName The name for the new State + * @return + */ + protected State createNewStepState(State state, String oldName, + String stateName) { + return new StepState(stateName, ((StepState) state).getStep(oldName)); + } + @Override public Class getObjectType() { return SimpleFlow.class; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java index 9249e2c34..8c04da538 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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. @@ -18,8 +18,8 @@ package org.springframework.batch.core.jsr.configuration.xml; import java.util.Collection; import org.springframework.batch.core.job.flow.JobExecutionDecider; -import org.springframework.batch.core.job.flow.support.state.StepState; import org.springframework.batch.core.jsr.configuration.support.BatchArtifact; +import org.springframework.batch.core.jsr.job.flow.support.state.StepState; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java index 8d3cb04af..fc457c6f1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java @@ -24,7 +24,6 @@ import java.util.Map; import java.util.Set; import org.springframework.batch.core.configuration.xml.AbstractFlowParser; -import org.springframework.batch.core.configuration.xml.SimpleFlowFactoryBean; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.jsr.job.flow.support.DefaultFlow; import org.springframework.beans.factory.config.BeanDefinition; @@ -73,7 +72,7 @@ public class FlowParser extends AbstractFlowParser { @Override protected Class getBeanClass(Element element) { - return SimpleFlowFactoryBean.class; + return JsrFlowFactoryBean.class; } @Override diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrFlowFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrFlowFactoryBean.java new file mode 100644 index 000000000..14d1f83b5 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrFlowFactoryBean.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014 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.jsr.configuration.xml; + +import org.springframework.batch.core.configuration.xml.SimpleFlowFactoryBean; +import org.springframework.batch.core.job.flow.State; +import org.springframework.batch.core.jsr.job.flow.support.state.StepState; + +/** + * Extension to the {@link SimpleFlowFactoryBean} that provides {@link StepState} + * implementations for JSR-352 based jobs. + * + * @author Michael Minella + * @since 3.0 + */ +public class JsrFlowFactoryBean extends SimpleFlowFactoryBean { + + /* (non-Javadoc) + * @see org.springframework.batch.core.configuration.xml.SimpleFlowFactoryBean#createNewStepState(org.springframework.batch.core.job.flow.State, java.lang.String, java.lang.String) + */ + @Override + protected State createNewStepState(State state, String oldName, + String stateName) { + return new StepState(stateName, ((StepState) state).getStep(oldName)); + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java index dc38a6f03..8d423c54d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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. @@ -17,8 +17,8 @@ package org.springframework.batch.core.jsr.configuration.xml; import java.util.Collection; -import org.springframework.batch.core.job.flow.support.state.StepState; import org.springframework.batch.core.jsr.configuration.support.BatchArtifact; +import org.springframework.batch.core.jsr.job.flow.support.state.StepState; import org.springframework.batch.core.listener.StepListenerFactoryBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/JsrFlowJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/JsrFlowJob.java index 8a043040a..0b8af8fb7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/JsrFlowJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/JsrFlowJob.java @@ -29,9 +29,9 @@ import org.springframework.batch.core.job.flow.FlowJob; import org.springframework.batch.core.job.flow.JobFlowExecutor; import org.springframework.batch.core.job.flow.State; import org.springframework.batch.core.job.flow.support.state.FlowState; -import org.springframework.batch.core.job.flow.support.state.StepState; import org.springframework.batch.core.jsr.job.DefaultStepHandler; import org.springframework.batch.core.jsr.job.flow.support.DefaultFlow; +import org.springframework.batch.core.jsr.job.flow.support.state.StepState; import org.springframework.batch.core.jsr.step.DecisionStep; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.launch.support.ExitCodeMapper; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/DefaultFlow.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/DefaultFlow.java index ed5a06de5..295af79bd 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/DefaultFlow.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/DefaultFlow.java @@ -27,7 +27,7 @@ import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.State; import org.springframework.batch.core.job.flow.support.SimpleFlow; import org.springframework.batch.core.job.flow.support.StateTransition; -import org.springframework.batch.core.job.flow.support.state.StepState; +import org.springframework.batch.core.jsr.job.flow.support.state.StepState; import org.springframework.batch.item.ExecutionContext; import org.springframework.util.StringUtils; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/state/SplitState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/state/SplitState.java index 2dbb65548..2a8f767da 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/state/SplitState.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/state/SplitState.java @@ -63,7 +63,7 @@ public class SplitState extends org.springframework.batch.core.job.flow.support. } if(!stepNames.isEmpty()) { - executor.getJobExecution().getExecutionContext().put("batch.splitLastSteps", stepNames); + executor.getJobExecution().getExecutionContext().put("batch.lastSteps", stepNames); } executor.getJobExecution().setExitStatus(null); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/state/StepState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/state/StepState.java new file mode 100644 index 000000000..7ec541d00 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/state/StepState.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014 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.jsr.job.flow.support.state; + +import java.util.Collections; + +import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.flow.FlowExecutionStatus; +import org.springframework.batch.core.job.flow.FlowExecutor; + +/** + * Extends {@link org.springframework.batch.core.job.flow.support.state.StepState} to persist what the + * last step that was executed was (used in Decisions and restarts). + * + * @author Michael Minella + * @since 3.0 + */ +public class StepState extends org.springframework.batch.core.job.flow.support.state.StepState { + + /** + * @param step the step that will be executed + */ + public StepState(Step step) { + super(step); + } + + /** + * @param name for the step that will be executed + * @param step the step that will be executed + */ + public StepState(String name, Step step) { + super(name, step); + } + + /* (non-Javadoc) + * @see org.springframework.batch.core.job.flow.support.state.StepState#handle(org.springframework.batch.core.job.flow.FlowExecutor) + */ + @Override + public FlowExecutionStatus handle(FlowExecutor executor) throws Exception { + FlowExecutionStatus result = super.handle(executor); + + executor.getJobExecution().getExecutionContext().put("batch.lastSteps", Collections.singletonList(getStep().getName())); + + return result; + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/step/DecisionStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/step/DecisionStep.java index 3c034c17e..3ef72f6eb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/step/DecisionStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/step/DecisionStep.java @@ -47,13 +47,14 @@ public class DecisionStep extends AbstractStep { this.decider = decider; } + @SuppressWarnings("unchecked") @Override protected void doExecute(StepExecution stepExecution) throws Exception { ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext(); List stepExecutions = new ArrayList(); - if(executionContext.containsKey("batch.splitLastSteps")) { - List stepNames = (List) executionContext.get("batch.splitLastSteps"); + if(executionContext.containsKey("batch.lastSteps")) { + List stepNames = (List) executionContext.get("batch.lastSteps"); for (String stepName : stepNames) { StepExecution curStepExecution = getJobRepository().getLastStepExecution(stepExecution.getJobExecution().getJobInstance(), stepName); @@ -81,8 +82,8 @@ public class DecisionStep extends AbstractStep { stepExecution.getJobExecution().setExitStatus(exitStatus); stepExecution.setExitStatus(exitStatus); - if(executionContext.containsKey("batch.splitLastSteps")) { - executionContext.remove("batch.splitLastSteps"); + if(executionContext.containsKey("batch.lastSteps")) { + executionContext.remove("batch.lastSteps"); } } catch (Exception e) { stepExecution.setTerminateOnly(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/DecisionStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/DecisionStepTests.java index 82705ff94..4ea381988 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/DecisionStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/DecisionStepTests.java @@ -1,6 +1,7 @@ package org.springframework.batch.core.jsr.step; import static org.junit.Assert.assertEquals; +import static org.springframework.batch.core.jsr.JsrTestUtils.restartJob; import static org.springframework.batch.core.jsr.JsrTestUtils.runJob; import java.util.List; @@ -20,6 +21,7 @@ import org.springframework.beans.factory.access.BeanFactoryReference; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.access.ContextSingletonBeanFactoryLocator; +import org.springframework.util.Assert; public class DecisionStepTests { @@ -101,6 +103,45 @@ public class DecisionStepTests { assertEquals(2, StepExecutionCountingDecider.previousStepCount); } + @Test + public void testDecisionRestart() throws Exception { + JobExecution execution = runJob("DecisionStepTests-restart-context", new Properties(), 10000l); + assertEquals(BatchStatus.STOPPED, execution.getBatchStatus()); + + List stepExecutions = BatchRuntime.getJobOperator().getStepExecutions(execution.getExecutionId()); + assertEquals(2, stepExecutions.size()); + + assertEquals("step1", stepExecutions.get(0).getStepName()); + assertEquals("decision1", stepExecutions.get(1).getStepName()); + + JobExecution execution2 = restartJob(execution.getExecutionId(), new Properties(), 10000l); + assertEquals(BatchStatus.COMPLETED, execution2.getBatchStatus()); + + List stepExecutions2 = BatchRuntime.getJobOperator().getStepExecutions(execution2.getExecutionId()); + assertEquals(2, stepExecutions2.size()); + + assertEquals("decision1", stepExecutions2.get(0).getStepName()); + assertEquals("step2", stepExecutions2.get(1).getStepName()); + } + + public static class RestartDecider implements Decider { + + private static int runs = 0; + + @Override + public String decide(StepExecution[] executions) throws Exception { + Assert.isTrue(executions.length == 1); + Assert.isTrue(executions[0].getStepName().equals("step1")); + + if(runs == 0) { + runs++; + return "STOP_HERE"; + } else { + return "CONTINUE"; + } + } + } + public static class StepExecutionCountingDecider implements Decider { static int previousStepCount = 0; diff --git a/spring-batch-core/src/test/resources/META-INF/batch-jobs/DecisionStepTests-restart-context.xml b/spring-batch-core/src/test/resources/META-INF/batch-jobs/DecisionStepTests-restart-context.xml new file mode 100644 index 000000000..5a12c2ff5 --- /dev/null +++ b/spring-batch-core/src/test/resources/META-INF/batch-jobs/DecisionStepTests-restart-context.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + +