Description
Bug description
When using a JobExecutionDecider the behavior is not accurate according to the documentation of DefaultStateTransitionComparator. Specifically
Hence * > foo* > ??? > fo? > foo
Additionally, the actual behavior when running a Job is not consistent with the test results found in DefaultStateTransitionComparatorTests
In almost all test scenarios the documented behavior is not followed.
Environment
Boot 3.1.4
Java 17
Steps to reproduce
See sample GitHub project to be linked in comments below. The JUnit tests have multiple failing scenarios that should pass.
The tests come in 3 different groups, all of which contain failing tests:
- Tests based solely on the example found in the documentation
- Tests based on the scenarios in DefaultStateTransitionComparator
- Tests created from interpretation of the documentation
Expected behavior
The stated documentation should accurate in regards to the priority Flow. All tests in the sample project should pass.
Activity
robertmcnees commentedon Sep 25, 2023
Sample GitHub Repository
Test Cases
hpoettker commentedon Sep 30, 2023
Hi @robertmcnees, in your tests you seem to assume that Spring Batch will jump to the most generic pattern if there is more than one match. This is not the case. Spring Batch will choose the most specific one.
Documented behavior of Spring Batch
The precedence of transitions is described in the reference documentation: https://docs.spring.io/spring-batch/docs/current/reference/html/step.html#conditionalFlow
It gives the following example
and states
The implementation
The
DefaultStateTransitionComparator
provides an order on patterns such that specific patterns are ranked lower than generic patterns, i.e.foo < fo? < ??? < foo* < *
. The order??? < foo*
holds because any*
is considered to be more generic than any amount of?
.In
SimpleFlow
, Spring Batch organizes the possible transitions in aTreeSet
, whose order is defined byDefaultStateTransitionComparator
. When it has to choose a transition, it will then iterate over the set from most specific to most generic, and it will pick the first transition that matches. This leads to the most specific match being chosen.It seems to me that the Javadoc of
DefaultStateTransitionComparator
, the reference documentation, and the implemented behavior are in sync.robertmcnees commentedon Oct 2, 2023
Thanks for the detailed reply. We are in agreement on the pattern matching priority but I am missing something in the implementation details. Let's focus on the example
??? < foo*
or similarly in the documentationfoo* > ???
. I agree in this example we would expectfoo*
to be prioritized over???
.However this test fails. In this example,
???
is given priority overfoo*
. Is the bug in my test?hpoettker commentedon Oct 2, 2023
According to the logic of DefaultStateTransitionComparator, it is actually
???
that is more specific thanfoo*
. It considers any appearance of*
to be more generic than any amount of?
, no matter where they are placed in the pattern.The merit of this can be debated but it is (maybe too briefly) stated in the Javadoc:
hpoettker commentedon Oct 4, 2023
For the sake of completeness: The Javadoc of
DefaultStateTransitionComparator
does contain an error. It claims thatfoo*
is more specific than*
, i.e.foo* < *
, which would make sense, but as the two strings have the same number of*
s they are compared as strings, which actually yields* < foo*
.This is the root cause of #3996, which mentions the interesting workaround to use
**
, where*
is not generic enough. The workaround uses that* < foo* < **
as more*
s are interpreted as being more generic.robertmcnees commentedon Nov 30, 2023
Thank you @hpoettker for the explanation. I was investigating #3996 and when my unit tests didn't pass it lead me to create this issue instead. Your comments helped to straighten out my thinking and indeed there is no bug in the code, although I do believe the documentation to be incorrect.
There were 2 issues that caused me confusion:
First, I believe the documentation text doesn't match the example. The example provided
* > foo* > ??? > fo? > foo
is sorted from most generic to most specific, or sorted in ascending order of specificity. The tests confirm that the examples are correct.Second, the order of specificity switches between the
DefaultStateTransitionComparator
and what is used in theFlowJobBuilder
. That is whileDefaultStateTransitionComparator
will return* > foo* > ??? > fo? > foo
, theFlowJobBuilder
will use the most specific case first. This turns the example backwards tofoo > fo? > ??? > foo* > *
. This was the mistake I made in my test suite. I was using the example fromDefaultStateTransitionComparator
to make assumptions howFlowJobBuilder
would behave. This explains why all of my tests were completely backwards.To illustrate the switch in ordering (more for my own sake), I created a new passing test for the
JobFlowBuilder
to illustratefoo > foo*
.Compare this to the existing test from
DefaultStateTransitionComparatorTests
that illustratesfoo* > foo
:This is a lot of text for a proposed one word documentation update. I needed to straighten it out in my head. As long as I'm not too far off I'll likely head to #3996 next.
Updated documentation and added unit tests for flow builder priority