another test state machine
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
task runGolden(type: JavaExec) {
|
task runGolden(type: JavaExec) {
|
||||||
mainClass = "click.kamil.springstatemachineexporter.GoldenUpdater"
|
mainClass = "click.kamil.springstatemachineexporter.GoldenUpdater"
|
||||||
classpath = sourceSets.test.runtimeClasspath
|
classpath = sourceSets.test.runtimeClasspath
|
||||||
|
workingDir = rootProject.projectDir
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ include ':state_machines:ultimate_ecosystem_sm'
|
|||||||
include ':state_machines:enterprise_order_system'
|
include ':state_machines:enterprise_order_system'
|
||||||
include ':state_machines:multi_module_sample:api-module'
|
include ':state_machines:multi_module_sample:api-module'
|
||||||
include ':state_machines:multi_module_sample:core-module'
|
include ':state_machines:multi_module_sample:core-module'
|
||||||
|
include ':state_machines:state_machine_enterprise'
|
||||||
|
|||||||
@@ -158,6 +158,24 @@ public class GoldenUpdater {
|
|||||||
Path.of("state_machines/inheritance_extra_functions3_state_machine"),
|
Path.of("state_machines/inheritance_extra_functions3_state_machine"),
|
||||||
Path.of("src/test/resources/golden/G2StateMachineConfiguration"),
|
Path.of("src/test/resources/golden/G2StateMachineConfiguration"),
|
||||||
"G2StateMachineConfiguration"
|
"G2StateMachineConfiguration"
|
||||||
|
),
|
||||||
|
new TestScenario(
|
||||||
|
"Enterprise Order State Machine",
|
||||||
|
Path.of("state_machines/state_machine_enterprise"),
|
||||||
|
Path.of("src/test/resources/golden/OrderStateMachineConfiguration"),
|
||||||
|
"OrderStateMachineConfiguration"
|
||||||
|
),
|
||||||
|
new TestScenario(
|
||||||
|
"Enterprise Document State Machine",
|
||||||
|
Path.of("state_machines/state_machine_enterprise"),
|
||||||
|
Path.of("src/test/resources/golden/DocumentStateMachineConfiguration"),
|
||||||
|
"DocumentStateMachineConfiguration"
|
||||||
|
),
|
||||||
|
new TestScenario(
|
||||||
|
"Enterprise User State Machine",
|
||||||
|
Path.of("state_machines/state_machine_enterprise"),
|
||||||
|
Path.of("src/test/resources/golden/UserStateMachineConfiguration"),
|
||||||
|
"UserStateMachineConfiguration"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
digraph statemachine {
|
||||||
|
rankdir=LR;
|
||||||
|
node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"];
|
||||||
|
edge [fontname="Arial", fontsize=10];
|
||||||
|
|
||||||
|
_start [shape=circle, label="", fillcolor=black, width=0.1];
|
||||||
|
_start -> DRAFT;
|
||||||
|
APPROVED [fillcolor=lightgray];
|
||||||
|
DRAFT -> IN_REVIEW [label="DocumentEvent.SUBMIT", style="solid", color="black"];
|
||||||
|
IN_REVIEW -> APPROVED [label="DocumentEvent.APPROVE", style="solid", color="black"];
|
||||||
|
IN_REVIEW -> DRAFT [label="DocumentEvent.REJECT", style="solid", color="black"];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"metadata" : {
|
||||||
|
"triggers" : [ ],
|
||||||
|
"entryPoints" : [ {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||||
|
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||||
|
"methodName" : "transition",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||||
|
"verb" : "POST"
|
||||||
|
},
|
||||||
|
"parameters" : [ {
|
||||||
|
"name" : "machineType",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "PathVariable" ]
|
||||||
|
}, {
|
||||||
|
"name" : "event",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "PathVariable" ]
|
||||||
|
}, {
|
||||||
|
"name" : "userId",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "RequestParam" ]
|
||||||
|
} ]
|
||||||
|
} ],
|
||||||
|
"callChains" : [ ],
|
||||||
|
"properties" : {
|
||||||
|
"default" : { }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name" : "click.kamil.enterprise.machines.document.DocumentStateMachineConfiguration",
|
||||||
|
"renderChoicesAsDiamonds" : true,
|
||||||
|
"startStates" : [ "DocumentState.DRAFT" ],
|
||||||
|
"transitions" : [ {
|
||||||
|
"type" : "EXTERNAL",
|
||||||
|
"sourceStates" : [ {
|
||||||
|
"rawName" : "DocumentState.DRAFT",
|
||||||
|
"fullIdentifier" : "DocumentState.DRAFT"
|
||||||
|
} ],
|
||||||
|
"targetStates" : [ {
|
||||||
|
"rawName" : "DocumentState.IN_REVIEW",
|
||||||
|
"fullIdentifier" : "DocumentState.IN_REVIEW"
|
||||||
|
} ],
|
||||||
|
"event" : {
|
||||||
|
"rawName" : "DocumentEvent.SUBMIT",
|
||||||
|
"fullIdentifier" : "DocumentEvent.SUBMIT"
|
||||||
|
},
|
||||||
|
"guard" : null,
|
||||||
|
"actions" : [ ],
|
||||||
|
"order" : null
|
||||||
|
}, {
|
||||||
|
"type" : "EXTERNAL",
|
||||||
|
"sourceStates" : [ {
|
||||||
|
"rawName" : "DocumentState.IN_REVIEW",
|
||||||
|
"fullIdentifier" : "DocumentState.IN_REVIEW"
|
||||||
|
} ],
|
||||||
|
"targetStates" : [ {
|
||||||
|
"rawName" : "DocumentState.APPROVED",
|
||||||
|
"fullIdentifier" : "DocumentState.APPROVED"
|
||||||
|
} ],
|
||||||
|
"event" : {
|
||||||
|
"rawName" : "DocumentEvent.APPROVE",
|
||||||
|
"fullIdentifier" : "DocumentEvent.APPROVE"
|
||||||
|
},
|
||||||
|
"guard" : null,
|
||||||
|
"actions" : [ ],
|
||||||
|
"order" : null
|
||||||
|
}, {
|
||||||
|
"type" : "EXTERNAL",
|
||||||
|
"sourceStates" : [ {
|
||||||
|
"rawName" : "DocumentState.IN_REVIEW",
|
||||||
|
"fullIdentifier" : "DocumentState.IN_REVIEW"
|
||||||
|
} ],
|
||||||
|
"targetStates" : [ {
|
||||||
|
"rawName" : "DocumentState.DRAFT",
|
||||||
|
"fullIdentifier" : "DocumentState.DRAFT"
|
||||||
|
} ],
|
||||||
|
"event" : {
|
||||||
|
"rawName" : "DocumentEvent.REJECT",
|
||||||
|
"fullIdentifier" : "DocumentEvent.REJECT"
|
||||||
|
},
|
||||||
|
"guard" : null,
|
||||||
|
"actions" : [ ],
|
||||||
|
"order" : null
|
||||||
|
} ],
|
||||||
|
"endStates" : [ "DocumentState.APPROVED" ]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
@startuml
|
||||||
|
!pragma layout smetana
|
||||||
|
set separator none
|
||||||
|
hide empty description
|
||||||
|
hide stereotype
|
||||||
|
skinparam state {
|
||||||
|
BackgroundColor white
|
||||||
|
BorderColor #94a3b8
|
||||||
|
BorderThickness 1
|
||||||
|
FontName Inter
|
||||||
|
FontSize 9
|
||||||
|
FontStyle bold
|
||||||
|
RoundCorner 20
|
||||||
|
Padding 1
|
||||||
|
}
|
||||||
|
skinparam shadowing false
|
||||||
|
skinparam ArrowFontName JetBrains Mono
|
||||||
|
skinparam ArrowFontSize 8
|
||||||
|
skinparam ArrowColor #cbd5e1
|
||||||
|
skinparam ArrowThickness 1
|
||||||
|
skinparam dpi 110
|
||||||
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
|
[*] --> DocumentState.DRAFT
|
||||||
|
|
||||||
|
|
||||||
|
DocumentState.DRAFT -[#1E90FF,bold]-> DocumentState.IN_REVIEW <<external>> : DocumentEvent.SUBMIT
|
||||||
|
DocumentState.IN_REVIEW -[#1E90FF,bold]-> DocumentState.APPROVED <<external>> : DocumentEvent.APPROVE
|
||||||
|
DocumentState.IN_REVIEW -[#1E90FF,bold]-> DocumentState.DRAFT <<external>> : DocumentEvent.REJECT
|
||||||
|
|
||||||
|
DocumentState.APPROVED --> [*]
|
||||||
|
@enduml
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="DRAFT">
|
||||||
|
<state id="DRAFT">
|
||||||
|
<transition target="IN_REVIEW" event="DocumentEvent.SUBMIT"/>
|
||||||
|
</state>
|
||||||
|
<state id="IN_REVIEW">
|
||||||
|
<transition target="APPROVED" event="DocumentEvent.APPROVE"/>
|
||||||
|
<transition target="DRAFT" event="DocumentEvent.REJECT"/>
|
||||||
|
</state>
|
||||||
|
<state id="APPROVED">
|
||||||
|
</state>
|
||||||
|
</scxml>
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
digraph statemachine {
|
||||||
|
rankdir=LR;
|
||||||
|
node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"];
|
||||||
|
edge [fontname="Arial", fontsize=10];
|
||||||
|
|
||||||
|
_start [shape=circle, label="", fillcolor=black, width=0.1];
|
||||||
|
_start -> NEW;
|
||||||
|
SHIPPED [fillcolor=lightgray];
|
||||||
|
NEW -> PENDING [label="OrderEvent.PAY / λ", style="solid", color="black"];
|
||||||
|
PENDING -> SHIPPED [label="OrderEvent.SHIP", style="solid", color="black"];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"metadata" : {
|
||||||
|
"triggers" : [ ],
|
||||||
|
"entryPoints" : [ {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||||
|
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||||
|
"methodName" : "transition",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||||
|
"verb" : "POST"
|
||||||
|
},
|
||||||
|
"parameters" : [ {
|
||||||
|
"name" : "machineType",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "PathVariable" ]
|
||||||
|
}, {
|
||||||
|
"name" : "event",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "PathVariable" ]
|
||||||
|
}, {
|
||||||
|
"name" : "userId",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "RequestParam" ]
|
||||||
|
} ]
|
||||||
|
} ],
|
||||||
|
"callChains" : [ ],
|
||||||
|
"properties" : {
|
||||||
|
"default" : { }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name" : "click.kamil.enterprise.machines.order.OrderStateMachineConfiguration",
|
||||||
|
"renderChoicesAsDiamonds" : true,
|
||||||
|
"startStates" : [ "OrderState.NEW" ],
|
||||||
|
"transitions" : [ {
|
||||||
|
"type" : "EXTERNAL",
|
||||||
|
"sourceStates" : [ {
|
||||||
|
"rawName" : "OrderState.NEW",
|
||||||
|
"fullIdentifier" : "OrderState.NEW"
|
||||||
|
} ],
|
||||||
|
"targetStates" : [ {
|
||||||
|
"rawName" : "OrderState.PENDING",
|
||||||
|
"fullIdentifier" : "OrderState.PENDING"
|
||||||
|
} ],
|
||||||
|
"event" : {
|
||||||
|
"rawName" : "OrderEvent.PAY",
|
||||||
|
"fullIdentifier" : "OrderEvent.PAY"
|
||||||
|
},
|
||||||
|
"guard" : null,
|
||||||
|
"actions" : [ {
|
||||||
|
"expression" : "context -> System.out.println(\"Payment processed.\")",
|
||||||
|
"isLambda" : true,
|
||||||
|
"internalLogic" : "context -> System.out.println(\"Payment processed.\")",
|
||||||
|
"lineNumber" : 28,
|
||||||
|
"className" : "click.kamil.enterprise.machines.order.OrderStateMachineConfiguration",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/enterprise/machines/order/OrderStateMachineConfiguration.java"
|
||||||
|
} ],
|
||||||
|
"order" : null
|
||||||
|
}, {
|
||||||
|
"type" : "EXTERNAL",
|
||||||
|
"sourceStates" : [ {
|
||||||
|
"rawName" : "OrderState.PENDING",
|
||||||
|
"fullIdentifier" : "OrderState.PENDING"
|
||||||
|
} ],
|
||||||
|
"targetStates" : [ {
|
||||||
|
"rawName" : "OrderState.SHIPPED",
|
||||||
|
"fullIdentifier" : "OrderState.SHIPPED"
|
||||||
|
} ],
|
||||||
|
"event" : {
|
||||||
|
"rawName" : "OrderEvent.SHIP",
|
||||||
|
"fullIdentifier" : "OrderEvent.SHIP"
|
||||||
|
},
|
||||||
|
"guard" : null,
|
||||||
|
"actions" : [ ],
|
||||||
|
"order" : null
|
||||||
|
} ],
|
||||||
|
"endStates" : [ "OrderState.SHIPPED" ]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,32 @@
|
|||||||
|
@startuml
|
||||||
|
!pragma layout smetana
|
||||||
|
set separator none
|
||||||
|
hide empty description
|
||||||
|
hide stereotype
|
||||||
|
skinparam state {
|
||||||
|
BackgroundColor white
|
||||||
|
BorderColor #94a3b8
|
||||||
|
BorderThickness 1
|
||||||
|
FontName Inter
|
||||||
|
FontSize 9
|
||||||
|
FontStyle bold
|
||||||
|
RoundCorner 20
|
||||||
|
Padding 1
|
||||||
|
}
|
||||||
|
skinparam shadowing false
|
||||||
|
skinparam ArrowFontName JetBrains Mono
|
||||||
|
skinparam ArrowFontSize 8
|
||||||
|
skinparam ArrowColor #cbd5e1
|
||||||
|
skinparam ArrowThickness 1
|
||||||
|
skinparam dpi 110
|
||||||
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
|
[*] --> OrderState.NEW
|
||||||
|
|
||||||
|
|
||||||
|
OrderState.NEW -[#1E90FF,bold]-> OrderState.PENDING <<external>> : OrderEvent.PAY / λ
|
||||||
|
OrderState.PENDING -[#1E90FF,bold]-> OrderState.SHIPPED <<external>> : OrderEvent.SHIP
|
||||||
|
|
||||||
|
OrderState.SHIPPED --> [*]
|
||||||
|
@enduml
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="NEW">
|
||||||
|
<state id="NEW">
|
||||||
|
<transition target="PENDING" event="OrderEvent.PAY"/>
|
||||||
|
</state>
|
||||||
|
<state id="PENDING">
|
||||||
|
<transition target="SHIPPED" event="OrderEvent.SHIP"/>
|
||||||
|
</state>
|
||||||
|
<state id="SHIPPED">
|
||||||
|
</state>
|
||||||
|
</scxml>
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
digraph statemachine {
|
||||||
|
rankdir=LR;
|
||||||
|
node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"];
|
||||||
|
edge [fontname="Arial", fontsize=10];
|
||||||
|
|
||||||
|
_start [shape=circle, label="", fillcolor=black, width=0.1];
|
||||||
|
_start -> GUEST;
|
||||||
|
VERIFIED [fillcolor=lightgray];
|
||||||
|
GUEST -> REGISTERED [label="UserEvent.REGISTER", style="solid", color="black"];
|
||||||
|
REGISTERED -> VERIFIED [label="UserEvent.VERIFY", style="solid", color="black"];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"metadata" : {
|
||||||
|
"triggers" : [ ],
|
||||||
|
"entryPoints" : [ {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||||
|
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||||
|
"methodName" : "transition",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||||
|
"verb" : "POST"
|
||||||
|
},
|
||||||
|
"parameters" : [ {
|
||||||
|
"name" : "machineType",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "PathVariable" ]
|
||||||
|
}, {
|
||||||
|
"name" : "event",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "PathVariable" ]
|
||||||
|
}, {
|
||||||
|
"name" : "userId",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "RequestParam" ]
|
||||||
|
} ]
|
||||||
|
} ],
|
||||||
|
"callChains" : [ ],
|
||||||
|
"properties" : {
|
||||||
|
"default" : { }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name" : "click.kamil.enterprise.machines.user.UserStateMachineConfiguration",
|
||||||
|
"renderChoicesAsDiamonds" : true,
|
||||||
|
"startStates" : [ "UserState.GUEST" ],
|
||||||
|
"transitions" : [ {
|
||||||
|
"type" : "EXTERNAL",
|
||||||
|
"sourceStates" : [ {
|
||||||
|
"rawName" : "UserState.GUEST",
|
||||||
|
"fullIdentifier" : "UserState.GUEST"
|
||||||
|
} ],
|
||||||
|
"targetStates" : [ {
|
||||||
|
"rawName" : "UserState.REGISTERED",
|
||||||
|
"fullIdentifier" : "UserState.REGISTERED"
|
||||||
|
} ],
|
||||||
|
"event" : {
|
||||||
|
"rawName" : "UserEvent.REGISTER",
|
||||||
|
"fullIdentifier" : "UserEvent.REGISTER"
|
||||||
|
},
|
||||||
|
"guard" : null,
|
||||||
|
"actions" : [ ],
|
||||||
|
"order" : null
|
||||||
|
}, {
|
||||||
|
"type" : "EXTERNAL",
|
||||||
|
"sourceStates" : [ {
|
||||||
|
"rawName" : "UserState.REGISTERED",
|
||||||
|
"fullIdentifier" : "UserState.REGISTERED"
|
||||||
|
} ],
|
||||||
|
"targetStates" : [ {
|
||||||
|
"rawName" : "UserState.VERIFIED",
|
||||||
|
"fullIdentifier" : "UserState.VERIFIED"
|
||||||
|
} ],
|
||||||
|
"event" : {
|
||||||
|
"rawName" : "UserEvent.VERIFY",
|
||||||
|
"fullIdentifier" : "UserEvent.VERIFY"
|
||||||
|
},
|
||||||
|
"guard" : null,
|
||||||
|
"actions" : [ ],
|
||||||
|
"order" : null
|
||||||
|
} ],
|
||||||
|
"endStates" : [ "UserState.VERIFIED" ]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,32 @@
|
|||||||
|
@startuml
|
||||||
|
!pragma layout smetana
|
||||||
|
set separator none
|
||||||
|
hide empty description
|
||||||
|
hide stereotype
|
||||||
|
skinparam state {
|
||||||
|
BackgroundColor white
|
||||||
|
BorderColor #94a3b8
|
||||||
|
BorderThickness 1
|
||||||
|
FontName Inter
|
||||||
|
FontSize 9
|
||||||
|
FontStyle bold
|
||||||
|
RoundCorner 20
|
||||||
|
Padding 1
|
||||||
|
}
|
||||||
|
skinparam shadowing false
|
||||||
|
skinparam ArrowFontName JetBrains Mono
|
||||||
|
skinparam ArrowFontSize 8
|
||||||
|
skinparam ArrowColor #cbd5e1
|
||||||
|
skinparam ArrowThickness 1
|
||||||
|
skinparam dpi 110
|
||||||
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
|
[*] --> UserState.GUEST
|
||||||
|
|
||||||
|
|
||||||
|
UserState.GUEST -[#1E90FF,bold]-> UserState.REGISTERED <<external>> : UserEvent.REGISTER
|
||||||
|
UserState.REGISTERED -[#1E90FF,bold]-> UserState.VERIFIED <<external>> : UserEvent.VERIFY
|
||||||
|
|
||||||
|
UserState.VERIFIED --> [*]
|
||||||
|
@enduml
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="GUEST">
|
||||||
|
<state id="GUEST">
|
||||||
|
<transition target="REGISTERED" event="UserEvent.REGISTER"/>
|
||||||
|
</state>
|
||||||
|
<state id="REGISTERED">
|
||||||
|
<transition target="VERIFIED" event="UserEvent.VERIFY"/>
|
||||||
|
</state>
|
||||||
|
<state id="VERIFIED">
|
||||||
|
</state>
|
||||||
|
</scxml>
|
||||||
|
|
||||||
30
state_machines/state_machine_enterprise/build.gradle
Normal file
30
state_machines/state_machine_enterprise/build.gradle
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.boot' version '3.5.3'
|
||||||
|
id 'io.spring.dependency-management' version '1.1.7'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'click.kamil'
|
||||||
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
implementation 'org.springframework.statemachine:spring-statemachine-starter:3.2.0'
|
||||||
|
|
||||||
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
}
|
||||||
|
|
||||||
|
bootJar { enabled = false }
|
||||||
|
jar { enabled = true }
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package click.kamil.enterprise;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class EnterpriseStateMachineApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(EnterpriseStateMachineApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package click.kamil.enterprise.core;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public abstract class AbstractStateMachine<S extends Enum<S> & EnterpriseState, E extends Enum<E> & EnterpriseEvent> {
|
||||||
|
|
||||||
|
private S currentState;
|
||||||
|
private final Map<E, S> transitions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
protected AbstractStateMachine(S initialState) {
|
||||||
|
this.currentState = initialState;
|
||||||
|
configureTransitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getMachineType();
|
||||||
|
|
||||||
|
protected abstract void configureTransitions();
|
||||||
|
|
||||||
|
protected void addTransition(E event, S targetState) {
|
||||||
|
transitions.put(event, targetState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void trigger(E event, TransitionContext<?> context) {
|
||||||
|
S target = transitions.get(event);
|
||||||
|
if (target == null) {
|
||||||
|
throw new IllegalStateException("Invalid event " + event + " for state " + currentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
executePreChecks(currentState, target, context);
|
||||||
|
S oldState = currentState;
|
||||||
|
currentState = target;
|
||||||
|
onTransition(oldState, currentState, event, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void executePreChecks(S from, S to, TransitionContext<?> context) {
|
||||||
|
// Parent checking logic
|
||||||
|
System.out.println("[Abstract] Executing pre-checks from " + from.name() + " to " + to.name() + " by user " + context.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void onTransition(S from, S to, E event, TransitionContext<?> context);
|
||||||
|
|
||||||
|
public S getCurrentState() {
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<E> getEventType() {
|
||||||
|
return (Class<E>) transitions.keySet().iterator().next().getClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package click.kamil.enterprise.core;
|
||||||
|
|
||||||
|
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
|
||||||
|
public abstract class AbstractStateMachineConfiguration<S extends Enum<S> & EnterpriseState, E extends Enum<E> & EnterpriseEvent> extends EnumStateMachineConfigurerAdapter<S, E> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<S, E> states) throws Exception {
|
||||||
|
configureStates(states);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<S, E> transitions) throws Exception {
|
||||||
|
configureTransitions(transitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void configureStates(StateMachineStateConfigurer<S, E> states) throws Exception;
|
||||||
|
protected abstract void configureTransitions(StateMachineTransitionConfigurer<S, E> transitions) throws Exception;
|
||||||
|
|
||||||
|
protected void commonProtectedMethod() {
|
||||||
|
System.out.println("Common protected method called in AbstractStateMachineConfiguration.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package click.kamil.enterprise.core;
|
||||||
|
|
||||||
|
public interface EnterpriseEvent {
|
||||||
|
String name();
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package click.kamil.enterprise.core;
|
||||||
|
|
||||||
|
public interface EnterpriseState {
|
||||||
|
String name();
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package click.kamil.enterprise.core;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TransitionContext<T> {
|
||||||
|
private final String userId;
|
||||||
|
private final Instant timestamp;
|
||||||
|
private final T payload;
|
||||||
|
|
||||||
|
public TransitionContext(String userId, T payload) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.timestamp = Instant.now();
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package click.kamil.enterprise.machines.document;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.EnterpriseEvent;
|
||||||
|
|
||||||
|
public enum DocumentEvent implements EnterpriseEvent {
|
||||||
|
SUBMIT, APPROVE, REJECT;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package click.kamil.enterprise.machines.document;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.EnterpriseState;
|
||||||
|
|
||||||
|
public enum DocumentState implements EnterpriseState {
|
||||||
|
DRAFT, IN_REVIEW, APPROVED;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package click.kamil.enterprise.machines.document;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.AbstractStateMachine;
|
||||||
|
import click.kamil.enterprise.core.TransitionContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DocumentStateMachine extends AbstractStateMachine<DocumentState, DocumentEvent> {
|
||||||
|
|
||||||
|
public DocumentStateMachine() {
|
||||||
|
super(DocumentState.DRAFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMachineType() {
|
||||||
|
return "DOCUMENT";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureTransitions() {
|
||||||
|
addTransition(DocumentEvent.SUBMIT, DocumentState.IN_REVIEW);
|
||||||
|
addTransition(DocumentEvent.APPROVE, DocumentState.APPROVED);
|
||||||
|
addTransition(DocumentEvent.REJECT, DocumentState.DRAFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onTransition(DocumentState from, DocumentState to, DocumentEvent event, TransitionContext<?> context) {
|
||||||
|
System.out.println("[Document] Document modified by " + context.getUserId() + ". Now " + to);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package click.kamil.enterprise.machines.document;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.AbstractStateMachineConfiguration;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.statemachine.config.EnableStateMachineFactory;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachineFactory(name = "documentStateMachineFactory")
|
||||||
|
public class DocumentStateMachineConfiguration extends AbstractStateMachineConfiguration<DocumentState, DocumentEvent> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureStates(StateMachineStateConfigurer<DocumentState, DocumentEvent> states) throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial(DocumentState.DRAFT)
|
||||||
|
.state(DocumentState.IN_REVIEW)
|
||||||
|
.end(DocumentState.APPROVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureTransitions(StateMachineTransitionConfigurer<DocumentState, DocumentEvent> transitions) throws Exception {
|
||||||
|
commonProtectedMethod();
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source(DocumentState.DRAFT).target(DocumentState.IN_REVIEW).event(DocumentEvent.SUBMIT)
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source(DocumentState.IN_REVIEW).target(DocumentState.APPROVED).event(DocumentEvent.APPROVE)
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source(DocumentState.IN_REVIEW).target(DocumentState.DRAFT).event(DocumentEvent.REJECT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package click.kamil.enterprise.machines.order;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.EnterpriseEvent;
|
||||||
|
|
||||||
|
public enum OrderEvent implements EnterpriseEvent {
|
||||||
|
PAY, SHIP;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package click.kamil.enterprise.machines.order;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.EnterpriseState;
|
||||||
|
|
||||||
|
public enum OrderState implements EnterpriseState {
|
||||||
|
NEW, PENDING, SHIPPED;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package click.kamil.enterprise.machines.order;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.AbstractStateMachine;
|
||||||
|
import click.kamil.enterprise.core.TransitionContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class OrderStateMachine extends AbstractStateMachine<OrderState, OrderEvent> {
|
||||||
|
|
||||||
|
public OrderStateMachine() {
|
||||||
|
super(OrderState.NEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMachineType() {
|
||||||
|
return "ORDER";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureTransitions() {
|
||||||
|
addTransition(OrderEvent.PAY, OrderState.PENDING);
|
||||||
|
addTransition(OrderEvent.SHIP, OrderState.SHIPPED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void executePreChecks(OrderState from, OrderState to, TransitionContext<?> context) {
|
||||||
|
super.executePreChecks(from, to, context);
|
||||||
|
if (to == OrderState.SHIPPED && from != OrderState.PENDING) {
|
||||||
|
throw new IllegalStateException("Must be PENDING to ship!");
|
||||||
|
}
|
||||||
|
System.out.println("[Order] Specific pre-checks passed for order.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onTransition(OrderState from, OrderState to, OrderEvent event, TransitionContext<?> context) {
|
||||||
|
System.out.println("[Order] Transitioned from " + from + " to " + to + " via " + event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package click.kamil.enterprise.machines.order;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.AbstractStateMachineConfiguration;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.statemachine.config.EnableStateMachineFactory;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachineFactory(name = "orderStateMachineFactory")
|
||||||
|
public class OrderStateMachineConfiguration extends AbstractStateMachineConfiguration<OrderState, OrderEvent> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureStates(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial(OrderState.NEW)
|
||||||
|
.state(OrderState.PENDING)
|
||||||
|
.end(OrderState.SHIPPED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureTransitions(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
|
||||||
|
commonProtectedMethod();
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source(OrderState.NEW).target(OrderState.PENDING).event(OrderEvent.PAY)
|
||||||
|
.action(context -> System.out.println("Payment processed."))
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source(OrderState.PENDING).target(OrderState.SHIPPED).event(OrderEvent.SHIP);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package click.kamil.enterprise.machines.user;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.EnterpriseEvent;
|
||||||
|
|
||||||
|
public enum UserEvent implements EnterpriseEvent {
|
||||||
|
REGISTER, VERIFY;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package click.kamil.enterprise.machines.user;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.EnterpriseState;
|
||||||
|
|
||||||
|
public enum UserState implements EnterpriseState {
|
||||||
|
GUEST, REGISTERED, VERIFIED;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package click.kamil.enterprise.machines.user;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.AbstractStateMachine;
|
||||||
|
import click.kamil.enterprise.core.TransitionContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class UserStateMachine extends AbstractStateMachine<UserState, UserEvent> {
|
||||||
|
|
||||||
|
public UserStateMachine() {
|
||||||
|
super(UserState.GUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMachineType() {
|
||||||
|
return "USER";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureTransitions() {
|
||||||
|
addTransition(UserEvent.REGISTER, UserState.REGISTERED);
|
||||||
|
addTransition(UserEvent.VERIFY, UserState.VERIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onTransition(UserState from, UserState to, UserEvent event, TransitionContext<?> context) {
|
||||||
|
System.out.println("[User] " + context.getUserId() + " is now " + to);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package click.kamil.enterprise.machines.user;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.AbstractStateMachineConfiguration;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.statemachine.config.EnableStateMachineFactory;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachineFactory(name = "userStateMachineFactory")
|
||||||
|
public class UserStateMachineConfiguration extends AbstractStateMachineConfiguration<UserState, UserEvent> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureStates(StateMachineStateConfigurer<UserState, UserEvent> states) throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial(UserState.GUEST)
|
||||||
|
.state(UserState.REGISTERED)
|
||||||
|
.end(UserState.VERIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureTransitions(StateMachineTransitionConfigurer<UserState, UserEvent> transitions) throws Exception {
|
||||||
|
commonProtectedMethod();
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source(UserState.GUEST).target(UserState.REGISTERED).event(UserEvent.REGISTER)
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source(UserState.REGISTERED).target(UserState.VERIFIED).event(UserEvent.VERIFY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package click.kamil.enterprise.web;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/machine")
|
||||||
|
public class StateMachineController {
|
||||||
|
|
||||||
|
private final StateMachineDispatcher dispatcher;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public StateMachineController(StateMachineDispatcher dispatcher) {
|
||||||
|
this.dispatcher = dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{machineType}/transition/{event}")
|
||||||
|
public ResponseEntity<String> transition(
|
||||||
|
@PathVariable String machineType,
|
||||||
|
@PathVariable String event,
|
||||||
|
@RequestParam(defaultValue = "system-user") String userId) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
String result = dispatcher.dispatch(machineType, event, userId, null);
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.badRequest().body(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package click.kamil.enterprise.web;
|
||||||
|
|
||||||
|
import click.kamil.enterprise.core.AbstractStateMachine;
|
||||||
|
import click.kamil.enterprise.core.EnterpriseEvent;
|
||||||
|
import click.kamil.enterprise.core.TransitionContext;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class StateMachineDispatcher {
|
||||||
|
|
||||||
|
private final Map<String, AbstractStateMachine<?, ?>> registry = new HashMap<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public StateMachineDispatcher(List<AbstractStateMachine<?, ?>> machines) {
|
||||||
|
for (AbstractStateMachine<?, ?> machine : machines) {
|
||||||
|
registry.put(machine.getMachineType().toUpperCase(), machine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
public String dispatch(String machineType, String eventString, String userId, Object payload) {
|
||||||
|
AbstractStateMachine machine = registry.get(machineType.toUpperCase());
|
||||||
|
if (machine == null) {
|
||||||
|
throw new IllegalArgumentException("Unknown machine type: " + machineType);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<? extends Enum> eventType = (Class<? extends Enum>) machine.getEventType();
|
||||||
|
Enum eventEnum;
|
||||||
|
try {
|
||||||
|
eventEnum = Enum.valueOf(eventType, eventString.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new IllegalArgumentException("Invalid event " + eventString + " for machine " + machineType);
|
||||||
|
}
|
||||||
|
|
||||||
|
TransitionContext<Object> context = new TransitionContext<>(userId, payload);
|
||||||
|
machine.trigger((EnterpriseEvent) eventEnum, context);
|
||||||
|
|
||||||
|
return "Triggered " + eventEnum.name() + " on " + machineType + ". New State: " + machine.getCurrentState().name();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user