Skip to content

Commit 023f5d2

Browse files
Add ExitStatus overload for on() method in builder classes
Introduce on(ExitStatus) method in builder classes for better type safety Fixes [#5306](#5306) Add on(ExitStatus) methods as overloaded convenience methods that delegate directly to the existing on(String) methods by extracting the underlying exit code. The new method names make the type-safe behavior explicit and improve API consistency across builder classes. Changes: - `SimpleJobBuilder.on(ExitStatus)` - delegates to on(String) - `FlowBuilder.on(ExitStatus)` - delegates to on(String) - `FlowBuilder.UnterminatedFlowBuilder.on(ExitStatus)` - delegates to on(String) The existing on(String) methods remain for backward compatibility and to preserve wildcard pattern matching. Signed-off-by: Taha Yigit Melek <yigittahamelek@gmail.com>
1 parent b24a85e commit 023f5d2

File tree

4 files changed

+103
-0
lines changed

4 files changed

+103
-0
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,16 @@ public TransitionBuilder<Q> on(String pattern) {
224224
return new TransitionBuilder<>(this, pattern);
225225
}
226226

227+
/**
228+
* Start a transition to a new state if the exit status from the previous state
229+
* matches the status given.
230+
* @param status the exit status on which to take this transition
231+
* @return a builder to enable fluent chaining
232+
*/
233+
public TransitionBuilder<Q> on(ExitStatus status) {
234+
return on(status.getExitCode());
235+
}
236+
227237
/**
228238
* A synonym for {@link #build()} which callers might find useful. Subclasses can
229239
* override build to create an object of the desired type (e.g. a parent builder or an
@@ -437,6 +447,16 @@ public TransitionBuilder<Q> on(String pattern) {
437447
return new TransitionBuilder<>(parent, pattern);
438448
}
439449

450+
/**
451+
* Start a transition to a new state if the exit status from the previous state
452+
* matches the status given.
453+
* @param status the exit status on which to take this transition
454+
* @return a TransitionBuilder
455+
*/
456+
public TransitionBuilder<Q> on(ExitStatus status) {
457+
return on(status.getExitCode());
458+
}
459+
440460
}
441461

442462
/**

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.jspecify.annotations.NullUnmarked;
2222

23+
import org.springframework.batch.core.ExitStatus;
2324
import org.springframework.batch.core.job.Job;
2425
import org.springframework.batch.core.step.Step;
2526
import org.springframework.batch.core.job.SimpleJob;
@@ -99,6 +100,15 @@ public FlowBuilder.TransitionBuilder<FlowJobBuilder> on(String pattern) {
99100
return builder.on(pattern);
100101
}
101102

103+
/**
104+
* Branch into a flow conditional on the outcome of the current step.
105+
* @param status the exit status of the current step
106+
* @return a builder for fluent chaining
107+
*/
108+
public FlowBuilder.TransitionBuilder<FlowJobBuilder> on(ExitStatus status) {
109+
return on(status.getExitCode());
110+
}
111+
102112
/**
103113
* Start with this decider. Returns a flow builder and when the flow is ended a job
104114
* builder will be returned to continue the job configuration if needed.

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,49 @@ public void execute(StepExecution stepExecution) throws UnexpectedJobExecutionEx
166166
assertFalse(stepExecutions.hasNext());
167167
}
168168

169+
@Test
170+
void testOnExitStatus() throws Exception {
171+
FlowBuilder<Flow> builder = new FlowBuilder<>("transitionsFlow");
172+
JobRepository jobRepository = new ResourcelessJobRepository();
173+
JobParameters jobParameters = new JobParameters();
174+
JobInstance jobInstance = jobRepository.createJobInstance("foo", jobParameters);
175+
JobExecution jobExecution = jobRepository.createJobExecution(jobInstance, jobParameters,
176+
new ExecutionContext());
177+
178+
StepSupport stepA = new StepSupport("stepA") {
179+
@Override
180+
public void execute(StepExecution stepExecution) throws UnexpectedJobExecutionException {
181+
stepExecution.setExitStatus(ExitStatus.FAILED);
182+
}
183+
};
184+
185+
StepSupport stepB = new StepSupport("stepB") {
186+
@Override
187+
public void execute(StepExecution stepExecution) throws UnexpectedJobExecutionException {
188+
}
189+
};
190+
191+
StepSupport stepC = new StepSupport("stepC") {
192+
@Override
193+
public void execute(StepExecution stepExecution) throws UnexpectedJobExecutionException {
194+
}
195+
};
196+
197+
FlowExecution flowExecution = builder.start(stepA)
198+
.on("*")
199+
.to(stepB)
200+
.from(stepA)
201+
.on(ExitStatus.FAILED)
202+
.to(stepC)
203+
.end()
204+
.start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), jobExecution));
205+
206+
Iterator<StepExecution> stepExecutions = jobExecution.getStepExecutions().iterator();
207+
assertEquals("stepA", stepExecutions.next().getStepName());
208+
assertEquals("stepC", stepExecutions.next().getStepName());
209+
assertFalse(stepExecutions.hasNext());
210+
}
211+
169212
private static StepSupport createCompleteStep(String name) {
170213
return new StepSupport(name) {
171214
@Override

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,18 @@ void testBuildOverTwoLines() throws JobInterruptedException {
171171
assertEquals(2, execution.getStepExecutions().size());
172172
}
173173

174+
@Test
175+
void testBuildOnExitStatus() throws JobInterruptedException {
176+
FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1)
177+
.on(ExitStatus.COMPLETED)
178+
.to(step2)
179+
.end();
180+
builder.preventRestart();
181+
builder.build().execute(execution);
182+
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
183+
assertEquals(2, execution.getStepExecutions().size());
184+
}
185+
174186
@Test
175187
void testBuildSubflow() throws JobInterruptedException {
176188
Flow flow = new FlowBuilder<Flow>("subflow").from(step1).end();
@@ -278,6 +290,24 @@ public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecu
278290
assertEquals(1, execution.getStepExecutions().size());
279291
}
280292

293+
@Test
294+
void testBuildWithDeciderAtStartOnExitStatus() throws JobInterruptedException {
295+
JobExecutionDecider decider = new JobExecutionDecider() {
296+
private int count = 0;
297+
298+
@Override
299+
public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecution stepExecution) {
300+
count++;
301+
return count < 2 ? new FlowExecutionStatus("ONGOING") : FlowExecutionStatus.COMPLETED;
302+
}
303+
};
304+
JobFlowBuilder builder = new JobBuilder("flow", jobRepository).start(decider);
305+
builder.on(ExitStatus.COMPLETED).end().from(decider).on("*").to(step1).end();
306+
builder.build().preventRestart().build().execute(execution);
307+
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
308+
assertEquals(1, execution.getStepExecutions().size());
309+
}
310+
281311
@Test
282312
void testBuildWithDeciderPriorityOnWildcardCount() throws JobInterruptedException {
283313
JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY");

0 commit comments

Comments
 (0)