diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f354389
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,103 @@
+# Created by https://www.gitignore.io/api/maven
+
+.idea
+### Maven ###
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+*.iml
+
+# End of https://www.gitignore.io/api/maven
+
+
+# Created by https://www.gitignore.io/api/eclipse
+
+### Eclipse ###
+
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+### Eclipse Patch ###
+# Eclipse Core
+.project
+
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# Annotation Processing
+.apt_generated
+
+.sts4-cache/
+
+
+# End of https://www.gitignore.io/api/eclipse
+*/target/*
+*/.vscode/settings.json
+.vscode/settings.json
+/target/
+
+.DS_Store
+
+reactor-flow.iml
+reactor-flow.ipr
+reactor-flow.iws
+.env
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..83e8831
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/../../../../../../../:\Users\20012796\Desktop\Projects\java-tools\.idea/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..8ded4ef
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+reactor-flow
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..d2eaff6
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..63c9ae9
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d24ea8e
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..e96534f
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..e69de29
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..8e6ebae
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Julien GALET
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..71bbba5
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,166 @@
+
+
+ 4.0.0
+
+ fr.jtools
+ reactor-flow
+ 0.1.0-SNAPSHOT
+
+ A library to execute flows with reactor. Inspired by https://github.com/j-easy/easy-flows project.
+
+
+ https://github.com/juliengalet/reactor-flow
+
+ git@github.com:juliengalet/reactor-flow.git
+ scm:git:https://github.com/juliengalet/reactor-flow
+ scm:git:https://github.com/juliengalet/reactor-flow
+ HEAD
+
+
+
+
+ MIT License
+ https://github.com/juliengalet/reactor-flow/blob/master/LICENSE.txt
+
+
+
+
+ Github Actions
+ https://github.com/juliengalet/reactor-flow/actions
+
+
+
+
+ galet.julien@gmail.com
+ juliengalet
+ Julien GALET
+
+
+
+
+ 11
+ 11
+ 11
+ UTF-8
+ 3.4.4
+ 5.7.0
+ 3.19.0
+ 2.22.1
+ 3.8.1
+ 2.5.3
+ 3.2.1
+ 3.2.0
+ 3.7.1
+ 3.1.1
+
+
+
+
+ io.projectreactor
+ reactor-core
+ ${reactor.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.7.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.version}
+ test
+
+
+ io.projectreactor
+ reactor-test
+ ${reactor.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+
+
+ src/main/java
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+ ${maven-release-plugin.version}
+
+ v@{project.version}
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ ${java.version}
+ ${java.version}
+ true
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${maven-source-plugin.version}
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven-javadoc-plugin.version}
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+ ${maven-site-plugin.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+ ${maven-project-info-reports-plugin.version}
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/fr/jtools/reactorflow/builder/ConditionalFlowBuilder.java b/src/main/java/fr/jtools/reactorflow/builder/ConditionalFlowBuilder.java
new file mode 100644
index 0000000..82c2101
--- /dev/null
+++ b/src/main/java/fr/jtools/reactorflow/builder/ConditionalFlowBuilder.java
@@ -0,0 +1,92 @@
+package fr.jtools.reactorflow.builder;
+
+import fr.jtools.reactorflow.exception.FlowBuilderException;
+import fr.jtools.reactorflow.flow.ConditionalFlow;
+import fr.jtools.reactorflow.flow.Flow;
+import fr.jtools.reactorflow.state.FlowContext;
+import fr.jtools.reactorflow.state.State;
+
+import java.util.Objects;
+import java.util.function.Predicate;
+
+public final class ConditionalFlowBuilder {
+ public static ConditionalFlowBuilder.Named defaultBuilder() {
+ return new ConditionalFlowBuilder.BuildSteps<>();
+ }
+
+ public static ConditionalFlowBuilder.Named builderForContextOfType(Class contextClass) {
+ return new ConditionalFlowBuilder.BuildSteps<>();
+ }
+
+ private static final class BuildSteps implements ConditionalFlowBuilder.Named,
+ ConditionalFlowBuilder.Condition,
+ ConditionalFlowBuilder.CaseTrue,
+ ConditionalFlowBuilder.CaseFalse,
+ ConditionalFlowBuilder.Build {
+ private String name;
+ private Flow flowCaseTrue;
+ private Flow flowCaseFalse;
+
+ private Predicate> condition;
+
+ private BuildSteps() {
+ }
+
+ public final ConditionalFlowBuilder.Condition named(String name) {
+ if (Objects.isNull(name)) {
+ throw new FlowBuilderException(ConditionalFlowBuilder.class, "name is mandatory");
+ }
+ this.name = name;
+ return this;
+ }
+
+ public final ConditionalFlowBuilder.CaseTrue condition(Predicate> condition) {
+ if (Objects.isNull(condition)) {
+ throw new FlowBuilderException(ConditionalFlowBuilder.class, "condition is mandatory");
+ }
+ this.condition = condition;
+ return this;
+ }
+
+ public final ConditionalFlowBuilder.CaseFalse caseTrue(Flow flow) {
+ if (Objects.isNull(flow)) {
+ throw new FlowBuilderException(ConditionalFlowBuilder.class, "caseTrue is mandatory");
+ }
+ this.flowCaseTrue = flow;
+ return this;
+ }
+
+ public final ConditionalFlowBuilder.Build caseFalse(Flow flow) {
+ if (Objects.isNull(flow)) {
+ throw new FlowBuilderException(ConditionalFlowBuilder.class, "caseFalse is mandatory");
+ }
+ this.flowCaseFalse = flow;
+ return this;
+ }
+
+ public final ConditionalFlow build() {
+ return ConditionalFlow.create(this.name, this.condition, this.flowCaseTrue, this.flowCaseFalse);
+ }
+ }
+
+ public interface CaseTrue {
+ ConditionalFlowBuilder.CaseFalse caseTrue(Flow flow);
+ }
+
+ public interface CaseFalse {
+ ConditionalFlowBuilder.Build caseFalse(Flow flow);
+ }
+
+ public interface Build {
+ ConditionalFlow build();
+ }
+
+ public interface Named {
+ ConditionalFlowBuilder.Condition named(String name);
+ }
+
+ public interface Condition {
+ ConditionalFlowBuilder.CaseTrue condition(Predicate> condition);
+ }
+}
+
diff --git a/src/main/java/fr/jtools/reactorflow/builder/ParallelFlowBuilder.java b/src/main/java/fr/jtools/reactorflow/builder/ParallelFlowBuilder.java
new file mode 100644
index 0000000..cb2c176
--- /dev/null
+++ b/src/main/java/fr/jtools/reactorflow/builder/ParallelFlowBuilder.java
@@ -0,0 +1,128 @@
+package fr.jtools.reactorflow.builder;
+
+import fr.jtools.reactorflow.exception.FlowBuilderException;
+import fr.jtools.reactorflow.flow.Flow;
+import fr.jtools.reactorflow.flow.ParallelFlow;
+import fr.jtools.reactorflow.state.FlowContext;
+import fr.jtools.reactorflow.state.State;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+
+public final class ParallelFlowBuilder {
+ public static BinaryOperator> defaultMergeStrategy() {
+ return (state1, state2) -> state1;
+ }
+
+ public static ParallelFlowBuilder.Named defaultBuilder() {
+ return new ParallelFlowBuilder.BuildSteps<>();
+ }
+
+ public static ParallelFlowBuilder.Named builderForContextOfType(Class contextClass) {
+ return new ParallelFlowBuilder.BuildSteps<>();
+ }
+
+ public static ParallelFlowBuilder.Named builderForMetadataType(Class metadataClass) {
+ return new ParallelFlowBuilder.BuildSteps<>();
+ }
+
+ public static ParallelFlowBuilder.Named builderForTypes(Class contextClass, Class metadataClass) {
+ return new ParallelFlowBuilder.BuildSteps<>();
+ }
+
+ private static final class BuildSteps implements ParallelFlowBuilder.Named,
+ ParallelFlowBuilder.Parallelize,
+ ParallelFlowBuilder.ParallelizedFlow,
+ ParallelFlowBuilder.MergeStrategy,
+ ParallelFlowBuilder.Build {
+ private final List> flows = new ArrayList<>();
+ private Function> parallelizeFromArray;
+ private Flow flowToParallelize;
+ private BinaryOperator> mergeStrategy;
+ private String name;
+
+ private BuildSteps() {
+ }
+
+ public final ParallelFlowBuilder.Parallelize named(String name) {
+ if (Objects.isNull(name)) {
+ throw new FlowBuilderException(ParallelFlowBuilder.class, "name is mandatory");
+ }
+ this.name = name;
+ return this;
+ }
+
+ public final ParallelFlowBuilder.MergeStrategy parallelize(List> flows) {
+ if (Objects.isNull(flows)) {
+ throw new FlowBuilderException(ParallelFlowBuilder.class, "flows are mandatory");
+ }
+ this.flows.addAll(flows);
+ return this;
+ }
+
+ @SafeVarargs
+ public final ParallelFlowBuilder.MergeStrategy parallelize(Flow... flows) {
+ if (Objects.isNull(flows)) {
+ throw new FlowBuilderException(ParallelFlowBuilder.class, "flows are mandatory");
+ }
+ this.flows.addAll(List.of(flows));
+ return this;
+ }
+
+ public final ParallelFlowBuilder.ParallelizedFlow parallelizeFromArray(Function> parallelizeFromArray) {
+ if (Objects.isNull(parallelizeFromArray)) {
+ throw new FlowBuilderException(ParallelFlowBuilder.class, "parallelizeFromArray is mandatory");
+ }
+ this.parallelizeFromArray = parallelizeFromArray;
+ return this;
+ }
+
+ public final ParallelFlowBuilder.MergeStrategy parallelizedFlow(Flow parallelizedFlow) {
+ if (Objects.isNull(parallelizedFlow)) {
+ throw new FlowBuilderException(ParallelFlowBuilder.class, "parallelizedFlow is mandatory");
+ }
+ this.flowToParallelize = parallelizedFlow;
+ return this;
+ }
+
+ public final ParallelFlowBuilder.Build mergeStrategy(BinaryOperator> mergeStrategy) {
+ if (Objects.isNull(mergeStrategy)) {
+ throw new FlowBuilderException(ParallelFlowBuilder.class, "mergeStrategy is mandatory");
+ }
+ this.mergeStrategy = mergeStrategy;
+ return this;
+ }
+
+ public final ParallelFlow build() {
+ return ParallelFlow.create(this.name, this.flows, this.mergeStrategy, this.parallelizeFromArray, this.flowToParallelize);
+ }
+ }
+
+ public interface Named {
+ ParallelFlowBuilder.Parallelize named(String name);
+ }
+
+ public interface Parallelize {
+ ParallelFlowBuilder.MergeStrategy parallelize(List> flows);
+
+ ParallelFlowBuilder.MergeStrategy parallelize(Flow... flows);
+
+ ParallelFlowBuilder.ParallelizedFlow parallelizeFromArray(Function> parallelizeFromArray);
+
+ }
+
+ public interface ParallelizedFlow {
+ ParallelFlowBuilder.MergeStrategy parallelizedFlow(Flow flow);
+ }
+
+ public interface MergeStrategy {
+ ParallelFlowBuilder.Build mergeStrategy(BinaryOperator> mergeStrategy);
+ }
+
+ public interface Build {
+ ParallelFlow build();
+ }
+}
diff --git a/src/main/java/fr/jtools/reactorflow/builder/RecoverableFlowBuilder.java b/src/main/java/fr/jtools/reactorflow/builder/RecoverableFlowBuilder.java
new file mode 100644
index 0000000..1eeef82
--- /dev/null
+++ b/src/main/java/fr/jtools/reactorflow/builder/RecoverableFlowBuilder.java
@@ -0,0 +1,91 @@
+package fr.jtools.reactorflow.builder;
+
+import fr.jtools.reactorflow.exception.FlowBuilderException;
+import fr.jtools.reactorflow.exception.RecoverableFlowException;
+import fr.jtools.reactorflow.flow.Flow;
+import fr.jtools.reactorflow.flow.RecoverableFlow;
+import fr.jtools.reactorflow.state.FlowContext;
+
+import java.util.Objects;
+
+public final class RecoverableFlowBuilder {
+ public static RecoverableFlowBuilder.Named defaultBuilder() {
+ return new RecoverableFlowBuilder.BuildSteps<>();
+ }
+
+ public static RecoverableFlowBuilder.Named builderForContextOfType(Class contextClass) {
+ return new RecoverableFlowBuilder.BuildSteps<>();
+ }
+
+ private static final class BuildSteps implements RecoverableFlowBuilder.Named,
+ RecoverableFlowBuilder.Try,
+ RecoverableFlowBuilder.Recover,
+ RecoverableFlowBuilder.RecoverOn,
+ RecoverableFlowBuilder.Build {
+
+ private String name;
+ private Flow flow;
+ private Flow recover;
+ private RecoverableFlowException recoverOn;
+
+
+ private BuildSteps() {
+ }
+
+ public final RecoverableFlowBuilder.Try named(String name) {
+ if (Objects.isNull(name)) {
+ throw new FlowBuilderException(RecoverableFlowBuilder.class, "name is mandatory");
+ }
+ this.name = name;
+ return this;
+ }
+
+ public final RecoverableFlowBuilder.Recover tryFlow(Flow flow) {
+ if (Objects.isNull(flow)) {
+ throw new FlowBuilderException(RecoverableFlowBuilder.class, "try flow is mandatory");
+ }
+ this.flow = flow;
+ return this;
+ }
+
+ public final RecoverableFlowBuilder.RecoverOn recover(Flow recoveredFlow) {
+ if (Objects.isNull(recoveredFlow)) {
+ throw new FlowBuilderException(RecoverableFlowBuilder.class, "recovered flow is mandatory");
+ }
+ this.recover = recoveredFlow;
+ return this;
+ }
+
+ public final RecoverableFlowBuilder.Build recoverOn(RecoverableFlowException recoverOn) {
+ if (Objects.isNull(recoverOn)) {
+ throw new FlowBuilderException(RecoverableFlowBuilder.class, "recoverOn is mandatory");
+ }
+ this.recoverOn = recoverOn;
+ return this;
+ }
+
+ public final RecoverableFlow build() {
+ return RecoverableFlow.create(this.name, this.flow, this.recover, this.recoverOn);
+ }
+ }
+
+ public interface Recover {
+ RecoverableFlowBuilder.RecoverOn recover(Flow flow);
+ }
+
+ public interface RecoverOn {
+ RecoverableFlowBuilder.Build recoverOn(RecoverableFlowException recoverOn);
+ }
+
+ public interface Try {
+ RecoverableFlowBuilder.Recover tryFlow(Flow flow);
+ }
+
+ public interface Build {
+ RecoverableFlow build();
+ }
+
+ public interface Named {
+ RecoverableFlowBuilder.Try named(String name);
+ }
+}
diff --git a/src/main/java/fr/jtools/reactorflow/builder/RetryableFlowBuilder.java b/src/main/java/fr/jtools/reactorflow/builder/RetryableFlowBuilder.java
new file mode 100644
index 0000000..b31d950
--- /dev/null
+++ b/src/main/java/fr/jtools/reactorflow/builder/RetryableFlowBuilder.java
@@ -0,0 +1,104 @@
+package fr.jtools.reactorflow.builder;
+
+import fr.jtools.reactorflow.exception.FlowBuilderException;
+import fr.jtools.reactorflow.exception.RecoverableFlowException;
+import fr.jtools.reactorflow.flow.Flow;
+import fr.jtools.reactorflow.flow.RetryableFlow;
+import fr.jtools.reactorflow.state.FlowContext;
+
+import java.util.Objects;
+
+public final class RetryableFlowBuilder {
+ public static RetryableFlowBuilder.Named defaultBuilder() {
+ return new RetryableFlowBuilder.BuildSteps<>();
+ }
+
+ public static RetryableFlowBuilder.Named builderForContextOfType(Class contextClass) {
+ return new RetryableFlowBuilder.BuildSteps<>();
+ }
+
+ private static final class BuildSteps implements RetryableFlowBuilder.Named,
+ RetryableFlowBuilder.Try,
+ RetryableFlowBuilder.RetryOn,
+ RetryableFlowBuilder.RetryTimes,
+ RetryableFlowBuilder.Delay,
+ RetryableFlowBuilder.Build {
+
+ private String name;
+ private Flow flow;
+ private RecoverableFlowException retryOn = RecoverableFlowException.TECHNICAL;
+ private Integer retryTimes = 1;
+ private Integer delay = 100;
+
+ private BuildSteps() {
+ }
+
+ public final RetryableFlowBuilder.Try named(String name) {
+ if (Objects.isNull(name)) {
+ throw new FlowBuilderException(RetryableFlowBuilder.class, "name is mandatory");
+ }
+ this.name = name;
+ return this;
+ }
+
+ public final RetryableFlowBuilder.RetryOn tryFlow(Flow flow) {
+ if (Objects.isNull(flow)) {
+ throw new FlowBuilderException(RetryableFlowBuilder.class, "try flow is mandatory");
+ }
+ this.flow = flow;
+ return this;
+ }
+
+ public final RetryableFlowBuilder.RetryTimes retryOn(RecoverableFlowException retryOn) {
+ if (Objects.isNull(retryOn)) {
+ throw new FlowBuilderException(RetryableFlowBuilder.class, "retryOn is mandatory");
+ }
+ this.retryOn = retryOn;
+ return this;
+ }
+
+ public final RetryableFlowBuilder.Delay retryTimes(Integer retryTimes) {
+ if (Objects.isNull(retryTimes)) {
+ throw new FlowBuilderException(RetryableFlowBuilder.class, "retryTimes is mandatory");
+ }
+ this.retryTimes = retryTimes;
+ return this;
+ }
+
+ public final RetryableFlowBuilder.Build delay(Integer delay) {
+ if (Objects.isNull(delay)) {
+ throw new FlowBuilderException(RetryableFlowBuilder.class, "delay is mandatory");
+ }
+ this.delay = delay;
+ return this;
+ }
+
+ public final RetryableFlow build() {
+ return RetryableFlow.create(this.name, this.flow, this.retryTimes, this.delay, this.retryOn);
+ }
+ }
+
+ public interface Delay {
+ RetryableFlowBuilder.Build delay(Integer delay);
+ }
+
+ public interface RetryOn {
+ RetryableFlowBuilder.RetryTimes retryOn(RecoverableFlowException retryOn);
+ }
+
+ public interface RetryTimes {
+ RetryableFlowBuilder.Delay retryTimes(Integer retryTimes);
+ }
+
+ public interface Try {
+ RetryableFlowBuilder.RetryOn tryFlow(Flow flow);
+ }
+
+ public interface Build {
+ RetryableFlow build();
+ }
+
+ public interface Named {
+ RetryableFlowBuilder.Try named(String name);
+ }
+}
diff --git a/src/main/java/fr/jtools/reactorflow/builder/SequentialFlowBuilder.java b/src/main/java/fr/jtools/reactorflow/builder/SequentialFlowBuilder.java
new file mode 100644
index 0000000..7b18178
--- /dev/null
+++ b/src/main/java/fr/jtools/reactorflow/builder/SequentialFlowBuilder.java
@@ -0,0 +1,78 @@
+package fr.jtools.reactorflow.builder;
+
+import fr.jtools.reactorflow.exception.FlowBuilderException;
+import fr.jtools.reactorflow.flow.Flow;
+import fr.jtools.reactorflow.flow.SequentialFlow;
+import fr.jtools.reactorflow.state.FlowContext;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public final class SequentialFlowBuilder {
+ public static SequentialFlowBuilder.Named defaultBuilder() {
+ return new SequentialFlowBuilder.BuildSteps<>();
+ }
+
+ public static SequentialFlowBuilder.Named builderForContextOfType(Class contextClass) {
+ return new SequentialFlowBuilder.BuildSteps<>();
+ }
+
+ private static final class BuildSteps implements SequentialFlowBuilder.Named,
+ SequentialFlowBuilder.Then,
+ SequentialFlowBuilder.Finally,
+ SequentialFlowBuilder.Build {
+
+ private final List> flows = new ArrayList<>();
+
+ private Flow finalFlow;
+ private String name;
+
+ private BuildSteps() {
+ }
+
+ public final SequentialFlowBuilder.Then named(String name) {
+ if (Objects.isNull(name)) {
+ throw new FlowBuilderException(SequentialFlowBuilder.class, "name is mandatory");
+ }
+ this.name = name;
+ return this;
+ }
+
+ public final SequentialFlowBuilder.Then then(Flow nextFlow) {
+ if (Objects.isNull(nextFlow)) {
+ throw new FlowBuilderException(SequentialFlowBuilder.class, "then flow is mandatory");
+ }
+ this.flows.add(nextFlow);
+ return this;
+ }
+
+ public final SequentialFlowBuilder.Build doFinally(Flow finalFlow) {
+ if (Objects.isNull(finalFlow)) {
+ throw new FlowBuilderException(SequentialFlowBuilder.class, "final flow is mandatory");
+ }
+ this.finalFlow = finalFlow;
+ return this;
+ }
+
+ public final SequentialFlow build() {
+ return SequentialFlow.create(this.name, this.flows, this.finalFlow);
+ }
+ }
+
+ public interface Then extends Finally, Build {
+ SequentialFlowBuilder.Then then(Flow flow);
+ }
+
+ public interface Finally {
+ SequentialFlowBuilder.Build doFinally(Flow flow);
+ }
+
+ public interface Build {
+ SequentialFlow build();
+ }
+
+ public interface Named {
+ SequentialFlowBuilder.Then named(String name);
+ }
+}
diff --git a/src/main/java/fr/jtools/reactorflow/builder/StepFlowBuilder.java b/src/main/java/fr/jtools/reactorflow/builder/StepFlowBuilder.java
new file mode 100644
index 0000000..f6417ad
--- /dev/null
+++ b/src/main/java/fr/jtools/reactorflow/builder/StepFlowBuilder.java
@@ -0,0 +1,68 @@
+package fr.jtools.reactorflow.builder;
+
+import fr.jtools.reactorflow.exception.FlowBuilderException;
+import fr.jtools.reactorflow.flow.Step;
+import fr.jtools.reactorflow.flow.StepFlow;
+import fr.jtools.reactorflow.state.FlowContext;
+
+import java.util.Objects;
+
+public final class StepFlowBuilder {
+ public static StepFlowBuilder.Named defaultBuilder() {
+ return new StepFlowBuilder.BuildSteps<>();
+ }
+
+ public static StepFlowBuilder.Named builderForContextOfType(Class contextClass) {
+ return new StepFlowBuilder.BuildSteps<>();
+ }
+
+ public static StepFlowBuilder.Named builderForMetadataType(Class metadataClass) {
+ return new StepFlowBuilder.BuildSteps<>();
+ }
+
+ public static StepFlowBuilder.Named builderForTypes(Class contextClass, Class metadataClass) {
+ return new StepFlowBuilder.BuildSteps<>();
+ }
+
+ private static final class BuildSteps implements StepFlowBuilder.Named,
+ StepFlowBuilder.Execution,
+ StepFlowBuilder.Build {
+ private Step execution;
+ private String name;
+
+ private BuildSteps() {
+ }
+
+ public final StepFlowBuilder.Execution named(String name) {
+ if (Objects.isNull(name)) {
+ throw new FlowBuilderException(StepFlowBuilder.class, "name is mandatory");
+ }
+ this.name = name;
+ return this;
+ }
+
+ public final StepFlowBuilder.Build execution(Step execution) {
+ if (Objects.isNull(execution)) {
+ throw new FlowBuilderException(StepFlowBuilder.class, "execution is mandatory");
+ }
+ this.execution = execution;
+ return this;
+ }
+
+ public final StepFlow build() {
+ return StepFlow.create(this.name, this.execution);
+ }
+ }
+
+ public interface Build {
+ StepFlow build();
+ }
+
+ public interface Named {
+ StepFlowBuilder.Execution named(String name);
+ }
+
+ public interface Execution {
+ StepFlowBuilder.Build execution(Step execution);
+ }
+}
diff --git a/src/main/java/fr/jtools/reactorflow/builder/SwitchFlowBuilder.java b/src/main/java/fr/jtools/reactorflow/builder/SwitchFlowBuilder.java
new file mode 100644
index 0000000..113d153
--- /dev/null
+++ b/src/main/java/fr/jtools/reactorflow/builder/SwitchFlowBuilder.java
@@ -0,0 +1,150 @@
+package fr.jtools.reactorflow.builder;
+
+import fr.jtools.reactorflow.exception.FlowBuilderException;
+import fr.jtools.reactorflow.flow.Flow;
+import fr.jtools.reactorflow.flow.SwitchFlow;
+import fr.jtools.reactorflow.state.FlowContext;
+import fr.jtools.reactorflow.state.State;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Class used to build a {@link SwitchFlow}.
+ */
+public final class SwitchFlowBuilder {
+ /**
+ * Get a default builder for {@link FlowContext} context.
+ *
+ * @param Context type
+ * @return {@link SwitchFlowBuilder.Named} builder step
+ */
+ public static SwitchFlowBuilder.Named defaultBuilder() {
+ return new SwitchFlowBuilder.BuildSteps<>();
+ }
+
+ /**
+ * Get a builder for a specified context class.
+ *
+ * @param contextClass Context class that will be inferred to {@link T}
+ * @param Context type
+ * @return {@link SwitchFlowBuilder.Named} builder step
+ */
+ public static SwitchFlowBuilder.Named builderForContextOfType(Class contextClass) {
+ return new SwitchFlowBuilder.BuildSteps<>();
+ }
+
+ private static final class BuildSteps implements SwitchFlowBuilder.Named,
+ SwitchFlowBuilder.SwitchCondition,
+ SwitchFlowBuilder.SwitchCase,
+ SwitchFlowBuilder.DefaultCase,
+ SwitchFlowBuilder.Build {
+ /**
+ * The name.
+ */
+ private String name;
+ /**
+ * A {@link Map} containing switch case keys and {@link Flow}s to execute.
+ */
+ private final Map> flows = new HashMap<>();
+ /**
+ * The default {@link Flow} is no switch case matches.
+ */
+ private Flow defaultFlow;
+ /**
+ * The switch condition.
+ */
+ private Function, String> switchCondition;
+
+ private BuildSteps() {
+ }
+
+ public final SwitchCondition named(String name) {
+ if (Objects.isNull(name)) {
+ throw new FlowBuilderException(SwitchFlowBuilder.class, "name is mandatory");
+ }
+ this.name = name;
+ return this;
+ }
+
+ public final SwitchCase switchCondition(Function, String> switchCondition) {
+ if (Objects.isNull(switchCondition)) {
+ throw new FlowBuilderException(SwitchFlowBuilder.class, "switchCondition is mandatory");
+ }
+ this.switchCondition = switchCondition;
+ return this;
+ }
+
+ public final SwitchFlowBuilder.SwitchCase switchCase(String key, Flow flow) {
+ if (Objects.isNull(flow) || Objects.isNull(key)) {
+ throw new FlowBuilderException(SwitchFlowBuilder.class, "key and switchCase flow are mandatory");
+ }
+ this.flows.put(key, flow);
+ return this;
+ }
+
+ public final SwitchFlowBuilder.Build defaultCase(Flow flow) {
+ if (Objects.isNull(flow)) {
+ throw new FlowBuilderException(SwitchFlowBuilder.class, "default flow is mandatory");
+ }
+ this.defaultFlow = flow;
+ return this;
+ }
+
+ public final SwitchFlow build() {
+ return SwitchFlow.create(this.name, this.switchCondition, this.flows, this.defaultFlow);
+ }
+ }
+
+ public interface SwitchCase extends DefaultCase {
+ /**
+ * Define a switch case flow, executed if {@link SwitchFlowBuilder.SwitchCondition} returns the matching key.
+ *
+ * @param key A key
+ * @param flow A {@link Flow}
+ * @return {@link SwitchFlowBuilder.SwitchCase} builder step
+ */
+ SwitchFlowBuilder.SwitchCase switchCase(String key, Flow flow);
+ }
+
+ public interface DefaultCase {
+ /**
+ * Define the default case, if switch condition match none of the switch cases.
+ *
+ * @param flow A {@link Flow}
+ * @return {@link SwitchFlowBuilder.Build} builder step
+ */
+ SwitchFlowBuilder.Build defaultCase(Flow flow);
+ }
+
+ public interface Build {
+ /**
+ * Build the {@link SwitchFlow}.
+ *
+ * @return Built {@link SwitchFlow}
+ */
+ SwitchFlow build();
+ }
+
+ public interface Named {
+ /**
+ * Define flow name.
+ *
+ * @param name The name
+ * @return {@link SwitchFlowBuilder.SwitchCondition} builder step
+ */
+ SwitchFlowBuilder.SwitchCondition named(String name);
+ }
+
+ public interface SwitchCondition {
+ /**
+ * Define the switch condition that will decide which flow should be executed.
+ *
+ * @param switchCondition The switchCondition, a function returning a {@link String} mapped from {@link State}
+ * @return {@link SwitchFlowBuilder.SwitchCase} builder step
+ */
+ SwitchFlowBuilder.SwitchCase switchCondition(Function, String> switchCondition);
+ }
+}
diff --git a/src/main/java/fr/jtools/reactorflow/exception/FlowBuilderException.java b/src/main/java/fr/jtools/reactorflow/exception/FlowBuilderException.java
new file mode 100644
index 0000000..506cf54
--- /dev/null
+++ b/src/main/java/fr/jtools/reactorflow/exception/FlowBuilderException.java
@@ -0,0 +1,20 @@
+package fr.jtools.reactorflow.exception;
+
+public final class FlowBuilderException extends FlowException {
+ public static String mapMessage(Class builder, String message) {
+ return String.format("%s: %s", builder.getSimpleName(), message);
+ }
+
+ @Override
+ public FlowExceptionType getType() {
+ return FlowExceptionType.BUILDER;
+ }
+
+ public FlowBuilderException(Class builder, String message) {
+ super(FlowBuilderException.mapMessage(builder, message));
+ }
+
+ public FlowBuilderException(Throwable cause, Class