From 8e8f9c8101b52b64050d8e92363de2d28319e3b0 Mon Sep 17 00:00:00 2001 From: Michael Minella Date: Tue, 14 Jan 2020 09:36:47 -0600 Subject: [PATCH] Added comparitor for state transitions when using java config Spring Batch orders the transitions as it goes from state to state based on specificity. The XML configuration has always had this functionality. However, when creating the JSR-352 implementation, the mechanism for which this occured was refactored. That occured at about the same time as the java builders were introduced. Because of this crossing of paths, the java configuration option for defining jobs has never correctly sorted the transitions. This PR applys the sorting algorithm to the java configuration, making XML and java configuration behave the same. Resolves #3638 --- .../batch/core/job/builder/FlowBuilder.java | 5 +- .../core/job/builder/FlowBuilderTests.java | 49 ++++++++++++++++++- 2 files changed, 52 insertions(+), 2 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 8a3a16ae3..d06c69c05 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2020 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. @@ -30,6 +30,7 @@ import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.job.flow.State; +import org.springframework.batch.core.job.flow.support.DefaultStateTransitionComparator; 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.DecisionState; @@ -44,6 +45,7 @@ import org.springframework.core.task.TaskExecutor; * conditional transitions that depend on the exit status of the previous step. * * @author Dave Syer + * @author Michael Minella * * @since 2.2 * @@ -244,6 +246,7 @@ public class FlowBuilder { } addDanglingEndStates(); flow.setStateTransitions(transitions); + flow.setStateTransitionComparator(new DefaultStateTransitionComparator()); dirty = false; return flow; } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowBuilderTests.java index 68b2eadbf..1fc4cd4fa 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2020 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. @@ -15,7 +15,11 @@ */ package org.springframework.batch.core.job.builder; +import java.util.Iterator; + import org.junit.Test; + +import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.JobParameters; @@ -23,13 +27,17 @@ import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.UnexpectedJobExecutionException; import org.springframework.batch.core.job.SimpleStepHandler; import org.springframework.batch.core.job.flow.Flow; +import org.springframework.batch.core.job.flow.FlowExecution; import org.springframework.batch.core.job.flow.JobFlowExecutor; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; import org.springframework.batch.core.step.StepSupport; +import static org.junit.Assert.assertEquals; + /** * @author Dave Syer + * @author Michael Minella * */ public class FlowBuilderTests { @@ -47,4 +55,43 @@ public class FlowBuilderTests { }).end().start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution)); } + @Test + public void testTransitionOrdering() throws Exception { + FlowBuilder builder = new FlowBuilder<>("transitionsFlow"); + JobRepository jobRepository = new MapJobRepositoryFactoryBean().getObject(); + JobExecution execution = jobRepository.createJobExecution("foo", new JobParameters()); + + StepSupport stepA = new StepSupport("stepA") { + @Override + public void execute(StepExecution stepExecution) throws JobInterruptedException, + UnexpectedJobExecutionException { + stepExecution.setExitStatus(new ExitStatus("FAILED")); + } + }; + + StepSupport stepB = new StepSupport("stepB") { + @Override + public void execute(StepExecution stepExecution) throws JobInterruptedException, + UnexpectedJobExecutionException { + } + }; + + StepSupport stepC = new StepSupport("stepC") { + @Override + public void execute(StepExecution stepExecution) throws JobInterruptedException, + UnexpectedJobExecutionException { + } + }; + + FlowExecution flowExecution = builder.start(stepA) + .on("*").to(stepB) + .from(stepA).on("FAILED").to(stepC) + .end().start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution)); + + Iterator stepExecutions = execution.getStepExecutions().iterator(); + StepExecution stepExecutionA = stepExecutions.next(); + assertEquals(stepExecutionA.getStepName(), "stepA"); + StepExecution stepExecutionC = stepExecutions.next(); + assertEquals(stepExecutionC.getStepName(), "stepC"); + } }