Skip to content

Commit

Permalink
feat(step name unicity) spring-projects#3757
Browse files Browse the repository at this point in the history
the names of different steps in a job must be different
  • Loading branch information
Fabrice Bibonne committed Feb 5, 2024
1 parent 4d4d52a commit a021d1d
Show file tree
Hide file tree
Showing 6 changed files with 485 additions and 425 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
package org.springframework.batch.core.job;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

import io.micrometer.core.instrument.LongTaskTimer;
Expand All @@ -43,6 +42,7 @@
import org.springframework.batch.core.StartLimitExceededException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.launch.support.ExitCodeMapper;
import org.springframework.batch.core.listener.CompositeJobExecutionListener;
Expand Down Expand Up @@ -300,6 +300,7 @@ public final void execute(JobExecution execution) {

execution.setStartTime(LocalDateTime.now());
updateStatus(execution, BatchStatus.STARTED);
checkStepNamesUnicity();

listener.beforeJob(execution);

Expand Down Expand Up @@ -368,9 +369,23 @@ public final void execute(JobExecution execution) {
finally {
JobSynchronizationManager.release();
}

}
}

protected abstract void checkStepNamesUnicity() throws AlreadyUsedStepNameException ;

private Optional<String> findFirstDoubleElementInList(List<String> strings) {
if (strings==null){
return Optional.empty();
}
Set<String> alreadyChecked=new HashSet<>();
for (String value:strings){
if (alreadyChecked.contains(value)){
return Optional.of(value);
}
alreadyChecked.add(value);
}
return Optional.empty();
}

private void stopObservation(JobExecution execution, Observation observation) {
Expand Down Expand Up @@ -430,6 +445,15 @@ else if (ex instanceof NoSuchJobException || ex.getCause() instanceof NoSuchJobE
return exitStatus;
}

protected static void addToMapCheckingUnicity(Map<String, Step> map, Step step, String name) throws AlreadyUsedStepNameException {
map.merge(name, step, (old, value)->{
if (!old.equals(value)){
throw new AlreadyUsedStepNameException(name);
}
return old;
});
}

private void updateStatus(JobExecution jobExecution, BatchStatus status) {
jobExecution.setStatus(status);
jobRepository.update(jobExecution);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@

package org.springframework.batch.core.job;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.*;

import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
Expand All @@ -27,6 +25,7 @@
import org.springframework.batch.core.StartLimitExceededException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.batch.core.step.StepLocator;

Expand Down Expand Up @@ -145,4 +144,10 @@ protected void doExecute(JobExecution execution)
}
}

@Override
protected void checkStepNamesUnicity() throws AlreadyUsedStepNameException {
Map<String, Step> map = new HashMap<>();
steps.forEach(step->{addToMapCheckingUnicity(map, step, step.getName());});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@
*/
package org.springframework.batch.core.job.flow;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.AbstractJob;
import org.springframework.batch.core.job.SimpleStepHandler;
import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException;
import org.springframework.batch.core.step.StepHolder;
import org.springframework.batch.core.step.StepLocator;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Implementation of the {@link Job} interface that allows for complex flows of steps,
* rather than requiring sequential execution. In general, this job implementation was
Expand Down Expand Up @@ -74,50 +75,55 @@ public void setFlow(Flow flow) {
*/
@Override
public Step getStep(String stepName) {
if (!initialized) {
init();
}
init();
return stepMap.get(stepName);
}


/**
* Initialize the step names
*/
private void init() {
findSteps(flow, stepMap);
initialized = true;
if (!initialized) {
findStepsThrowingIfNameNotUnique(flow, stepMap);
initialized = true;
}
}

private void findSteps(Flow flow, Map<String, Step> map) {
private void findStepsThrowingIfNameNotUnique(Flow flow, Map<String, Step> map) {

for (State state : flow.getStates()) {
if (state instanceof StepLocator locator) {
for (String name : locator.getStepNames()) {
map.put(name, locator.getStep(name));
addToMapCheckingUnicity(map, locator.getStep(name), name);
}
}
else if (state instanceof StepHolder) {
Step step = ((StepHolder) state).getStep();
String name = step.getName();
stepMap.put(name, step);
//TODO remove this else bock ? not executed during tests : the only State wich implements StepHolder is StepState which implements also StepLocator
/*
Tests Coverage
Hits : 30
state instanceof StepHolder
true hits: 0
false hits : 30
*/
else if (state instanceof StepHolder stepHolder) {
Step step = stepHolder.getStep();
addToMapCheckingUnicity(map, step, step.getName());
}
else if (state instanceof FlowHolder) {
for (Flow subflow : ((FlowHolder) state).getFlows()) {
findSteps(subflow, map);
else if (state instanceof FlowHolder flowHolder) {
for (Flow subflow : flowHolder.getFlows()) {
findStepsThrowingIfNameNotUnique(subflow, map);
}
}
}

}

/**
* {@inheritDoc}
*/
@Override
public Collection<String> getStepNames() {
if (!initialized) {
init();
}
init();
return stepMap.keySet();
}

Expand All @@ -139,4 +145,9 @@ protected void doExecute(final JobExecution execution) throws JobExecutionExcept
}
}

@Override
protected void checkStepNamesUnicity() throws AlreadyUsedStepNameException {
init();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.core.step.StepSupport;
Expand All @@ -36,6 +37,7 @@
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
Expand Down Expand Up @@ -215,6 +217,11 @@ public StubJob() {
protected void doExecute(JobExecution execution) throws JobExecutionException {
}

@Override
protected void checkStepNamesUnicity() throws AlreadyUsedStepNameException {

}

@Override
public Step getStep(String stepName) {
return null;
Expand Down
Loading

0 comments on commit a021d1d

Please sign in to comment.