From 7b42d2c2af2516f17fb33ed5d16be08147cd4ee6 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 17 Dec 2012 10:33:34 +0000 Subject: [PATCH] BATCH-1923: add stop() methods to FlowBuilder --- .../batch/core/job/builder/FlowBuilder.java | 88 +++++++++++++++++-- .../core/job/builder/FlowJobBuilderTests.java | 48 ++++++++++ 2 files changed, 130 insertions(+), 6 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java index dd06f9a97..d593b779b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java @@ -66,10 +66,14 @@ public class FlowBuilder { private EndState completedState; + private EndState stoppedState; + private int decisionCounter = 0; private int splitCounter = 0; + private int endCounter = 0; + private Map states = new HashMap(); private SimpleFlow flow; @@ -81,6 +85,7 @@ public class FlowBuilder { this.prefix = name + "."; this.failedState = new EndState(FlowExecutionStatus.FAILED, prefix + "FAILED"); this.completedState = new EndState(FlowExecutionStatus.COMPLETED, prefix + "COMPLETED"); + this.stoppedState = new EndState(FlowExecutionStatus.STOPPED, prefix + "STOPPED"); } /** @@ -325,10 +330,8 @@ public class FlowBuilder { for (String to : copy.keySet()) { if (!froms.contains(to)) { currentState = copy.get(to); - if (currentState != completedState) { + if (!currentState.isEndState()) { addTransition("COMPLETED", completedState); - } - if (currentState != failedState) { addTransition("*", failedState); } } @@ -337,12 +340,10 @@ public class FlowBuilder { // Then find the states that do not have a default transition for (String from : copy.keySet()) { currentState = copy.get(from); - if (currentState != failedState) { + if (!currentState.isEndState()) { if (!hasFail(from)) { addTransition("*", failedState); } - } - if (currentState != completedState) { if (!hasCompleted(from)) { addTransition("*", completedState); } @@ -373,14 +374,33 @@ public class FlowBuilder { if (transitions.size() == 1) { transitions.add(StateTransition.createEndStateTransition(failedState)); transitions.add(StateTransition.createEndStateTransition(completedState)); + transitions.add(StateTransition.createEndStateTransition(stoppedState)); + } + if (next.isEndState()) { + transitions.add(StateTransition.createEndStateTransition(next)); } dirty = true; } + private void stop(String pattern) { + addTransition(pattern, stoppedState); + } + + private void stop(String pattern, State restart) { + EndState next = new EndState(FlowExecutionStatus.STOPPED, "STOPPED", prefix + "stop" + (endCounter++), true); + addTransition(pattern, next); + currentState = next; + addTransition("*", restart); + } + private void end(String pattern) { addTransition(pattern, completedState); } + private void end(String pattern, String code) { + addTransition(pattern, new EndState(FlowExecutionStatus.COMPLETED, code, prefix + "end" + (endCounter++))); + } + private void fail(String pattern) { addTransition(pattern, failedState); } @@ -471,6 +491,52 @@ public class FlowBuilder { return parent; } + /** + * Signal the successful end of the flow. + * + * @return a FlowBuilder + */ + public FlowBuilder stop() { + parent.stop(pattern); + return parent; + } + + /** + * Stop the flow and provide a flow to start with if the flow is restarted. + * + * @param flow the flow to restart with + * @return a FlowBuilder + */ + public FlowBuilder stopAndRestart(Flow flow) { + State next = parent.createState(flow); + parent.stop(pattern, next); + return parent; + } + + /** + * Stop the flow and provide a decider to start with if the flow is restarted. + * + * @param restart a decider to restart with + * @return a FlowBuilder + */ + public FlowBuilder stopAndRestart(JobExecutionDecider decider) { + State next = parent.createState(decider); + parent.stop(pattern, next); + return parent; + } + + /** + * Stop the flow and provide a step to start with if the flow is restarted. + * + * @param restart the step to restart with + * @return a FlowBuilder + */ + public FlowBuilder stopAndRestart(Step restart) { + State next = parent.createState(restart); + parent.stop(pattern, next); + return parent; + } + /** * Signal the successful end of the flow. * @@ -481,6 +547,16 @@ public class FlowBuilder { return parent; } + /** + * Signal the end of the flow with the status provided. + * + * @return a FlowBuilder + */ + public FlowBuilder end(String status) { + parent.end(pattern, status); + return parent; + } + /** * Signal the end of the flow with an error condition. * diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java index 59b9a4481..bc937ed1d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java @@ -21,6 +21,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.JobParameters; @@ -54,6 +55,16 @@ public class FlowJobBuilderTests { } }; + private StepSupport fails = new StepSupport("fails") { + @Override + public void execute(StepExecution stepExecution) throws JobInterruptedException, + UnexpectedJobExecutionException { + stepExecution.upgradeStatus(BatchStatus.FAILED); + stepExecution.setExitStatus(ExitStatus.FAILED); + jobRepository.update(stepExecution); + } + }; + private StepSupport step2 = new StepSupport("step2") { @Override public void execute(StepExecution stepExecution) throws JobInterruptedException, @@ -164,4 +175,41 @@ public class FlowJobBuilderTests { assertEquals(2, execution.getStepExecutions().size()); } + @Test + public void testBuildWithCustomEndState() throws Exception { + SimpleJobBuilder builder = new JobBuilder("flow").repository(jobRepository).start(step1); + builder.on("COMPLETED").end("FOO"); + builder.preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals("FOO", execution.getExitStatus().getExitCode()); + assertEquals(1, execution.getStepExecutions().size()); + } + + @Test + public void testBuildWithStop() throws Exception { + SimpleJobBuilder builder = new JobBuilder("flow").repository(jobRepository).start(step1); + builder.on("COMPLETED").stop(); + builder.preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.STOPPED, execution.getStatus()); + assertEquals("STOPPED", execution.getExitStatus().getExitCode()); + assertEquals(1, execution.getStepExecutions().size()); + } + + @Test + public void testBuildWithStopAndRestart() throws Exception { + SimpleJobBuilder builder = new JobBuilder("flow").repository(jobRepository).start(fails); + builder.on("FAILED").stopAndRestart(step2); + Job job = builder.build(); + job.execute(execution); + assertEquals(BatchStatus.STOPPED, execution.getStatus()); + assertEquals(1, execution.getStepExecutions().size()); + execution = jobRepository.createJobExecution("flow", new JobParameters()); + job.execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(1, execution.getStepExecutions().size()); + assertEquals("step2", execution.getStepExecutions().iterator().next().getStepName()); + } + }