commit 6d6c56518c2b0c121dc92bbd3c5e738b81c34b06 Author: Florian THIERRY Date: Fri Jul 30 18:36:02 2021 +0200 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2ff871c --- /dev/null +++ b/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.3 + + + org.takiguchi.workflow-validator + workflow-validator + 0.0.1-SNAPSHOT + workflow-validator + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/WorkflowValidatorApplication.java b/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/WorkflowValidatorApplication.java new file mode 100644 index 0000000..d3cb7a6 --- /dev/null +++ b/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/WorkflowValidatorApplication.java @@ -0,0 +1,13 @@ +package org.takiguchi.workflowvalidator.workflowvalidator; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WorkflowValidatorApplication { + + public static void main(String[] args) { + SpringApplication.run(WorkflowValidatorApplication.class, args); + } + +} diff --git a/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/model/Step.java b/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/model/Step.java new file mode 100644 index 0000000..93f62c7 --- /dev/null +++ b/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/model/Step.java @@ -0,0 +1,122 @@ +package org.takiguchi.workflowvalidator.workflowvalidator.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.takiguchi.workflowvalidator.workflowvalidator.model.StepType.FINAL; +import static org.takiguchi.workflowvalidator.workflowvalidator.model.StepType.INITIAL; + +public class Step { + private String name; + private StepType type; + private List previousSteps; + private List nextSteps; + + public Step(String name, StepType type, List previousSteps, List nextSteps) { + this.name = name; + this.type = type; + this.previousSteps = previousSteps; + this.nextSteps = nextSteps; + } + + public static Builder oneStep() { + return new Builder(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public StepType getType() { + return type; + } + + public void setType(StepType type) { + this.type = type; + } + + public List getPreviousSteps() { + return previousSteps; + } + + public void setPreviousSteps(List previousSteps) { + this.previousSteps = previousSteps; + } + + public List getNextSteps() { + return nextSteps; + } + + public void setNextSteps(List nextSteps) { + this.nextSteps = nextSteps; + } + + public boolean hasAtLeastOneNextStep() { + return !Optional.ofNullable(nextSteps) + .map(List::isEmpty) + .orElse(false); + } + + public boolean hasAtLeastOnePreviousStep() { + return !Optional.ofNullable(previousSteps) + .map(List::isEmpty) + .orElse(false); + } + + public boolean isInitialStep() { + return type == INITIAL; + } + + public boolean isFinalStep() { + return type == FINAL; + } + + public void addPreviousStep(Step step) { + previousSteps.add(step); + } + + public void addNextStep(Step step) { + nextSteps.add(step); + } + + @Override + public String toString() { + return "Step{" + name + '}'; + } + + public static class Builder { + private String name; + private StepType type; + private List previousSteps = new ArrayList<>(); + private List nextSteps = new ArrayList<>(); + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder type(StepType type) { + this.type = type; + return this; + } + + public Builder previousSteps(List previousSteps) { + this.previousSteps = previousSteps; + return this; + } + + public Builder nextSteps(List nextSteps) { + this.nextSteps = nextSteps; + return this; + } + + public Step build() { + return new Step(name, type, previousSteps, nextSteps); + } + } +} diff --git a/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/model/StepType.java b/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/model/StepType.java new file mode 100644 index 0000000..58a3f1b --- /dev/null +++ b/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/model/StepType.java @@ -0,0 +1,5 @@ +package org.takiguchi.workflowvalidator.workflowvalidator.model; + +public enum StepType { + INITIAL, MIDDLE, FINAL +} diff --git a/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/model/Workflow.java b/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/model/Workflow.java new file mode 100644 index 0000000..a3c2ee0 --- /dev/null +++ b/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/model/Workflow.java @@ -0,0 +1,15 @@ +package org.takiguchi.workflowvalidator.workflowvalidator.model; + +import java.util.List; + +public class Workflow { + private final List steps; + + public Workflow(List steps) { + this.steps = steps; + } + + public List getSteps() { + return steps; + } +} diff --git a/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/service/WorkflowValidator.java b/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/service/WorkflowValidator.java new file mode 100644 index 0000000..fe32e93 --- /dev/null +++ b/src/main/java/org/takiguchi/workflowvalidator/workflowvalidator/service/WorkflowValidator.java @@ -0,0 +1,71 @@ +package org.takiguchi.workflowvalidator.workflowvalidator.service; + +import org.springframework.util.CollectionUtils; +import org.takiguchi.workflowvalidator.workflowvalidator.model.Step; +import org.takiguchi.workflowvalidator.workflowvalidator.model.Workflow; + +public class WorkflowValidator { + public void validate(Workflow workflow) { + if (CollectionUtils.isEmpty(workflow.getSteps())) { + throw new IllegalStateException("Workflow should have at least one step."); + } + workflow.getSteps().forEach(this::validateStep); + } + + private void validateStep(Step step) { + if (step.hasAtLeastOneNextStep()) { + if (step.isFinalStep()) { + throw new IllegalStateException(String.format("The step %s is final, but it has at least one next step, but it shouldn't.", step)); + } + + // Non final step, that has at least one next step. + validatePreviousStepsOf(step); + } else { + if (!step.isFinalStep()) { + throw new IllegalStateException(String.format("The step %s should have at least one next step.", step)); + } + + // Final Step, that hasn't any next step. + validatePreviousStepsOf(step); + } + } + + private void validatePreviousStepsOf(Step step) { + if (step.hasAtLeastOnePreviousStep()) { + if (step.isInitialStep()) { + throw new IllegalStateException(String.format("The step %s is initial, but is has at least one previous step, but it shouldn't.", step)); + } + + // Non initial step, that has at least one previous step. + + if (canPreviousStepsReachAnInitialStep(step)) { + // Ok + } else { + throw new IllegalStateException(String.format("The step %s hasn't any previous step that can reach an initial step.", step)); + } + } else { + if (!step.isInitialStep()) { + throw new IllegalStateException(String.format("The step %s han't any previous step while it is not an initial step.", step)); + } + + // Initial step, that hasn't any previous step. + } + } + + private boolean canPreviousStepsReachAnInitialStep(Step step) { + return step.getPreviousSteps().stream() + .anyMatch(this::canStepReachAnInitialStep); + } + + private boolean canStepReachAnInitialStep(Step step) { + boolean result; + + if (step.hasAtLeastOnePreviousStep()) { + result = canPreviousStepsReachAnInitialStep(step); + } else { + result = step.isInitialStep(); + } + + return result; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/src/test/java/org/takiguchi/workflowvalidator/workflowvalidator/service/WorkflowValidatorTest.java b/src/test/java/org/takiguchi/workflowvalidator/workflowvalidator/service/WorkflowValidatorTest.java new file mode 100644 index 0000000..705fdf5 --- /dev/null +++ b/src/test/java/org/takiguchi/workflowvalidator/workflowvalidator/service/WorkflowValidatorTest.java @@ -0,0 +1,147 @@ +package org.takiguchi.workflowvalidator.workflowvalidator.service; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.takiguchi.workflowvalidator.workflowvalidator.model.Step; +import org.takiguchi.workflowvalidator.workflowvalidator.model.StepType; +import org.takiguchi.workflowvalidator.workflowvalidator.model.Workflow; + +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; +import static org.takiguchi.workflowvalidator.workflowvalidator.model.Step.oneStep; +import static org.takiguchi.workflowvalidator.workflowvalidator.model.StepType.*; + +class WorkflowValidatorTest { + /** Tested validator. */ + private WorkflowValidator validator; + + @BeforeEach + void setUp() { + validator = new WorkflowValidator(); + } + + @ParameterizedTest + @MethodSource("should_validate_data") + void should_validate(Workflow workflow) { + // when + validator.validate(workflow); + } + + private static Stream should_validate_data() { + return Stream.of( + Arguments.of(workflowGood1()), + Arguments.of(workflowGood2()), + Arguments.of(workflowGood3()) + ); + } + + private static Step buildOneStep(String name, StepType type) { + return oneStep() + .name(name) + .type(type) + .build(); + } + + private static Workflow workflowGood1() { + Step stepInitial = buildOneStep("A", INITIAL); + + Step stepFinal = buildOneStep("B", FINAL); + + stepInitial.addNextStep(stepFinal); + stepFinal.addPreviousStep(stepInitial); + + return new Workflow(List.of(stepInitial, stepFinal)); + } + + private static Workflow workflowGood2() { + Step stepInitial = buildOneStep("A", INITIAL); + Step stepB = buildOneStep("B", MIDDLE); + Step stepFinal = buildOneStep("B", FINAL); + + stepInitial.addNextStep(stepB); + + stepB.addPreviousStep(stepInitial); + stepB.addNextStep(stepFinal); + + stepFinal.addPreviousStep(stepB); + + return new Workflow(List.of(stepInitial, stepB, stepFinal)); + } + + private static Object workflowGood3() { + Step stepInitial = buildOneStep("A", INITIAL); + Step stepB = buildOneStep("B", MIDDLE); + Step stepC = buildOneStep("C", MIDDLE); + Step stepFinal = buildOneStep("D", FINAL); + + stepInitial.addNextStep(stepB); + stepInitial.addNextStep(stepC); + + stepB.addPreviousStep(stepInitial); + stepB.addNextStep(stepFinal); + + stepC.addPreviousStep(stepInitial); + stepC.addNextStep(stepFinal); + + stepFinal.addPreviousStep(stepB); + stepFinal.addPreviousStep(stepC); + + return new Workflow(List.of(stepInitial, stepB, stepC, stepFinal)); + } + + @ParameterizedTest + @MethodSource("should_throw_an_exception_data") + void should_throw_an_exception(Workflow workflow) { + // when + IllegalStateException exception = catchThrowableOfType(() -> validator.validate(workflow), IllegalStateException.class); + + assertThat(exception).isNotNull(); + } + + private static Stream should_throw_an_exception_data() { + return Stream.of( + Arguments.of(workflowBad1()), + Arguments.of(workflowBad2()), + Arguments.of(workflowBad3()), + Arguments.of(workflowBad4()) + ); + } + + private static Workflow workflowBad1() { + Step stepInitial = buildOneStep("A", INITIAL); + + return new Workflow(List.of(stepInitial)); + } + + private static Workflow workflowBad2() { + Step stepFinal = buildOneStep("B", FINAL); + + return new Workflow(List.of(stepFinal)); + } + + private static Workflow workflowBad3() { + Step stepInitial = buildOneStep("A", INITIAL); + Step stepFinal = buildOneStep("B", FINAL); + + stepInitial.addNextStep(stepFinal); + + return new Workflow(List.of(stepInitial, stepFinal)); + } + + private static Workflow workflowBad4() { + Step stepInitial = buildOneStep("A", INITIAL); + Step stepFinal = buildOneStep("B", FINAL); + + stepFinal.addPreviousStep(stepInitial); + + return new Workflow(List.of(stepInitial, stepFinal)); + } + + +} \ No newline at end of file