jakarta-eebatch-processingjsr352java-batchjbatch

JEE Batch Job Specification with many optional Steps


Is there a way to achieve the following logic with JSR 352 Batch API? I have a series of Steps that each need to be executed based on a different condition known when starting the job. ConditionsEntity is provided by an external system.

public List<Steps> createStepSequence(ConditionsEntity conditions) {
  if (conditions.isStep1Enabled()) {
    steps.add(step1)
  }
  if (conditions.isStep2Enabled()) {
    steps.add(step2)
  }
  if (conditions.isStep3Enabled()) {
    steps.add(step3)
  }
  //many more ifs

return steps;
}

My first attempt fails because of: com.ibm.jbatch.container.exception.BatchContainerRuntimeException: A decision cannot precede another decision. I'm adding the FAILING Code here

<?xml version="1.0" encoding="UTF-8"?>
<job id="myJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd" version="1.0">
    <properties>
        <property name="isExecuteStep2" value="false"/>
        <property name="isExecuteStep3" value="false"/>
    </properties>
    <step id="step1" next="decider1">
        <batchlet ref="myBatchlet1"/>
    </step>
    <decision id="decider1" ref="SkipNextStepDecider">
        <properties>
            <property name="isExecuteNextStep" value="#{jobProperties['isExecuteStep2']}"/>
        </properties>
        <next on="EXECUTE" to="step2"/>
        <next on="SKIP" to="decider2"/>
    </decision>
    <step id="step2">
        <batchlet ref="myBatchlet2"/>
    </step>
    <decision id="decider2" ref="SkipNextStepDecider">
        <properties>
            <property name="isExecuteNextStep" value="#{jobProperties['isExecuteStep3']}"/>
        </properties>
        <next on="EXECUTE" to="step3"/>
        <end on="SKIP"/>
    </decision>
    <step id="step3">
        <batchlet ref="myBatchlet3"/>
    </step>
</job>


@Named
public class SkipNextStepDecider implements Decider {

    @Inject
    @BatchProperty
    private String isExecuteNextStep;

    @Override
    public String decide(StepExecution[] ses) throws Exception {
        if (isExecuteNextStep.equalsIgnoreCase("true")) {
            return "EXECUTE";
        } else {
            return "SKIP";
        }
    }
}

UPDATE I have implemented the following suggested solution with a passThroughStep. It's working correctly, but I would still love to be able to avoid all this code duplication.

<?xml version="1.0" encoding="UTF-8"?>
<job id="decisionpoc" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
    <step id="dummy0" next="decider1">
        <batchlet ref="dummyBatchlet"/>
    </step>
    <decision id="decider1" ref="skipNextStepDecider">
        <properties>
            <property name="condition" value="isExecuteStep1"/>
        </properties>
        <next on="EXECUTE" to="step1"/>
        <next on="SKIP" to="dummy1"/>
    </decision>
    <step id="step1" next="decider2">
        <batchlet ref="myBatchlet1"/>
    </step>
    <step id="dummy1" next="decider2">
        <batchlet ref="dummyBatchlet"/>
    </step>
    <decision id="decider2" ref="skipNextStepDecider">
        <properties>
            <property name="condition" value="isExecuteStep2"/>
        </properties>
        <next on="EXECUTE" to="step2"/>
        <next on="SKIP" to="dummy2"/>
    </decision>
    <step id="step2">
        <batchlet ref="myBatchlet2"/>
    </step>
    <step id="dummy2" next="decider3">
        <batchlet ref="dummyBatchlet"/>
    </step>
    <decision id="decider3" ref="skipNextStepDecider">
        <properties>
            <property name="condition" value="isExecuteStep3"/>
        </properties>
        <next on="EXECUTE" to="step3"/>
        <end on="SKIP"/>
    </decision>
    <step id="step3">
        <batchlet ref="myBatchlet3"/>
    </step>
</job>

The Decider

@Named
public class SkipNextStepDecider implements Decider {

    @Inject
    @BatchProperty
    private String condition;

    @Inject
    private JobContext jobContext;

    @Override
    public String decide(StepExecution[] ses) throws Exception {
        Properties parameters = getParameters();
        String isExecuteNextStep = parameters.getProperty(condition);
        if (isExecuteNextStep.equalsIgnoreCase("true")) {
            return "EXECUTE";
        } else {
            return "SKIP";
        }
    }

    private Properties getParameters() {
        JobOperator operator = getJobOperator();
        return operator.getParameters(jobContext.getExecutionId());

    }
}

My Test

public class DecisionPOCTest extends AbstractBatchLOT {

    @Test
    public void testProcess() throws Exception {
        JobOperator jobOperator = getJobOperator();
        Properties properties = new Properties();
        properties.setProperty("isExecuteStep1", "true");
        properties.setProperty("isExecuteStep2", "false");
        properties.setProperty("isExecuteStep3", "true");
        Long executionId = jobOperator.start("poc/decisionPOC", properties);
        JobExecution jobExecution = jobOperator.getJobExecution(executionId);

        jobExecution = BatchTestHelper.keepTestAlive(jobExecution);


        List<StepExecution> stepExecutions = jobOperator.getStepExecutions(executionId);
        List<String> executedSteps = new ArrayList<>();
        for (StepExecution stepExecution : stepExecutions) {
            executedSteps.add(stepExecution.getStepName());
        }

        assertEquals(COMPLETED, jobExecution.getBatchStatus());
        assertEquals(4, stepExecutions.size());
        assertArrayEquals(new String[]{"dummy0", "step1", "dummy2", "step3"}, executedSteps.toArray());
        assertFalse(executedSteps.contains("step2"));
    }
}

Solution

  • It looks like the failure was caused by the fact that one decision had another decison as its next execution point at runtime. As per the JSR 352 spec Section 8.5, it should be a supported use case:

    A job may contain any number of decision elements. A decision element is the target of the "next" attribute from a job-level step, flow, split, or another decision.

    As a workaround, you can try having a pass-through batchlet-step that contains the same condition and logic. For example,

    <step id="pass-through-step">
       <batchlet ref="PassThroughBatchlet"/>
       <next on="EXECUTE" to="step2"/>
       <next on="SKIP" to="decider2"/>
    </step>
    

    Or if some of your conditional logic can be achived with a batchlet-step containing transition elements, you can do away with those decisions.