Skip to content

Commit adc3a42

Browse files
injae-kimfmbenhassine
authored andcommitted
Fix infinite loop in FlowBuilder#next()
Resolves #4432 (cherry picked from commit 4a67b22)
1 parent 955f9a4 commit adc3a42

File tree

2 files changed

+94
-22
lines changed

2 files changed

+94
-22
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
* @author Dave Syer
4949
* @author Michael Minella
5050
* @author Mahmoud Ben Hassine
51+
* @author Injae Kim
5152
* @since 2.2
5253
* @param <Q> the type of object returned by the builder (by default a Flow)
5354
*
@@ -107,7 +108,8 @@ public Q build() {
107108

108109
/**
109110
* Transition to the next step on successful completion of the current step. All other
110-
* outcomes are treated as failures.
111+
* outcomes are treated as failures. If no steps are registered yet, then this method
112+
* will behave in the same way as {@link #start(Step)}.
111113
* @param step the next step
112114
* @return this to enable chaining
113115
*/
@@ -250,26 +252,32 @@ private void doNext(Object input) {
250252
if (this.currentState == null) {
251253
doStart(input);
252254
}
253-
State next = createState(input);
254-
addTransition("COMPLETED", next);
255-
addTransition("*", failedState);
256-
this.currentState = next;
255+
else {
256+
State next = createState(input);
257+
addTransition("COMPLETED", next);
258+
addTransition("*", failedState);
259+
this.currentState = next;
260+
}
257261
}
258262

259263
private void doStart(Object input) {
260264
if (this.currentState != null) {
261265
doFrom(input);
262266
}
263-
this.currentState = createState(input);
267+
else {
268+
this.currentState = createState(input);
269+
}
264270
}
265271

266272
private void doFrom(Object input) {
267273
if (currentState == null) {
268274
doStart(input);
269275
}
270-
State state = createState(input);
271-
tos.put(currentState.getName(), currentState);
272-
this.currentState = state;
276+
else {
277+
State state = createState(input);
278+
tos.put(currentState.getName(), currentState);
279+
this.currentState = state;
280+
}
273281
}
274282

275283
private State createState(Object input) {

spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowBuilderTests.java

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919

2020
import org.junit.jupiter.api.Test;
2121

22+
import org.springframework.batch.core.BatchStatus;
2223
import org.springframework.batch.core.ExitStatus;
2324
import org.springframework.batch.core.JobExecution;
2425
import org.springframework.batch.core.JobInterruptedException;
@@ -34,26 +35,79 @@
3435
import org.springframework.batch.core.step.StepSupport;
3536

3637
import static org.junit.jupiter.api.Assertions.assertEquals;
38+
import static org.junit.jupiter.api.Assertions.assertFalse;
3739

3840
/**
3941
* @author Dave Syer
4042
* @author Michael Minella
4143
* @author Mahmoud Ben Hassine
44+
* @author Injae Kim
4245
*
4346
*/
4447
class FlowBuilderTests {
4548

4649
@Test
47-
void test() throws Exception {
50+
void testNext() throws Exception {
4851
FlowBuilder<Flow> builder = new FlowBuilder<>("flow");
4952
JobRepository jobRepository = new JobRepositorySupport();
5053
JobExecution execution = jobRepository.createJobExecution("foo", new JobParameters());
51-
builder.start(new StepSupport("step") {
52-
@Override
53-
public void execute(StepExecution stepExecution)
54-
throws JobInterruptedException, UnexpectedJobExecutionException {
55-
}
56-
}).end().start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution));
54+
55+
builder.next(createCompleteStep("stepA"))
56+
.end()
57+
.start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution));
58+
59+
Iterator<StepExecution> stepExecutions = execution.getStepExecutions().iterator();
60+
assertEquals(stepExecutions.next().getStepName(), "stepA");
61+
assertFalse(stepExecutions.hasNext());
62+
}
63+
64+
@Test
65+
void testMultipleNext() throws Exception {
66+
FlowBuilder<Flow> builder = new FlowBuilder<>("flow");
67+
JobRepository jobRepository = new JobRepositorySupport();
68+
JobExecution execution = jobRepository.createJobExecution("foo", new JobParameters());
69+
70+
builder.next(createCompleteStep("stepA"))
71+
.next(createCompleteStep("stepB"))
72+
.next(createCompleteStep("stepC"))
73+
.end()
74+
.start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution));
75+
76+
Iterator<StepExecution> stepExecutions = execution.getStepExecutions().iterator();
77+
assertEquals(stepExecutions.next().getStepName(), "stepA");
78+
assertEquals(stepExecutions.next().getStepName(), "stepB");
79+
assertEquals(stepExecutions.next().getStepName(), "stepC");
80+
assertFalse(stepExecutions.hasNext());
81+
}
82+
83+
@Test
84+
void testStart() throws Exception {
85+
FlowBuilder<Flow> builder = new FlowBuilder<>("flow");
86+
JobRepository jobRepository = new JobRepositorySupport();
87+
JobExecution execution = jobRepository.createJobExecution("foo", new JobParameters());
88+
89+
builder.start(createCompleteStep("stepA"))
90+
.end()
91+
.start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution));
92+
93+
Iterator<StepExecution> stepExecutions = execution.getStepExecutions().iterator();
94+
assertEquals(stepExecutions.next().getStepName(), "stepA");
95+
assertFalse(stepExecutions.hasNext());
96+
}
97+
98+
@Test
99+
void testFrom() throws Exception {
100+
FlowBuilder<Flow> builder = new FlowBuilder<>("flow");
101+
JobRepository jobRepository = new JobRepositorySupport();
102+
JobExecution execution = jobRepository.createJobExecution("foo", new JobParameters());
103+
104+
builder.from(createCompleteStep("stepA"))
105+
.end()
106+
.start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution));
107+
108+
Iterator<StepExecution> stepExecutions = execution.getStepExecutions().iterator();
109+
assertEquals(stepExecutions.next().getStepName(), "stepA");
110+
assertFalse(stepExecutions.hasNext());
57111
}
58112

59113
@Test
@@ -66,7 +120,7 @@ void testTransitionOrdering() throws Exception {
66120
@Override
67121
public void execute(StepExecution stepExecution)
68122
throws JobInterruptedException, UnexpectedJobExecutionException {
69-
stepExecution.setExitStatus(new ExitStatus("FAILED"));
123+
stepExecution.setExitStatus(ExitStatus.FAILED);
70124
}
71125
};
72126

@@ -94,10 +148,20 @@ public void execute(StepExecution stepExecution)
94148
.start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution));
95149

96150
Iterator<StepExecution> stepExecutions = execution.getStepExecutions().iterator();
97-
StepExecution stepExecutionA = stepExecutions.next();
98-
assertEquals(stepExecutionA.getStepName(), "stepA");
99-
StepExecution stepExecutionC = stepExecutions.next();
100-
assertEquals(stepExecutionC.getStepName(), "stepC");
151+
assertEquals(stepExecutions.next().getStepName(), "stepA");
152+
assertEquals(stepExecutions.next().getStepName(), "stepC");
153+
assertFalse(stepExecutions.hasNext());
154+
}
155+
156+
private static StepSupport createCompleteStep(String name) {
157+
return new StepSupport(name) {
158+
@Override
159+
public void execute(StepExecution stepExecution)
160+
throws JobInterruptedException, UnexpectedJobExecutionException {
161+
stepExecution.upgradeStatus(BatchStatus.COMPLETED);
162+
stepExecution.setExitStatus(ExitStatus.COMPLETED);
163+
}
164+
};
101165
}
102166

103167
}

0 commit comments

Comments
 (0)