From 1a017dced2fa6f4e44a30aeb536082e8bd7eb494 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 15 Apr 2025 14:08:19 +0200 Subject: [PATCH] Remove dependency to Spring Shell This commit updates the samples to use Java's standard CLI utilities instead of Spring Shell. Resolves #1184 --- .../reference/asciidoc/getting-started.adoc | 2 +- .../asciidoc/sm-examples-cdplayer.adoc | 22 +- .../asciidoc/sm-examples-persist.adoc | 10 +- .../asciidoc/sm-examples-showcase.adoc | 32 +- .../reference/asciidoc/sm-examples-tasks.adoc | 4 +- .../asciidoc/sm-examples-turnstile.adoc | 16 +- .../asciidoc/sm-examples-washer.adoc | 10 +- .../asciidoc/sm-examples-zookeeper.adoc | 12 +- docs/src/reference/asciidoc/sm-examples.adoc | 3 +- gradle.properties | 1 - spring-statemachine-platform/build.gradle | 1 - .../spring-statemachine-platform.gradle | 1 - ...pring-statemachine-samples-cdplayer.gradle | 2 +- .../main/java/demo/cdplayer/Application.java | 3 +- .../src/main/java/demo/cdplayer/CdPlayer.java | 2 +- .../java/demo/cdplayer/CdPlayerCommands.java | 155 ++++--- .../demo/cdplayer/StateMachineCommands.java | 28 +- .../META-INF/spring/spring-shell-plugin.xml | 8 - .../src/main/resources/application.properties | 1 + .../java/demo/cdplayer/CdPlayerTests.java | 389 ------------------ .../cdplayer/src/test/resources/log4j2.xml | 13 - ...spring-statemachine-samples-persist.gradle | 1 - .../main/java/demo/persist/Application.java | 2 +- .../java/demo/persist/PersistCommands.java | 58 ++- .../demo/persist/StateMachineCommands.java | 28 +- .../META-INF/spring/spring-shell-plugin.xml | 8 - .../src/main/resources/application.properties | 1 + .../test/java/demo/persist/PersistTests.java | 125 ------ ...pring-statemachine-samples-showcase.gradle | 2 +- .../main/java/demo/showcase/Application.java | 2 - .../demo/showcase/StateMachineCommands.java | 28 +- .../src/main/resources/application.properties | 2 +- .../java/demo/showcase/ShowcaseTests.java | 375 ----------------- .../spring-statemachine-samples.gradle | 1 - .../demo/AbstractStateMachineCommands.java | 131 +++--- .../src/main/java/demo/BasicCommand.java | 55 +++ ...achinePromptProvider.java => Command.java} | 36 +- .../src/main/java/demo/CommandRunner.java | 96 +++++ .../main/java/demo/CommonConfiguration.java | 79 ---- .../java/demo/StateMachineEventListener.java | 31 ++ .../META-INF/spring/spring-shell-plugin.xml | 8 - .../spring-statemachine-samples-tasks.gradle | 2 +- .../src/main/java/demo/tasks/Application.java | 3 +- .../java/demo/tasks/StateMachineCommands.java | 30 +- .../main/java/demo/tasks/TasksCommands.java | 56 ++- .../META-INF/spring/spring-shell-plugin.xml | 8 - .../src/main/resources/application.properties | 1 + .../StateMachineBlockHoundIntegration.java | 30 -- .../src/test/java/demo/tasks/TasksTests.java | 243 ----------- ...ockhound.integration.BlockHoundIntegration | 1 - .../tasks/src/test/resources/log4j2.xml | 13 - .../tasks/src/test/resources/logback.xml | 17 - ...ring-statemachine-samples-turnstile.gradle | 2 +- .../main/java/demo/turnstile/Application.java | 54 --- .../src/main/java/demo/turnstile/Events.java | 7 + .../demo/turnstile/StateMachineCommands.java | 30 +- .../turnstile/StateMachineConfiguration.java | 42 ++ .../src/main/java/demo/turnstile/States.java | 7 + .../src/main/resources/application.properties | 2 +- .../java/demo/turnstile/TurnstileTests.java | 155 ------- .../spring-statemachine-samples-washer.gradle | 2 +- .../main/java/demo/washer/Application.java | 3 +- .../demo/washer/StateMachineCommands.java | 29 +- .../META-INF/spring/spring-shell-plugin.xml | 8 - .../src/main/resources/application.properties | 1 + .../test/java/demo/washer/WasherTests.java | 185 --------- ...ring-statemachine-samples-zookeeper.gradle | 2 +- .../main/java/demo/zookeeper/Application.java | 3 +- .../demo/zookeeper/StateMachineCommands.java | 28 +- .../META-INF/spring/spring-shell-plugin.xml | 8 - .../src/main/resources/application.properties | 1 + 71 files changed, 733 insertions(+), 2024 deletions(-) delete mode 100644 spring-statemachine-samples/cdplayer/src/main/resources/META-INF/spring/spring-shell-plugin.xml create mode 100644 spring-statemachine-samples/cdplayer/src/main/resources/application.properties delete mode 100644 spring-statemachine-samples/cdplayer/src/test/java/demo/cdplayer/CdPlayerTests.java delete mode 100644 spring-statemachine-samples/cdplayer/src/test/resources/log4j2.xml delete mode 100644 spring-statemachine-samples/persist/src/main/resources/META-INF/spring/spring-shell-plugin.xml create mode 100644 spring-statemachine-samples/persist/src/main/resources/application.properties delete mode 100644 spring-statemachine-samples/persist/src/test/java/demo/persist/PersistTests.java delete mode 100644 spring-statemachine-samples/showcase/src/test/java/demo/showcase/ShowcaseTests.java create mode 100644 spring-statemachine-samples/src/main/java/demo/BasicCommand.java rename spring-statemachine-samples/src/main/java/demo/{StateMachinePromptProvider.java => Command.java} (53%) create mode 100644 spring-statemachine-samples/src/main/java/demo/CommandRunner.java delete mode 100644 spring-statemachine-samples/src/main/java/demo/CommonConfiguration.java create mode 100644 spring-statemachine-samples/src/main/java/demo/StateMachineEventListener.java delete mode 100644 spring-statemachine-samples/src/main/resources/META-INF/spring/spring-shell-plugin.xml delete mode 100644 spring-statemachine-samples/tasks/src/main/resources/META-INF/spring/spring-shell-plugin.xml create mode 100644 spring-statemachine-samples/tasks/src/main/resources/application.properties delete mode 100644 spring-statemachine-samples/tasks/src/test/java/demo/tasks/StateMachineBlockHoundIntegration.java delete mode 100644 spring-statemachine-samples/tasks/src/test/java/demo/tasks/TasksTests.java delete mode 100644 spring-statemachine-samples/tasks/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration delete mode 100644 spring-statemachine-samples/tasks/src/test/resources/log4j2.xml delete mode 100644 spring-statemachine-samples/tasks/src/test/resources/logback.xml create mode 100644 spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Events.java create mode 100644 spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineConfiguration.java create mode 100644 spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/States.java delete mode 100644 spring-statemachine-samples/turnstile/src/test/java/demo/turnstile/TurnstileTests.java delete mode 100644 spring-statemachine-samples/washer/src/main/resources/META-INF/spring/spring-shell-plugin.xml create mode 100644 spring-statemachine-samples/washer/src/main/resources/application.properties delete mode 100644 spring-statemachine-samples/washer/src/test/java/demo/washer/WasherTests.java delete mode 100644 spring-statemachine-samples/zookeeper/src/main/resources/META-INF/spring/spring-shell-plugin.xml create mode 100644 spring-statemachine-samples/zookeeper/src/main/resources/application.properties diff --git a/docs/src/reference/asciidoc/getting-started.adoc b/docs/src/reference/asciidoc/getting-started.adoc index bcf896cc..7b233d32 100644 --- a/docs/src/reference/asciidoc/getting-started.adoc +++ b/docs/src/reference/asciidoc/getting-started.adoc @@ -17,7 +17,7 @@ dependencies outside of Spring Framework within its core system. Other optional parts (such as <>) have dependencies on Zookeeper, while <> has dependencies -on `spring-shell` and `spring-boot`, which pull other dependencies +on `spring-boot`, which pull other dependencies beyond the framework itself. Also, the optional security and data access features have dependencies to on Spring Security and Spring Data modules. diff --git a/docs/src/reference/asciidoc/sm-examples-cdplayer.adoc b/docs/src/reference/asciidoc/sm-examples-cdplayer.adoc index 3d8f6be4..7a0132c4 100644 --- a/docs/src/reference/asciidoc/sm-examples-cdplayer.adoc +++ b/docs/src/reference/asciidoc/sm-examples-cdplayer.adoc @@ -231,15 +231,15 @@ The following example shows how this state machine actually works. ==== [source,text] ---- -sm>sm start +sm>start Entry state IDLE Entry state CLOSED State machine started -sm>cd lcd +sm>lcd No CD -sm>cd library +sm>list 0: Greatest Hits 0: Bohemian Rhapsody 05:56 1: Another One Bites the Dust 03:36 @@ -247,14 +247,14 @@ sm>cd library 0: A Kind of Magic 04:22 1: Under Pressure 04:08 -sm>cd eject +sm>eject Exit state CLOSED Entry state OPEN -sm>cd load 0 +sm>load 0 Loading cd Greatest Hits -sm>cd play +sm>play Exit state OPEN Entry state CLOSED Exit state CLOSED @@ -262,21 +262,21 @@ Exit state IDLE Entry state BUSY Entry state PLAYING -sm>cd lcd +sm>lcd Greatest Hits Bohemian Rhapsody 00:03 -sm>cd forward +sm>forward -sm>cd lcd +sm>lcd Greatest Hits Another One Bites the Dust 00:04 -sm>cd stop +sm>stop Exit state PLAYING Exit state BUSY Entry state IDLE Entry state CLOSED -sm>cd lcd +sm>lcd Greatest Hits ---- ==== diff --git a/docs/src/reference/asciidoc/sm-examples-persist.adoc b/docs/src/reference/asciidoc/sm-examples-persist.adoc index 03b822c3..7b06686e 100644 --- a/docs/src/reference/asciidoc/sm-examples-persist.adoc +++ b/docs/src/reference/asciidoc/sm-examples-persist.adoc @@ -44,27 +44,27 @@ The following example shows the state machine's output: ==== [source,text] ---- -sm>persist db +sm>list Order [id=1, state=PLACED] Order [id=2, state=PROCESSING] Order [id=3, state=SENT] Order [id=4, state=DELIVERED] -sm>persist process 1 +sm>process 1 Exit state PLACED Entry state PROCESSING -sm>persist db +sm>list Order [id=2, state=PROCESSING] Order [id=3, state=SENT] Order [id=4, state=DELIVERED] Order [id=1, state=PROCESSING] -sm>persist deliver 3 +sm>deliver 3 Exit state SENT Entry state DELIVERED -sm>persist db +sm>list Order [id=2, state=PROCESSING] Order [id=4, state=DELIVERED] Order [id=1, state=PROCESSING] diff --git a/docs/src/reference/asciidoc/sm-examples-showcase.adoc b/docs/src/reference/asciidoc/sm-examples-showcase.adoc index 146d4ca9..8d11b8b7 100644 --- a/docs/src/reference/asciidoc/sm-examples-showcase.adoc +++ b/docs/src/reference/asciidoc/sm-examples-showcase.adoc @@ -83,14 +83,14 @@ various events are sent to it: ==== [source,text] ---- -sm>sm start +sm>start Init foo to 0 Entry state S0 Entry state S1 Entry state S11 State machine started -sm>sm event A +sm>event A Event A send sm>sm event C @@ -101,12 +101,12 @@ Entry state S21 Entry state S211 Event C send -sm>sm event H +sm>event H Switch foo to 1 Internal transition source=S0 Event H send -sm>sm event C +sm>event C Exit state S211 Exit state S21 Exit state S2 @@ -114,7 +114,7 @@ Entry state S1 Entry state S11 Event C send -sm>sm event A +sm>event A Exit state S11 Exit state S1 Entry state S1 @@ -144,27 +144,27 @@ handling works: ==== [source,text] ---- -sm>sm variables +sm>variables No variables -sm>sm start +sm>start Init foo to 0 Entry state S0 Entry state S1 Entry state S11 State machine started -sm>sm variables +sm>variables foo=0 -sm>sm event H +sm>event H Internal transition source=S1 Event H send -sm>sm variables +sm>variables foo=0 -sm>sm event C +sm>event C Exit state S11 Exit state S1 Entry state S2 @@ -172,23 +172,23 @@ Entry state S21 Entry state S211 Event C send -sm>sm variables +sm>variables foo=0 -sm>sm event H +sm>event H Switch foo to 1 Internal transition source=S0 Event H send -sm>sm variables +sm>variables foo=1 -sm>sm event H +sm>event H Switch foo to 0 Internal transition source=S2 Event H send -sm>sm variables +sm>variables foo=0 ---- ==== diff --git a/docs/src/reference/asciidoc/sm-examples-tasks.adoc b/docs/src/reference/asciidoc/sm-examples-tasks.adoc index 37476505..0e1e070b 100644 --- a/docs/src/reference/asciidoc/sm-examples-tasks.adoc +++ b/docs/src/reference/asciidoc/sm-examples-tasks.adoc @@ -102,11 +102,11 @@ The following example shows how this state machine actually works: ==== [source,text] ---- -sm>sm start +sm>start State machine started Entry state READY -sm>tasks run +sm>run Exit state READY Entry state TASKS run task on T2 diff --git a/docs/src/reference/asciidoc/sm-examples-turnstile.adoc b/docs/src/reference/asciidoc/sm-examples-turnstile.adoc index 134b3f7e..b94a2ef0 100644 --- a/docs/src/reference/asciidoc/sm-examples-turnstile.adoc +++ b/docs/src/reference/asciidoc/sm-examples-turnstile.adoc @@ -3,7 +3,7 @@ Turnstile is a simple device that gives you access if payment is made. It is a concept that is simple to model using a state machine. In its -simplest, form there are only two states: `LOCKED` and `UNLOCKED`. Two +simplest form, there are only two states: `LOCKED` and `UNLOCKED`. Two events, `COIN` and `PUSH` can happen, depending on whether someone makes a payment or tries to go through the turnstile. The following image shows the state machine: @@ -16,7 +16,7 @@ The following listing shows the enumeration that defines the possible states: .States [source,java,indent=0] ---- -include::samples/demo/turnstile/Application.java[tags=snippetB] +include::samples/demo/turnstile/States.java[tags=snippetB] ---- ==== @@ -26,7 +26,7 @@ The following listing shows the enumeration that defines the events: .Events [source,java,indent=0] ---- -include::samples/demo/turnstile/Application.java[tags=snippetC] +include::samples/demo/turnstile/Events.java[tags=snippetC] ---- ==== @@ -36,7 +36,7 @@ The following listing shows the code that configures the state machine: .Configuration [source,java,indent=0] ---- -include::samples/demo/turnstile/Application.java[tags=snippetA] +include::samples/demo/turnstile/StateMachineConfiguration.java[tags=snippetA] ---- ==== @@ -49,7 +49,7 @@ and shows the command's output: ---- $ java -jar spring-statemachine-samples-turnstile-{revnumber}.jar -sm>sm print +sm>print +----------------------------------------------------------------+ | SM | +----------------------------------------------------------------+ @@ -70,15 +70,15 @@ sm>sm print | | +----------------------------------------------------------------+ -sm>sm start +sm>start State changed to LOCKED State machine started -sm>sm event COIN +sm>event COIN State changed to UNLOCKED Event COIN send -sm>sm event PUSH +sm>event PUSH State changed to LOCKED Event PUSH send ---- diff --git a/docs/src/reference/asciidoc/sm-examples-washer.adoc b/docs/src/reference/asciidoc/sm-examples-washer.adoc index 495fb4cd..d0f83f4e 100644 --- a/docs/src/reference/asciidoc/sm-examples-washer.adoc +++ b/docs/src/reference/asciidoc/sm-examples-washer.adoc @@ -57,28 +57,28 @@ The following example shows how this state machine actually works: ==== [source,text] ---- -sm>sm start +sm>start Entry state RUNNING Entry state WASHING State machine started -sm>sm event RINSE +sm>event RINSE Exit state WASHING Entry state RINSING Event RINSE send -sm>sm event DRY +sm>event DRY Exit state RINSING Entry state DRYING Event DRY send -sm>sm event CUTPOWER +sm>event CUTPOWER Exit state DRYING Exit state RUNNING Entry state POWEROFF Event CUTPOWER send -sm>sm event RESTOREPOWER +sm>event RESTOREPOWER Exit state POWEROFF Entry state RUNNING Entry state WASHING diff --git a/docs/src/reference/asciidoc/sm-examples-zookeeper.adoc b/docs/src/reference/asciidoc/sm-examples-zookeeper.adoc index 0326ed40..591cbaa6 100644 --- a/docs/src/reference/asciidoc/sm-examples-zookeeper.adoc +++ b/docs/src/reference/asciidoc/sm-examples-zookeeper.adoc @@ -48,16 +48,16 @@ The following example shows what happens: .Shell1 [source,text] ---- -sm>sm start +sm>start Entry state LOCKED State machine started -sm>sm event COIN +sm>event COIN Exit state LOCKED Entry state UNLOCKED Event COIN send -sm>sm state +sm>state UNLOCKED ---- ==== @@ -73,10 +73,10 @@ The following example shows the state machine and its output: .Shell2 [source,text] ---- -sm>sm start +sm>start State machine started -sm>sm state +sm>state UNLOCKED ---- ==== @@ -89,7 +89,7 @@ The following example shows the state machine command and its output: .Shell2 [source,text] ---- -sm>sm event PUSH +sm>event PUSH Exit state UNLOCKED Entry state LOCKED Event PUSH send diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index 7a6d264f..0f63ee74 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -58,8 +58,7 @@ The following listing shows how to build the samples: ==== Every sample is located in its own directory under -`spring-statemachine-samples`. The samples are based on Spring Boot and -Spring Shell, and you can find the usual Boot fat jars under every sample +`spring-statemachine-samples`. The samples are based on Spring Boot and you can find the usual Boot fat jars under every sample project's `build/libs` directory. NOTE: The filenames for the jars to which we refer in this section are populated during a diff --git a/gradle.properties b/gradle.properties index 4b1bb41f..787cd19f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,5 @@ version=4.0.1-SNAPSHOT springBootVersion=3.5.0-SNAPSHOT -springShellVersion=3.4.1-SNAPSHOT jakartaPersistenceVersion=3.1.0 kryoVersion=4.0.3 diff --git a/spring-statemachine-platform/build.gradle b/spring-statemachine-platform/build.gradle index a3e1340b..7c8bf1f1 100644 --- a/spring-statemachine-platform/build.gradle +++ b/spring-statemachine-platform/build.gradle @@ -12,7 +12,6 @@ dependencies { api "log4j:log4j:$log4jVersion" api "jakarta.persistence:jakarta.persistence-api:$jakartaPersistenceVersion" api "com.esotericsoftware:kryo-shaded:$kryoVersion" - api "org.springframework.shell:spring-shell-core:$springShellVersion" api "org.eclipse.uml2:uml:$eclipseUml2UmlVersion" api "org.eclipse.uml2:types:$eclipseUml2TypesVersion" api "org.eclipse.uml2:common:$eclipseUml2CommonVersion" diff --git a/spring-statemachine-platform/spring-statemachine-platform.gradle b/spring-statemachine-platform/spring-statemachine-platform.gradle index a14b37b9..8b16f180 100644 --- a/spring-statemachine-platform/spring-statemachine-platform.gradle +++ b/spring-statemachine-platform/spring-statemachine-platform.gradle @@ -10,7 +10,6 @@ description = 'Spring Statemachine BOM' dependencies { api platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion") - api platform("org.springframework.shell:spring-shell-dependencies:$springShellVersion") constraints { api "com.google.code.findbugs:jsr305:$findbugsVersion" api "com.esotericsoftware:kryo-shaded:$kryoVersion" diff --git a/spring-statemachine-samples/cdplayer/spring-statemachine-samples-cdplayer.gradle b/spring-statemachine-samples/cdplayer/spring-statemachine-samples-cdplayer.gradle index 5663848c..4f45cfe2 100644 --- a/spring-statemachine-samples/cdplayer/spring-statemachine-samples-cdplayer.gradle +++ b/spring-statemachine-samples/cdplayer/spring-statemachine-samples-cdplayer.gradle @@ -8,7 +8,7 @@ dependencies { management platform(project(":spring-statemachine-platform")) implementation project(':spring-statemachine-samples-common') implementation project(':spring-statemachine-core') - implementation 'org.springframework.shell:spring-shell-core' + implementation 'org.springframework.boot:spring-boot-starter' testImplementation(testFixtures(project(':spring-statemachine-core'))) testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' diff --git a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java index 3ea1f51d..b570009d 100644 --- a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java +++ b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java @@ -22,6 +22,7 @@ import java.lang.annotation.Target; import java.util.Map; import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.MessageBuilder; @@ -37,7 +38,7 @@ import org.springframework.statemachine.guard.Guard; import reactor.core.publisher.Mono; -@Configuration +@SpringBootApplication(scanBasePackages = "demo") public class Application { @Configuration diff --git a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayer.java b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayer.java index f1d13a38..80913fab 100644 --- a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayer.java +++ b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayer.java @@ -92,7 +92,7 @@ public class CdPlayer { .subscribe(); } - public String getLdcStatus() { + public String getLcdStatus() { return cdStatus + " " + trackStatus; } diff --git a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayerCommands.java b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayerCommands.java index 8c5685e3..c05a6b7e 100644 --- a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayerCommands.java +++ b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayerCommands.java @@ -18,12 +18,15 @@ package demo.cdplayer; import java.text.SimpleDateFormat; import java.util.Date; +import demo.AbstractStateMachineCommands; +import demo.BasicCommand; +import demo.Command; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.shell.command.annotation.Command; -import org.springframework.shell.command.annotation.Option; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; -@Command -public class CdPlayerCommands { +@Configuration +public class CdPlayerCommands extends AbstractStateMachineCommands { @Autowired private CdPlayer cdPlayer; @@ -31,67 +34,119 @@ public class CdPlayerCommands { @Autowired private Library library; - @Command(command = "cd lcd", description = "Prints CD player lcd info") - public String lcd() { - return cdPlayer.getLdcStatus(); - } - - @Command(command = "cd library", description = "List user CD library") - public String library() { - SimpleDateFormat format = new SimpleDateFormat("mm:ss"); - StringBuilder buf = new StringBuilder(); - int i1 = 0; - for (Cd cd : library.getCollection()) { - buf.append(i1++ + ": " + cd.getName() + "\n"); - int i2 = 0; - for (Track track : cd.getTracks()) { - buf.append(" " + i2++ + ": " + track.getName() + " " + format.format(new Date(track.getLength()*1000)) + "\n"); + @Bean + public Command lcd() { + return new BasicCommand("lcd", "Prints CD player lcd info") { + @Override + public String execute(String[] args) { + return cdPlayer.getLcdStatus(); } - } - return buf.toString(); + }; } - @Command(command = "cd load", description = "Load CD into player") - public String load(@Option(longNames = {"", "index"}) int index) { - StringBuilder buf = new StringBuilder(); - try { - Cd cd = library.getCollection().get(index); - cdPlayer.load(cd); - buf.append("Loading cd " + cd); - } catch (Exception e) { - buf.append("Cd with index " + index + " not found, check library"); - } - return buf.toString(); + @Bean + public Command list() { + return new BasicCommand("list", "List user CD library") { + @Override + public String execute(String[] args) { + SimpleDateFormat format = new SimpleDateFormat("mm:ss"); + StringBuilder buf = new StringBuilder(); + int i1 = 0; + for (Cd cd : library.getCollection()) { + buf.append(i1++ + ": " + cd.getName() + "\n"); + int i2 = 0; + for (Track track : cd.getTracks()) { + buf.append(" " + i2++ + ": " + track.getName() + " " + format.format(new Date(track.getLength()*1000)) + "\n"); + } + } + return buf.toString(); + } + }; } - @Command(command = "cd play", description = "Press player play button") - public void play() { - cdPlayer.play(); + @Bean + public Command load() { + return new BasicCommand("load [index]", "Load CD [index] into player") { + @Override + public String execute(String[] args) { + StringBuilder buf = new StringBuilder(); + int index = Integer.parseInt(args[0]); + try { + Cd cd = library.getCollection().get(index); + cdPlayer.load(cd); + buf.append("Loading cd " + cd); + } catch (Exception e) { + buf.append("Cd with index " + index + " not found, check library"); + } + return buf.toString(); + } + }; } - @Command(command = "cd stop", description = "Press player stop button") - public void stop() { - cdPlayer.stop(); + @Bean + public Command play() { + return new BasicCommand("play", "Press player play button") { + @Override + public String execute(String[] args) { + cdPlayer.play(); + return ""; + } + }; } - @Command(command = "cd pause", description = "Press player pause button") - public void pause() { - cdPlayer.pause(); + @Bean + public Command stop() { + return new BasicCommand("stop", "Press player stop button") { + @Override + public String execute(String[] args) { + cdPlayer.stop(); + return ""; + } + }; } - @Command(command = "cd eject", description = "Press player eject button") - public void eject() { - cdPlayer.eject(); + @Bean + public Command pause() { + return new BasicCommand("pause", "Press player pause button") { + @Override + public String execute(String[] args) { + cdPlayer.pause(); + return ""; + } + }; } - @Command(command = "cd forward", description = "Press player forward button") - public void forward() { - cdPlayer.forward(); + @Bean + public Command eject() { + return new BasicCommand("eject", "Press player eject button") { + @Override + public String execute(String[] args) { + cdPlayer.eject(); + return ""; + } + }; } - @Command(command = "cd back", description = "Press player back button") - public void back() { - cdPlayer.back(); + @Bean + public Command forward() { + return new BasicCommand("forward", "Press player forward button") { + @Override + public String execute(String[] args) { + cdPlayer.forward(); + return ""; + } + }; + } + + @Bean + public Command back() { + return new BasicCommand("back", "Press player back button") { + @Override + public String execute(String[] args) { + cdPlayer.back(); + return ""; + } + }; } } diff --git a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/StateMachineCommands.java b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/StateMachineCommands.java index 2125ea51..2748781f 100644 --- a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/StateMachineCommands.java +++ b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/StateMachineCommands.java @@ -15,24 +15,32 @@ */ package demo.cdplayer; +import demo.BasicCommand; +import demo.Command; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.shell.command.annotation.Command; -import org.springframework.shell.command.annotation.Option; import demo.AbstractStateMachineCommands; import demo.cdplayer.Application.Events; import demo.cdplayer.Application.States; import reactor.core.publisher.Mono; -@Command +@Configuration public class StateMachineCommands extends AbstractStateMachineCommands { - @Command(command = "sm event", description = "Sends an event to a state machine") - public String event(@Option(longNames = { "", "event" }, required = true, description = "The event") final Events event) { - getStateMachine() - .sendEvent(Mono.just(MessageBuilder - .withPayload(event).build())) - .subscribe(); - return "Event " + event + " send"; + @Bean + public Command event() { + return new BasicCommand("sm event", "Sends an event to a state machine") { + @Override + public String execute(String[] args) { + Events event = Events.valueOf(args[0]); + getStateMachine() + .sendEvent(Mono.just(MessageBuilder + .withPayload(event).build())) + .subscribe(); + return "Event " + event + " sent"; + } + }; } } diff --git a/spring-statemachine-samples/cdplayer/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/spring-statemachine-samples/cdplayer/src/main/resources/META-INF/spring/spring-shell-plugin.xml deleted file mode 100644 index 1112bb35..00000000 --- a/spring-statemachine-samples/cdplayer/src/main/resources/META-INF/spring/spring-shell-plugin.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/spring-statemachine-samples/cdplayer/src/main/resources/application.properties b/spring-statemachine-samples/cdplayer/src/main/resources/application.properties new file mode 100644 index 00000000..70957423 --- /dev/null +++ b/spring-statemachine-samples/cdplayer/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.main.allow-bean-definition-overriding=true \ No newline at end of file diff --git a/spring-statemachine-samples/cdplayer/src/test/java/demo/cdplayer/CdPlayerTests.java b/spring-statemachine-samples/cdplayer/src/test/java/demo/cdplayer/CdPlayerTests.java deleted file mode 100644 index 15867ca6..00000000 --- a/spring-statemachine-samples/cdplayer/src/test/java/demo/cdplayer/CdPlayerTests.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package demo.cdplayer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.statemachine.TestUtils.doStartAndAssert; -import static org.springframework.statemachine.TestUtils.doStopAndAssert; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.statemachine.ObjectStateMachine; -import org.springframework.statemachine.StateContext; -import org.springframework.statemachine.StateContext.Stage; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.StateMachineSystemConstants; -import org.springframework.statemachine.listener.StateMachineListener; -import org.springframework.statemachine.listener.StateMachineListenerAdapter; -import org.springframework.statemachine.state.State; -import org.springframework.statemachine.transition.TransitionKind; - -import demo.CommonConfiguration; -import demo.cdplayer.Application.Events; -import demo.cdplayer.Application.States; - -public class CdPlayerTests { - - private AnnotationConfigApplicationContext context; - - private StateMachine machine; - - private CdPlayer player; - - private Library library; - - private TestListener listener; - - @Test - public void testInitialState() throws InterruptedException { - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(2); - assertThat(machine.getState().getIds()).containsExactly(States.IDLE, States.CLOSED); - assertLcdStatusStartsWith("No CD"); - } - - @Test - public void testEjectTwice() throws Exception { - listener.reset(1, 0, 0); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - assertThat(machine.getState().getIds()).containsExactly(States.IDLE, States.OPEN); - listener.reset(1, 0, 0); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - assertThat(machine.getState().getIds()).containsExactly(States.IDLE, States.CLOSED); - } - - @Test - public void testPlayWithCdLoaded() throws Exception { - listener.reset(1, 0, 0); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - - listener.reset(1, 0, 0); - player.load(library.getCollection().get(0)); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - - listener.reset(2, 0, 0); - player.play(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(2); - assertThat(machine.getState().getIds()).containsExactly(States.BUSY, States.PLAYING); - assertLcdStatusContains("cd1"); - } - - @Test - public void testPlayWithCdLoadedDeckOpen() throws Exception { - listener.reset(1, 0, 0); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - - listener.reset(3, 0, 0); - player.load(library.getCollection().get(0)); - player.play(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(3); - assertThat(machine.getState().getIds()).containsExactly(States.BUSY, States.PLAYING); - assertLcdStatusContains("cd1"); - } - - @Test - public void testPlayWithNoCdLoaded() throws Exception { - listener.reset(0, 0, 0); - player.play(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isZero(); - assertThat(machine.getState().getIds()).containsExactly(States.IDLE, States.CLOSED); - assertLcdStatusStartsWith("No CD"); - } - - @Test - public void testPlayLcdTimeChanges() throws Exception { - listener.reset(1, 0, 0); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - - listener.reset(1, 0, 0); - player.load(library.getCollection().get(0)); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - - listener.reset(2, 0, 0); - player.play(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(2); - assertThat(machine.getState().getIds()).containsExactly(States.BUSY, States.PLAYING); - assertLcdStatusContains("cd1"); - - listener.reset(0, 0, 0, 0, 1); - assertThat(listener.transitionTimerLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.transitionTimerCount).isEqualTo(1); - assertLcdStatusContains("00:01"); - - listener.reset(0, 0, 0, 0, 1); - assertThat(listener.transitionTimerLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertLcdStatusContains("00:02"); - assertThat(listener.transitionTimerCount).isEqualTo(1); - - listener.reset(0, 0, 0, 0, 2); - assertThat(listener.transitionTimerLatch.await(4, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.transitionTimerCount).isEqualTo(2); - // ok we have some timing problems with - // this test, so for now just check it's - // not previous - assertLcdStatusNotContains("00:02"); - } - - @Test - public void testPlayPause() throws Exception { - listener.reset(1, 0, 0); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - - listener.reset(1, 0, 0); - player.load(library.getCollection().get(0)); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - - listener.reset(2, 0, 0, 0, 1); - player.play(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(2); - assertThat(listener.transitionTimerLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.transitionTimerCount).isEqualTo(1); - assertThat(machine.getState().getIds()).containsExactly(States.BUSY, States.PLAYING); - assertLcdStatusContains("cd1"); - assertLcdStatusContains("00:01"); - - listener.reset(0, 0, 0, 1, 1); - assertThat(listener.transitionTimerLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertLcdStatusContains("00:02"); - assertThat(listener.transitionTimerCount).isEqualTo(1); - - listener.reset(1, 0, 0, 0); - player.pause(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - assertLcdStatusContains("00:02"); - - listener.reset(1, 0, 0, 1); - player.pause(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - assertThat(listener.transitionLatch.await(2, TimeUnit.SECONDS)).isTrue(); - - listener.reset(0, 0, 0, 2, 2); - assertThat(listener.transitionTimerLatch.await(2100, TimeUnit.MILLISECONDS)).isTrue(); - assertThat(listener.transitionTimerCount).isEqualTo(2); - assertLcdStatusNotContains("00:02"); - } - - @Test - public void testPlayStop() throws Exception { - listener.reset(1, 0, 0); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - - listener.reset(1, 0, 0); - player.load(library.getCollection().get(0)); - player.eject(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - - listener.reset(2, 0, 0); - player.play(); - - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(2); - assertThat(machine.getState().getIds()).containsExactly(States.BUSY, States.PLAYING); - - listener.reset(2, 0, 0); - player.stop(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(2); - assertLcdStatusIs("cd1 "); - } - - @Test - public void testPlayDeckOpenNoCd() throws Exception { - listener.reset(2, 0, 0); - player.eject(); - player.play(); - assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(2); - assertThat(machine.getState().getIds()).containsExactly(States.IDLE, States.CLOSED); - } - - private void assertLcdStatusIs(String text) { - assertThat(player.getLdcStatus()).isEqualTo(text); - } - - private void assertLcdStatusStartsWith(String text) { - assertThat(player.getLdcStatus()).startsWith(text); - } - - private void assertLcdStatusContains(String text) { - assertThat(player.getLdcStatus()).contains(text); - } - - private void assertLcdStatusNotContains(String text) { - assertThat(player.getLdcStatus()).doesNotContain(text); - } - - @SuppressWarnings("unchecked") - @BeforeEach - public void setup() throws Exception { - context = new AnnotationConfigApplicationContext(); - context.register(CommonConfiguration.class, Application.class, TestConfig.class); - context.refresh(); - machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); - player = context.getBean(CdPlayer.class); - library = context.getBean(Library.class); - listener = context.getBean(TestListener.class); - doStartAndAssert(machine); - assertThat(listener.stateMachineStartedLatch.await(2, TimeUnit.SECONDS)).isTrue(); - } - - @AfterEach - public void clean() { - doStopAndAssert(machine); - context.close(); - context = null; - machine = null; - player = null; - library = null; - listener = null; - } - - static class TestConfig { - - @Autowired - private StateMachine machine; - - @Bean - public StateMachineListener stateMachineListener() { - TestListener listener = new TestListener(); - machine.addStateListener(listener); - return listener; - } - - @Bean - public Library library() { - // override library to make it easier to test - Track cd1track1 = new Track("cd1track1", 30); - Track cd1track2 = new Track("cd1track2", 30); - Cd cd1 = new Cd("cd1", new Track[]{cd1track1,cd1track2}); - Track cd2track1 = new Track("cd2track1", 30); - Track cd2track2 = new Track("cd2track2", 30); - Cd cd2 = new Cd("cd2", new Track[]{cd2track1,cd2track2}); - return new Library(new Cd[]{cd1,cd2}); - } - - } - - static class TestListener extends StateMachineListenerAdapter { - - volatile CountDownLatch stateMachineStartedLatch = new CountDownLatch(1); - volatile CountDownLatch stateChangedLatch = new CountDownLatch(1); - volatile CountDownLatch stateEnteredLatch = new CountDownLatch(2); - volatile CountDownLatch stateExitedLatch = new CountDownLatch(0); - volatile CountDownLatch transitionLatch = new CountDownLatch(0); - volatile CountDownLatch transitionTimerLatch = new CountDownLatch(0); - volatile int stateChangedCount = 0; - volatile int transitionCount = 0; - volatile int transitionTimerCount = 0; - List> statesEntered = new ArrayList>(); - List> statesExited = new ArrayList>(); - - @Override - public void stateMachineStarted(StateMachine stateMachine) { - stateMachineStartedLatch.countDown(); - } - - @Override - public void stateChanged(State from, State to) { - stateChangedCount++; - stateChangedLatch.countDown(); - } - - @Override - public void stateEntered(State state) { - statesEntered.add(state); - stateEnteredLatch.countDown(); - } - - @Override - public void stateExited(State state) { - statesExited.add(state); - stateExitedLatch.countDown(); - } - - @Override - public void stateContext(StateContext stateContext) { - if (stateContext.getStage() == Stage.TRANSITION_END) { - if (stateContext.getTransition().getKind() == TransitionKind.INTERNAL - && stateContext.getEvent() == null) { - transitionTimerCount++; - transitionTimerLatch.countDown(); - } else { - transitionCount++; - transitionLatch.countDown(); - } - } - } - - public void reset(int c1, int c2, int c3) { - reset(c1, c2, c3, 0); - } - - public void reset(int c1, int c2, int c3, int c4) { - reset(c1, c2, c3, c4, 0); - } - - public void reset(int c1, int c2, int c3, int c4, int c5) { - stateChangedLatch = new CountDownLatch(c1); - stateEnteredLatch = new CountDownLatch(c2); - stateExitedLatch = new CountDownLatch(c3); - transitionLatch = new CountDownLatch(c4); - transitionTimerLatch = new CountDownLatch(c5); - stateChangedCount = 0; - transitionCount = 0; - transitionTimerCount = 0; - statesEntered.clear(); - statesExited.clear(); - } - } -} diff --git a/spring-statemachine-samples/cdplayer/src/test/resources/log4j2.xml b/spring-statemachine-samples/cdplayer/src/test/resources/log4j2.xml deleted file mode 100644 index 9c440f57..00000000 --- a/spring-statemachine-samples/cdplayer/src/test/resources/log4j2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/spring-statemachine-samples/persist/spring-statemachine-samples-persist.gradle b/spring-statemachine-samples/persist/spring-statemachine-samples-persist.gradle index ba723ca0..2afc5e8a 100644 --- a/spring-statemachine-samples/persist/spring-statemachine-samples-persist.gradle +++ b/spring-statemachine-samples/persist/spring-statemachine-samples-persist.gradle @@ -9,7 +9,6 @@ dependencies { implementation project(':spring-statemachine-samples-common') implementation project(':spring-statemachine-recipes-common') implementation 'org.springframework.boot:spring-boot-starter' - implementation 'org.springframework.shell:spring-shell-core' implementation ('org.hsqldb:hsqldb') implementation ('org.springframework:spring-jdbc') testImplementation(testFixtures(project(':spring-statemachine-core'))) diff --git a/spring-statemachine-samples/persist/src/main/java/demo/persist/Application.java b/spring-statemachine-samples/persist/src/main/java/demo/persist/Application.java index fb7aa00c..551c36be 100644 --- a/spring-statemachine-samples/persist/src/main/java/demo/persist/Application.java +++ b/spring-statemachine-samples/persist/src/main/java/demo/persist/Application.java @@ -27,7 +27,7 @@ import org.springframework.statemachine.config.builders.StateMachineStateConfigu import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.recipes.persist.PersistStateMachineHandler; -@SpringBootApplication +@SpringBootApplication(scanBasePackages = "demo") public class Application { //tag::snippetA[] diff --git a/spring-statemachine-samples/persist/src/main/java/demo/persist/PersistCommands.java b/spring-statemachine-samples/persist/src/main/java/demo/persist/PersistCommands.java index 6f8eeac4..00dcfdda 100644 --- a/spring-statemachine-samples/persist/src/main/java/demo/persist/PersistCommands.java +++ b/spring-statemachine-samples/persist/src/main/java/demo/persist/PersistCommands.java @@ -15,34 +15,62 @@ */ package demo.persist; +import demo.BasicCommand; +import demo.Command; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.shell.command.annotation.Command; -import org.springframework.shell.command.annotation.Option; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; -@Command +@Configuration public class PersistCommands { @Autowired private Persist persist; - @Command(command = "persist db", description = "List entries from db") - public String listDbEntries() { - return persist.listDbEntries(); + @Bean + public Command list() { + return new BasicCommand("list", "List entries from db") { + @Override + public String execute(String[] args) { + return persist.listDbEntries(); + } + }; } - @Command(command = "persist process", description = "Process order") - public void process(@Option(longNames = {"", "id"}, description = "Order id") int order) { - persist.change(order, "PROCESS"); + @Bean + public Command process() { + return new BasicCommand("process [orderId]", "Process order with [orderId]") { + @Override + public String execute(String[] args) { + int order = Integer.parseInt(args[0]); + persist.change(order, "PROCESS"); + return "Order " + order + " processed"; + } + }; } - @Command(command = "persist send", description = "Send order") - public void send(@Option(longNames = {"", "id"}, description = "Order id") int order) { - persist.change(order, "SEND"); + @Bean + public Command send() { + return new BasicCommand("send [orderId]", "Send order with [orderId]") { + @Override + public String execute(String[] args) { + int order = Integer.parseInt(args[0]); + persist.change(order, "SEND"); + return "Order " + order + " sent"; + } + }; } - @Command(command = "persist deliver", description = "Deliver order") - public void deliver(@Option(longNames = {"", "id"}, description = "Order id") int order) { - persist.change(order, "DELIVER"); + @Bean + public Command deliver() { + return new BasicCommand("deliver [orderId]", "Deliver order with [orderId]") { + @Override + public String execute(String[] args) { + int order = Integer.parseInt(args[0]); + persist.change(order, "DELIVER"); + return "Order " + order + " delivered"; + } + }; } } diff --git a/spring-statemachine-samples/persist/src/main/java/demo/persist/StateMachineCommands.java b/spring-statemachine-samples/persist/src/main/java/demo/persist/StateMachineCommands.java index 2991ba34..4ded9e89 100644 --- a/spring-statemachine-samples/persist/src/main/java/demo/persist/StateMachineCommands.java +++ b/spring-statemachine-samples/persist/src/main/java/demo/persist/StateMachineCommands.java @@ -15,22 +15,30 @@ */ package demo.persist; +import demo.BasicCommand; +import demo.Command; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.shell.command.annotation.Command; -import org.springframework.shell.command.annotation.Option; import demo.AbstractStateMachineCommands; import reactor.core.publisher.Mono; -@Command +@Configuration public class StateMachineCommands extends AbstractStateMachineCommands { - @Command(command = "sm event", description = "Sends an event to a state machine") - public String event(@Option(longNames = { "", "event" }, required = true, description = "The event") final String event) { - getStateMachine() - .sendEvent(Mono.just(MessageBuilder - .withPayload(event).build())) - .subscribe(); - return "Event " + event + " send"; + @Bean + public Command event() { + return new BasicCommand("event", "Sends an event to a state machine") { + @Override + public String execute(String[] args) { + String event = args[0]; + getStateMachine() + .sendEvent(Mono.just(MessageBuilder + .withPayload(event).build())) + .subscribe(); + return "Event " + event + " sent"; + } + }; } } diff --git a/spring-statemachine-samples/persist/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/spring-statemachine-samples/persist/src/main/resources/META-INF/spring/spring-shell-plugin.xml deleted file mode 100644 index 1112bb35..00000000 --- a/spring-statemachine-samples/persist/src/main/resources/META-INF/spring/spring-shell-plugin.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/spring-statemachine-samples/persist/src/main/resources/application.properties b/spring-statemachine-samples/persist/src/main/resources/application.properties new file mode 100644 index 00000000..70957423 --- /dev/null +++ b/spring-statemachine-samples/persist/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.main.allow-bean-definition-overriding=true \ No newline at end of file diff --git a/spring-statemachine-samples/persist/src/test/java/demo/persist/PersistTests.java b/spring-statemachine-samples/persist/src/test/java/demo/persist/PersistTests.java deleted file mode 100644 index bcb3ccf4..00000000 --- a/spring-statemachine-samples/persist/src/test/java/demo/persist/PersistTests.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package demo.persist; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.statemachine.TestUtils.doStartAndAssert; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.listener.StateMachineListenerAdapter; -import org.springframework.statemachine.state.State; -import org.springframework.statemachine.transition.Transition; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import demo.CommonConfiguration; - -@ExtendWith(SpringExtension.class) -@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) -@SpringBootTest(classes = { CommonConfiguration.class, Application.class, StateMachineCommands.class }) -public class PersistTests { - - @Autowired - private StateMachineCommands commands; - - @Autowired - private StateMachine machine; - - @Autowired - private Persist persist; - - @Test - public void testNotStarted() throws Exception { - assertThat(commands.state()).isEqualTo("No state"); - } - - @Test - public void testInitialState() throws Exception { - TestListener listener = new TestListener(); - machine.addStateListener(listener); - doStartAndAssert(machine); - assertThat(listener.stateChangedLatch.await(3, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateEnteredLatch.await(3, TimeUnit.SECONDS)).isTrue(); - assertThat(machine.getState().getIds()).containsExactly("PLACED"); - assertThat(listener.statesEntered).hasSize(1); - assertThat(listener.statesEntered.get(0).getId()).isEqualTo("PLACED"); - assertThat(listener.statesExited).isEmpty(); - } - - @Test - public void testInitialDbList() { - // dataOrder [id=1, state=PLACED]Order [id=2, state=PROCESSING]Order [id=3, state=SENT]Order [id=4, state=DELIVERED] - assertThat(persist.listDbEntries()).contains("PLACED"); - } - - @Test - public void testUpdate1() { - persist.change(1, "PROCESS"); - assertThat(persist.listDbEntries()).contains("id=1, state=PROCESSING"); - } - - @Test - public void testUpdate2() { - persist.change(2, "SEND"); - assertThat(persist.listDbEntries()).contains("id=2, state=SENT"); - } - - private static class TestListener extends StateMachineListenerAdapter { - - volatile CountDownLatch stateChangedLatch = new CountDownLatch(1); - volatile CountDownLatch stateEnteredLatch = new CountDownLatch(1); - volatile CountDownLatch stateExitedLatch = new CountDownLatch(0); - volatile CountDownLatch transitionLatch = new CountDownLatch(0); - volatile List> transitions = new ArrayList>(); - List> statesEntered = new ArrayList>(); - List> statesExited = new ArrayList>(); - - @Override - public void stateChanged(State from, State to) { - stateChangedLatch.countDown(); - } - - @Override - public void stateEntered(State state) { - statesEntered.add(state); - stateEnteredLatch.countDown(); - } - - @Override - public void stateExited(State state) { - statesExited.add(state); - stateExitedLatch.countDown(); - } - - @Override - public void transition(Transition transition) { - transitions.add(transition); - transitionLatch.countDown(); - } - - } - -} diff --git a/spring-statemachine-samples/showcase/spring-statemachine-samples-showcase.gradle b/spring-statemachine-samples/showcase/spring-statemachine-samples-showcase.gradle index 523e6ba6..0eb46c77 100644 --- a/spring-statemachine-samples/showcase/spring-statemachine-samples-showcase.gradle +++ b/spring-statemachine-samples/showcase/spring-statemachine-samples-showcase.gradle @@ -8,7 +8,7 @@ dependencies { management platform(project(":spring-statemachine-platform")) implementation project(':spring-statemachine-samples-common') implementation project(':spring-statemachine-core') - implementation 'org.springframework.shell:spring-shell-starter' + implementation 'org.springframework.boot:spring-boot-starter' testImplementation(testFixtures(project(':spring-statemachine-core'))) testImplementation (project(':spring-statemachine-test')) testImplementation 'org.hamcrest:hamcrest-core' diff --git a/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java b/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java index b1d8fcd4..8f6d4516 100644 --- a/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java +++ b/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java @@ -23,7 +23,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.shell.command.annotation.CommandScan; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.EnableStateMachine; @@ -32,7 +31,6 @@ import org.springframework.statemachine.config.builders.StateMachineStateConfigu import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.guard.Guard; -@CommandScan @SpringBootApplication(scanBasePackages = "demo") public class Application { diff --git a/spring-statemachine-samples/showcase/src/main/java/demo/showcase/StateMachineCommands.java b/spring-statemachine-samples/showcase/src/main/java/demo/showcase/StateMachineCommands.java index 9b5761ed..2f91c46c 100644 --- a/spring-statemachine-samples/showcase/src/main/java/demo/showcase/StateMachineCommands.java +++ b/spring-statemachine-samples/showcase/src/main/java/demo/showcase/StateMachineCommands.java @@ -15,24 +15,32 @@ */ package demo.showcase; +import demo.BasicCommand; +import demo.Command; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.shell.command.annotation.Command; -import org.springframework.shell.command.annotation.Option; import demo.AbstractStateMachineCommands; import demo.showcase.Application.Events; import demo.showcase.Application.States; import reactor.core.publisher.Mono; -@Command +@Configuration public class StateMachineCommands extends AbstractStateMachineCommands { - @Command(command = "sm event", description = "Sends an event to a state machine") - public String event(@Option(longNames = { "", "event" }, required = true, description = "The event") final Events event) { - getStateMachine() - .sendEvent(Mono.just(MessageBuilder - .withPayload(event).build())) - .subscribe(); - return "Event " + event + " send"; + @Bean + public Command event() { + return new BasicCommand("event", "Sends an event to a state machine") { + @Override + public String execute(String[] args) { + Events event = Events.valueOf(args[0]); + getStateMachine() + .sendEvent(Mono.just(MessageBuilder + .withPayload(event).build())) + .subscribe(); + return "Event " + event + " sent"; + } + }; } } diff --git a/spring-statemachine-samples/showcase/src/main/resources/application.properties b/spring-statemachine-samples/showcase/src/main/resources/application.properties index 810a40c4..70957423 100644 --- a/spring-statemachine-samples/showcase/src/main/resources/application.properties +++ b/spring-statemachine-samples/showcase/src/main/resources/application.properties @@ -1 +1 @@ -spring.shell.interactive.enabled=true +spring.main.allow-bean-definition-overriding=true \ No newline at end of file diff --git a/spring-statemachine-samples/showcase/src/test/java/demo/showcase/ShowcaseTests.java b/spring-statemachine-samples/showcase/src/test/java/demo/showcase/ShowcaseTests.java deleted file mode 100644 index 71a659ef..00000000 --- a/spring-statemachine-samples/showcase/src/test/java/demo/showcase/ShowcaseTests.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package demo.showcase; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.statemachine.TestUtils.doSendEventAndConsumeAll; -import static org.springframework.statemachine.TestUtils.doStartAndAssert; -import static org.springframework.statemachine.TestUtils.doStopAndAssert; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.statemachine.ObjectStateMachine; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.StateMachineSystemConstants; -import org.springframework.statemachine.listener.StateMachineListener; -import org.springframework.statemachine.listener.StateMachineListenerAdapter; -import org.springframework.statemachine.state.State; -import org.springframework.statemachine.transition.Transition; - -import demo.CommonConfiguration; -import demo.showcase.Application.Events; -import demo.showcase.Application.States; - -public class ShowcaseTests { - - private AnnotationConfigApplicationContext context; - - private StateMachine machine; - - private TestListener listener; - - @Test - public void testInitialState() throws Exception { - assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateEnteredLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S1, States.S11); - assertThat(listener.statesEntered).hasSize(3); - assertThat(listener.statesEntered.get(0).getId()).isEqualTo(States.S0); - assertThat(listener.statesEntered.get(1).getId()).isEqualTo(States.S1); - assertThat(listener.statesEntered.get(2).getId()).isEqualTo(States.S11); - assertThat(listener.statesExited).isEmpty(); - } - - @Test - public void testA() throws Exception { - testInitialState(); - listener.reset(1, 2, 2); - doSendEventAndConsumeAll(machine, Events.A); - // machine.sendEvent(Events.A); - // variable foo is 0, guard denies transition - assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS)).isFalse(); - assertThat(listener.stateEnteredLatch.await(1, TimeUnit.SECONDS)).isFalse(); - assertThat(listener.stateExitedLatch.await(1, TimeUnit.SECONDS)).isFalse(); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S1, States.S11); - } - - @Test - public void testB() throws Exception { - testInitialState(); - listener.reset(1, 2, 2); - doSendEventAndConsumeAll(machine, Events.B); - assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateEnteredLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateExitedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.statesExited).hasSize(2); - assertThat(listener.statesEntered).hasSize(2); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S1, States.S11); - } - - @Test - public void testCHCA() throws Exception { - testInitialState(); - listener.reset(3, 0, 0); - doSendEventAndConsumeAll(machine, Events.C); - doSendEventAndConsumeAll(machine, Events.H); - doSendEventAndConsumeAll(machine, Events.C); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - - listener.reset(1, 2, 2, 1); - doSendEventAndConsumeAll(machine, Events.A); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - listener.stateEnteredLatch.await(1, TimeUnit.SECONDS); - listener.stateExitedLatch.await(1, TimeUnit.SECONDS); - listener.transitionLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S1, States.S11); - assertThat(listener.statesEntered).hasSize(2); - assertThat(listener.statesEntered.get(0).getId()).isEqualTo(States.S1); - assertThat(listener.statesEntered.get(1).getId()).isEqualTo(States.S11); - assertThat(listener.statesExited).hasSize(2); - assertThat(listener.statesExited.get(0).getId()).isEqualTo(States.S11); - assertThat(listener.statesExited.get(1).getId()).isEqualTo(States.S1); - assertThat(listener.transitionCount).isEqualTo(2); - } - - @Test - public void testC() throws Exception { - testInitialState(); - listener.reset(1, 3, 0); - doSendEventAndConsumeAll(machine, Events.C); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - listener.stateEnteredLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S2, States.S21, States.S211); - assertThat(listener.statesEntered).hasSize(3); - assertThat(listener.statesEntered.get(0).getId()).isEqualTo(States.S2); - assertThat(listener.statesEntered.get(1).getId()).isEqualTo(States.S21); - assertThat(listener.statesEntered.get(2).getId()).isEqualTo(States.S211); - } - - @Test - public void testCC() throws Exception { - testInitialState(); - listener.reset(1, 3, 0); - doSendEventAndConsumeAll(machine, Events.C); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S2, States.S21, States.S211); - listener.reset(1, 2, 0); - doSendEventAndConsumeAll(machine, Events.C); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - listener.stateEnteredLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S1, States.S11); - assertThat(listener.statesEntered).hasSize(2); - assertThat(listener.statesEntered.get(0).getId()).isEqualTo(States.S1); - assertThat(listener.statesEntered.get(1).getId()).isEqualTo(States.S11); - } - - @Test - public void testD() throws Exception { - testInitialState(); - listener.reset(3, 3, 0); - doSendEventAndConsumeAll(machine, Events.D); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - listener.stateEnteredLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S1, States.S11); - assertThat(listener.statesEntered).hasSize(3); - assertThat(listener.statesEntered.get(0).getId()).isEqualTo(States.S0); - assertThat(listener.statesEntered.get(1).getId()).isEqualTo(States.S1); - assertThat(listener.statesEntered.get(2).getId()).isEqualTo(States.S11); - assertThat(listener.statesExited).hasSize(3); - } - - @Test - public void testCD() throws Exception { - testInitialState(); - listener.reset(1, 3, 0); - doSendEventAndConsumeAll(machine, Events.C); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - listener.reset(1, 2, 0); - doSendEventAndConsumeAll(machine, Events.D); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - listener.stateEnteredLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S2, States.S21, States.S211); - assertThat(listener.statesEntered).hasSize(2); - assertThat(listener.statesEntered.get(0).getId()).isEqualTo(States.S21); - assertThat(listener.statesEntered.get(1).getId()).isEqualTo(States.S211); - } - - @Test - public void testI() throws Exception { - testInitialState(); - listener.reset(1, 1, 1); - doSendEventAndConsumeAll(machine, Events.I); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - listener.stateEnteredLatch.await(1, TimeUnit.SECONDS); - listener.stateExitedLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S1, States.S12); - assertThat(listener.statesEntered).hasSize(1); - assertThat(listener.statesEntered.get(0).getId()).isEqualTo(States.S12); - assertThat(listener.statesExited).hasSize(1); - assertThat(listener.statesExited.get(0).getId()).isEqualTo(States.S11); - } - - @Test - public void testII() throws Exception { - testInitialState(); - listener.reset(1, 1, 1); - doSendEventAndConsumeAll(machine, Events.I); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateEnteredLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateExitedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.statesEntered).hasSize(1); - assertThat(listener.statesExited).hasSize(1); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S1, States.S12); - - listener.reset(1, 3, 2); - doSendEventAndConsumeAll(machine, Events.I); - assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateEnteredLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateExitedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.statesEntered).hasSize(3); - assertThat(listener.statesExited).hasSize(2); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S2, States.S21, States.S212); - } - - @Test - public void testH() throws Exception { - testInitialState(); - listener.reset(0, 0, 0, 1); - doSendEventAndConsumeAll(machine, Events.H); - listener.transitionLatch.await(1, TimeUnit.SECONDS); - assertThat(listener.transitionCount).isEqualTo(1); - assertThat(listener.transitions.get(0).getSource().getId()).isEqualTo(States.S1); - } - - @Test - public void testCH() throws Exception { - testInitialState(); - doSendEventAndConsumeAll(machine, Events.C); - listener.reset(0, 0, 0, 1); - doSendEventAndConsumeAll(machine, Events.H); - listener.transitionLatch.await(1, TimeUnit.SECONDS); - assertThat(listener.transitionCount).isEqualTo(1); - assertThat(listener.transitions.get(0).getSource().getId()).isEqualTo(States.S0); - } - - @Test - public void testACH() throws Exception { - testInitialState(); - doSendEventAndConsumeAll(machine, Events.A); - doSendEventAndConsumeAll(machine, Events.C); - listener.reset(0, 0, 0, 1); - doSendEventAndConsumeAll(machine, Events.H); - doSendEventAndConsumeAll(machine, Events.A); - listener.transitionLatch.await(1, TimeUnit.SECONDS); - assertThat(listener.transitionCount).isEqualTo(1); - assertThat(listener.transitions.get(0).getSource().getId()).isEqualTo(States.S0); - } - - @Test - public void testE() throws Exception { - testInitialState(); - listener.reset(1, 4, 3, 0); - doSendEventAndConsumeAll(machine, Events.E); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateEnteredLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateExitedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S2, States.S21, States.S211); - assertThat(listener.statesExited).hasSize(3); - assertThat(listener.statesEntered).hasSize(4); - } - - @Test - public void testF() throws Exception { - testInitialState(); - listener.reset(1, 3, 2, 0); - doSendEventAndConsumeAll(machine, Events.F); - assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateEnteredLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateExitedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S2, States.S21, States.S211); - assertThat(listener.statesExited).hasSize(2); - assertThat(listener.statesEntered).hasSize(3); - } - - @Test - public void testG() throws Exception { - testInitialState(); - listener.reset(1, 3, 2, 0); - doSendEventAndConsumeAll(machine, Events.G); - assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateEnteredLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateExitedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(machine.getState().getIds()).containsExactly(States.S0, States.S2, States.S21, States.S211); - assertThat(listener.statesExited).hasSize(2); - assertThat(listener.statesEntered).hasSize(3); - } - - static class Config { - - @Autowired - private StateMachine machine; - - @Bean - public StateMachineListener stateMachineListener() { - TestListener listener = new TestListener(); - machine.addStateListener(listener); - return listener; - } - } - - static class TestListener extends StateMachineListenerAdapter { - - volatile CountDownLatch stateChangedLatch = new CountDownLatch(1); - volatile CountDownLatch stateEnteredLatch = new CountDownLatch(3); - volatile CountDownLatch stateExitedLatch = new CountDownLatch(0); - volatile CountDownLatch transitionLatch = new CountDownLatch(0); - volatile List> transitions = new ArrayList>(); - List> statesEntered = new ArrayList>(); - List> statesExited = new ArrayList>(); - volatile int transitionCount = 0; - - @Override - public void stateChanged(State from, State to) { - stateChangedLatch.countDown(); - } - - @Override - public void stateEntered(State state) { - statesEntered.add(state); - stateEnteredLatch.countDown(); - } - - @Override - public void stateExited(State state) { - statesExited.add(state); - stateExitedLatch.countDown(); - } - - @Override - public void transition(Transition transition) { - transitions.add(transition); - transitionLatch.countDown(); - transitionCount++; - } - - public void reset(int c1, int c2, int c3) { - reset(c1, c2, c3, 0); - } - - public void reset(int c1, int c2, int c3, int c4) { - stateChangedLatch = new CountDownLatch(c1); - stateEnteredLatch = new CountDownLatch(c2); - stateExitedLatch = new CountDownLatch(c3); - transitionLatch = new CountDownLatch(c4); - statesEntered.clear(); - statesExited.clear(); - transitionCount = 0; - transitions.clear(); - } - - } - - @SuppressWarnings("unchecked") - @BeforeEach - public void setup() { - context = new AnnotationConfigApplicationContext(); - context.register(CommonConfiguration.class, Application.class, Config.class); - context.refresh(); - machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); - listener = context.getBean(TestListener.class); - doStartAndAssert(machine); - } - - @AfterEach - public void clean() { - doStopAndAssert(machine); - context.close(); - context = null; - machine = null; - } - -} diff --git a/spring-statemachine-samples/spring-statemachine-samples.gradle b/spring-statemachine-samples/spring-statemachine-samples.gradle index d955870e..02305597 100644 --- a/spring-statemachine-samples/spring-statemachine-samples.gradle +++ b/spring-statemachine-samples/spring-statemachine-samples.gradle @@ -7,7 +7,6 @@ description = 'Spring State Machine Samples Common' dependencies { management platform(project(":spring-statemachine-platform")) implementation project(':spring-statemachine-core') - implementation 'org.springframework.shell:spring-shell-core' implementation 'org.springframework.boot:spring-boot-starter' } diff --git a/spring-statemachine-samples/src/main/java/demo/AbstractStateMachineCommands.java b/spring-statemachine-samples/src/main/java/demo/AbstractStateMachineCommands.java index f2ea1580..a18cbe1b 100644 --- a/spring-statemachine-samples/src/main/java/demo/AbstractStateMachineCommands.java +++ b/spring-statemachine-samples/src/main/java/demo/AbstractStateMachineCommands.java @@ -15,18 +15,21 @@ */ package demo; -import java.util.Iterator; -import java.util.Map.Entry; -import java.util.Set; - import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.shell.command.annotation.Command; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.state.State; import org.springframework.util.StringUtils; -@Command +import java.io.InputStream; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Scanner; +import java.util.Set; + +@Configuration public class AbstractStateMachineCommands { @Autowired @@ -36,54 +39,74 @@ public class AbstractStateMachineCommands { return stateMachine; } - @Autowired - @Qualifier("stateChartModel") - private String stateChartModel; - - @Command(command = "sm state", description = "Prints current state") - public String state() { - State state = stateMachine.getState(); - if (state != null) { - return StringUtils.collectionToCommaDelimitedString(state.getIds()); - } else { - return "No state"; - } - } - - @Command(command = "sm start", description = "Start a state machine") - public String start() { - stateMachine.startReactively().subscribe(); - return "State machine started"; - } - - @Command(command = "sm stop", description = "Stop a state machine") - public String stop() { - stateMachine.stopReactively().subscribe(); - return "State machine stopped"; - } - - @Command(command = "sm print", description = "Print state machine") - public String print() { - return stateChartModel; - } - - @Command(command = "sm variables", description = "Prints extended state variables") - public String variables() { - StringBuilder buf = new StringBuilder(); - Set> entrySet = stateMachine.getExtendedState().getVariables().entrySet(); - Iterator> iterator = entrySet.iterator(); - if (entrySet.size() > 0) { - while (iterator.hasNext()) { - Entry e = iterator.next(); - buf.append(e.getKey() + "=" + e.getValue()); - if (iterator.hasNext()) { - buf.append("\n"); + @Bean + public Command state() { + return new BasicCommand("state", "Prints current state") { + public String execute(String[] args) { + State state = stateMachine.getState(); + if (state != null) { + return StringUtils.collectionToCommaDelimitedString(state.getIds()); + } else { + return "No state"; } } - } else { - buf.append("No variables"); - } - return buf.toString(); + }; } -} \ No newline at end of file + @Bean + public Command start() { + return new BasicCommand("start", "Start a state machine") { + public String execute(String[] args) { + stateMachine.startReactively().subscribe(); + return "State machine started"; + } + }; + } + + @Bean + public Command stop() { + return new BasicCommand("stop", "Stop a state machine") { + public String execute(String[] args) { + stateMachine.stopReactively().subscribe(); + return "State machine stopped"; + } + }; + } + + @Bean + public Command print() { + return new BasicCommand("print", "Print state machine") { + public String execute(String[] args) throws Exception { + ClassPathResource model = new ClassPathResource("statechartmodel.txt"); + InputStream inputStream = model.getInputStream(); + Scanner scanner = new Scanner(inputStream); + String content = scanner.useDelimiter("\\Z").next(); + scanner.close(); + return content; + } + }; + } + + @Bean + public Command variables() { + return new BasicCommand("variables", "Prints extended state variables") { + public String execute(String[] args) { + StringBuilder buf = new StringBuilder(); + Set> entrySet = stateMachine.getExtendedState().getVariables().entrySet(); + Iterator> iterator = entrySet.iterator(); + if (entrySet.size() > 0) { + while (iterator.hasNext()) { + Entry e = iterator.next(); + buf.append(e.getKey() + "=" + e.getValue()); + if (iterator.hasNext()) { + buf.append("\n"); + } + } + } else { + buf.append("No variables"); + } + return buf.toString(); + } + }; + } +} diff --git a/spring-statemachine-samples/src/main/java/demo/BasicCommand.java b/spring-statemachine-samples/src/main/java/demo/BasicCommand.java new file mode 100644 index 00000000..4f9be509 --- /dev/null +++ b/spring-statemachine-samples/src/main/java/demo/BasicCommand.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package demo; + +/** + * Basic implementation of the {@link Command} interface. + * This class provides a simple way to create commands with a name and description. + * It can be extended to implement specific command logic. + * + * @author Mahmoud Ben Hassine + */ +public class BasicCommand implements Command { + + private final String name; + private final String description; + + /** + * Create a new {@link BasicCommand} with a name and description. + * + * @param name the command name + * @param description the command description + */ + public BasicCommand(String name, String description) { + this.name = name; + this.description = description; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getDescription() { + return this.description; + } + + @Override + public String execute(String[] args) throws Exception { + throw new UnsupportedOperationException("Not implemented yet"); + } +} diff --git a/spring-statemachine-samples/src/main/java/demo/StateMachinePromptProvider.java b/spring-statemachine-samples/src/main/java/demo/Command.java similarity index 53% rename from spring-statemachine-samples/src/main/java/demo/StateMachinePromptProvider.java rename to spring-statemachine-samples/src/main/java/demo/Command.java index 897e0299..cc0b2585 100644 --- a/spring-statemachine-samples/src/main/java/demo/StateMachinePromptProvider.java +++ b/spring-statemachine-samples/src/main/java/demo/Command.java @@ -15,19 +15,31 @@ */ package demo; -import org.jline.utils.AttributedString; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.shell.jline.PromptProvider; -import org.springframework.stereotype.Component; +/** + * Simple interface for command line commands. + * + * @author Mahmoud Ben Hassine + */ +public interface Command { -@Component -@Order(Ordered.HIGHEST_PRECEDENCE) -public class StateMachinePromptProvider implements PromptProvider { + /** + * The command name. + * @return the command name + */ + String getName(); - @Override - public AttributedString getPrompt() { - return AttributedString.fromAnsi("sm>"); - } + /** + * The command description. + * @return the command description + */ + String getDescription(); + + /** + * Execute the command. + * @param args the command arguments + * @return the message to be printed to the console + * @throws Exception in case of error + */ + String execute(String[] args) throws Exception; } diff --git a/spring-statemachine-samples/src/main/java/demo/CommandRunner.java b/spring-statemachine-samples/src/main/java/demo/CommandRunner.java new file mode 100644 index 00000000..dd6f7dc3 --- /dev/null +++ b/spring-statemachine-samples/src/main/java/demo/CommandRunner.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package demo; + +import org.springframework.beans.BeansException; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; + +import java.io.Console; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Command line runner that executes commands. + *

+ * This class implements the {@link ApplicationRunner} interface and is responsible for + * executing commands in the command line interface. It retrieves all beans of type {@link Command} + * from the application context and allows the user to execute them interactively. + * + * @author Mahmoud Ben Hassine + */ +@Component +public class CommandRunner implements ApplicationRunner, ApplicationContextAware, Ordered { + + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + Map commands = this.applicationContext.getBeansOfType(Command.class); + Console console = System.console(); + if (console == null) { + System.err.println("No console available."); + return; + } + + final String lineSeparator = System.lineSeparator(); + console.printf("Available commands:" + lineSeparator); + for (Command command : commands.values()) { + console.printf(" " + command.getName() + ": " + command.getDescription() + lineSeparator); + } + console.printf(" quit: exit" + lineSeparator); + + String commandString = ""; + while (!commandString.equalsIgnoreCase("quit")) { + console.printf("sm>"); + commandString = console.readLine(); + String[] tokens = commandString.split(" "); + String commandName = tokens[0]; + String[] commandArgs = Stream.of(tokens).skip(1).limit(tokens.length).toArray(String[]::new); + if (commandName.equalsIgnoreCase("quit")) { + continue; + } + try { + Command command = commands.get(commandName); + if (command == null) { + console.printf("Command not found: " + commandString + lineSeparator); + continue; + } + console.printf(command.execute(commandArgs)); + console.printf(lineSeparator); + } catch (Exception exception) { + console.printf("Error while executing command: " + commandString + lineSeparator); + exception.printStackTrace(); + } + } + console.printf("bye!" + lineSeparator); + } + +} diff --git a/spring-statemachine-samples/src/main/java/demo/CommonConfiguration.java b/spring-statemachine-samples/src/main/java/demo/CommonConfiguration.java deleted file mode 100644 index d2b69843..00000000 --- a/spring-statemachine-samples/src/main/java/demo/CommonConfiguration.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package demo; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Scanner; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.statemachine.event.OnStateEntryEvent; -import org.springframework.statemachine.event.OnStateExitEvent; -import org.springframework.statemachine.event.OnTransitionEvent; -import org.springframework.statemachine.event.StateMachineEvent; -import org.springframework.statemachine.transition.TransitionKind; - -@Configuration -public class CommonConfiguration { - - private final static Log log = LogFactory.getLog(CommonConfiguration.class); - - @Configuration - static class ApplicationConfig { - - @Bean - public TestEventListener testEventListener() { - return new TestEventListener(); - } - - @Bean - public String stateChartModel() throws IOException { - ClassPathResource model = new ClassPathResource("statechartmodel.txt"); - InputStream inputStream = model.getInputStream(); - Scanner scanner = new Scanner(inputStream); - String content = scanner.useDelimiter("\\Z").next(); - scanner.close(); - return content; - } - - } - - static class TestEventListener implements ApplicationListener { - - @Override - public void onApplicationEvent(StateMachineEvent event) { - if (event instanceof OnStateEntryEvent) { - OnStateEntryEvent e = (OnStateEntryEvent)event; - log.info("Entry state " + e.getState().getId()); - } else if (event instanceof OnStateExitEvent) { - OnStateExitEvent e = (OnStateExitEvent)event; - log.info("Exit state " + e.getState().getId()); - } else if (event instanceof OnTransitionEvent) { - OnTransitionEvent e = (OnTransitionEvent)event; - if (e.getTransition().getKind() == TransitionKind.INTERNAL) { - log.info("Internal transition source=" + e.getTransition().getSource().getId()); - } - } - } - - } - -} diff --git a/spring-statemachine-samples/src/main/java/demo/StateMachineEventListener.java b/spring-statemachine-samples/src/main/java/demo/StateMachineEventListener.java new file mode 100644 index 00000000..3f819c21 --- /dev/null +++ b/spring-statemachine-samples/src/main/java/demo/StateMachineEventListener.java @@ -0,0 +1,31 @@ +package demo; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.statemachine.event.OnStateEntryEvent; +import org.springframework.statemachine.event.OnStateExitEvent; +import org.springframework.statemachine.event.OnTransitionEvent; +import org.springframework.statemachine.event.StateMachineEvent; +import org.springframework.statemachine.transition.TransitionKind; +import org.springframework.stereotype.Component; + +@Component +class StateMachineEventListener implements ApplicationListener { + + private final static Log log = LogFactory.getLog(StateMachineEventListener.class); + + @Override + public void onApplicationEvent(StateMachineEvent stateMachineEvent) { + if (stateMachineEvent instanceof OnStateEntryEvent event) { + log.info("Entry state " + event.getState().getId()); + } else if (stateMachineEvent instanceof OnStateExitEvent event) { + log.info("Exit state " + event.getState().getId()); + } else if (stateMachineEvent instanceof OnTransitionEvent event) { + if (event.getTransition().getKind() == TransitionKind.INTERNAL) { + log.info("Internal transition source=" + event.getTransition().getSource().getId()); + } + } + } + +} \ No newline at end of file diff --git a/spring-statemachine-samples/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/spring-statemachine-samples/src/main/resources/META-INF/spring/spring-shell-plugin.xml deleted file mode 100644 index 1112bb35..00000000 --- a/spring-statemachine-samples/src/main/resources/META-INF/spring/spring-shell-plugin.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/spring-statemachine-samples/tasks/spring-statemachine-samples-tasks.gradle b/spring-statemachine-samples/tasks/spring-statemachine-samples-tasks.gradle index 37d61a40..cf881a1e 100644 --- a/spring-statemachine-samples/tasks/spring-statemachine-samples-tasks.gradle +++ b/spring-statemachine-samples/tasks/spring-statemachine-samples-tasks.gradle @@ -8,7 +8,7 @@ dependencies { management platform(project(":spring-statemachine-platform")) implementation project(':spring-statemachine-samples-common') implementation project(':spring-statemachine-core') - implementation 'org.springframework.shell:spring-shell-core' + implementation 'org.springframework.boot:spring-boot-starter' testImplementation(testFixtures(project(':spring-statemachine-core'))) testImplementation (project(':spring-statemachine-test')) testImplementation 'org.hamcrest:hamcrest-core' diff --git a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Application.java b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Application.java index ea9dc3d6..080a714f 100644 --- a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Application.java +++ b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Application.java @@ -22,6 +22,7 @@ import java.lang.annotation.Target; import java.util.Map; import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.MessageBuilder; @@ -39,7 +40,7 @@ import org.springframework.util.ObjectUtils; import reactor.core.publisher.Mono; -@Configuration +@SpringBootApplication(scanBasePackages = "demo") public class Application { @Configuration diff --git a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/StateMachineCommands.java b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/StateMachineCommands.java index 90fa20cb..f8056aba 100644 --- a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/StateMachineCommands.java +++ b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/StateMachineCommands.java @@ -15,24 +15,32 @@ */ package demo.tasks; +import demo.BasicCommand; +import demo.Command; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.shell.command.annotation.Command; -import org.springframework.shell.command.annotation.Option; import demo.AbstractStateMachineCommands; import demo.tasks.Application.Events; import demo.tasks.Application.States; import reactor.core.publisher.Mono; -@Command +@Configuration public class StateMachineCommands extends AbstractStateMachineCommands { - @Command(command = "sm event", description = "Sends an event to a state machine") - public String event(@Option(longNames = { "", "event" }, required = true, description = "The event") final Events event) { - getStateMachine() - .sendEvent(Mono.just(MessageBuilder - .withPayload(event).build())) - .subscribe(); - return "Event " + event + " send"; + @Bean + public Command event() { + return new BasicCommand("event", "Sends an event to a state machine") { + @Override + public String execute(String[] args) { + Events event = Events.valueOf(args[0]); + getStateMachine() + .sendEvent(Mono.just(MessageBuilder + .withPayload(event).build())) + .subscribe(); + return "Event " + event + " sent"; + } + }; } -} +} \ No newline at end of file diff --git a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/TasksCommands.java b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/TasksCommands.java index b23a46f0..d04d12f3 100644 --- a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/TasksCommands.java +++ b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/TasksCommands.java @@ -15,34 +15,60 @@ */ package demo.tasks; +import demo.BasicCommand; +import demo.Command; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.shell.command.annotation.Command; -import org.springframework.shell.command.annotation.Option; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; -@Command +@Configuration public class TasksCommands { @Autowired private Tasks tasks; - @Command(command = "tasks run", description = "Run tasks") - public void run() { - tasks.run(); + @Bean + public Command run() { + return new BasicCommand("run", "Run tasks") { + @Override + public String execute(String[] args) { + tasks.run(); + return "Tasks started"; + } + }; } - @Command(command = "tasks list", description = "List tasks") - public String list() { - return tasks.toString(); + @Bean + public Command list() { + return new BasicCommand("list", "List tasks") { + @Override + public String execute(String[] args) { + return tasks.toString(); + } + }; } - @Command(command = "tasks fix", description = "Fix tasks") - public void fix() { - tasks.fix(); + @Bean + public Command fix() { + return new BasicCommand("fix", "Fix tasks") { + @Override + public String execute(String[] args) { + tasks.fix(); + return "Tasks fixed"; + } + }; } - @Command(command = "tasks fail", description = "Fail task") - public void fail(@Option(longNames = {"", "task"}, description = "Task id") String task) { - tasks.fail(task); + @Bean + public Command fail() { + return new BasicCommand("fail [taskId]", "Fail task with [taskId]") { + @Override + public String execute(String[] args) { + String taskId = args[0]; + tasks.fail(taskId); + return "Task " + taskId + " failed"; + } + }; } } diff --git a/spring-statemachine-samples/tasks/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/spring-statemachine-samples/tasks/src/main/resources/META-INF/spring/spring-shell-plugin.xml deleted file mode 100644 index 1112bb35..00000000 --- a/spring-statemachine-samples/tasks/src/main/resources/META-INF/spring/spring-shell-plugin.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/spring-statemachine-samples/tasks/src/main/resources/application.properties b/spring-statemachine-samples/tasks/src/main/resources/application.properties new file mode 100644 index 00000000..70957423 --- /dev/null +++ b/spring-statemachine-samples/tasks/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.main.allow-bean-definition-overriding=true \ No newline at end of file diff --git a/spring-statemachine-samples/tasks/src/test/java/demo/tasks/StateMachineBlockHoundIntegration.java b/spring-statemachine-samples/tasks/src/test/java/demo/tasks/StateMachineBlockHoundIntegration.java deleted file mode 100644 index a41363a4..00000000 --- a/spring-statemachine-samples/tasks/src/test/java/demo/tasks/StateMachineBlockHoundIntegration.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package demo.tasks; - -import reactor.blockhound.BlockHound.Builder; -import reactor.blockhound.integration.BlockHoundIntegration; - -public class StateMachineBlockHoundIntegration implements BlockHoundIntegration { - - @Override - public void applyTo(Builder builder) { - // whitelisting some blocking calls in tests - builder - .allowBlockingCallsInside("demo.tasks.Tasks", "sleep") - .allowBlockingCallsInside("java.util.concurrent.locks.LockSupport", "park"); - } -} diff --git a/spring-statemachine-samples/tasks/src/test/java/demo/tasks/TasksTests.java b/spring-statemachine-samples/tasks/src/test/java/demo/tasks/TasksTests.java deleted file mode 100644 index 08669f96..00000000 --- a/spring-statemachine-samples/tasks/src/test/java/demo/tasks/TasksTests.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package demo.tasks; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.statemachine.TestUtils.doStartAndAssert; -import static org.springframework.statemachine.TestUtils.doStopAndAssert; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.statemachine.ObjectStateMachine; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.StateMachineSystemConstants; -import org.springframework.statemachine.listener.StateMachineListener; -import org.springframework.statemachine.listener.StateMachineListenerAdapter; -import org.springframework.statemachine.state.State; -import org.springframework.statemachine.transition.Transition; -import org.springframework.util.StringUtils; - -import demo.CommonConfiguration; -import demo.tasks.Application.Events; -import demo.tasks.Application.States; - -public class TasksTests { - - private final static Log log = LogFactory.getLog(TasksTests.class); - - private AnnotationConfigApplicationContext context; - - private StateMachine machine; - - private Tasks tasks; - - private TestListener listener; - - @Test - public void testInitialState() throws InterruptedException { - Map variables = machine.getExtendedState().getVariables(); - assertThat(variables).isEmpty(); - } - - @Test - public void testRunOnce() throws InterruptedException { - listener.reset(8, 8, 0); - tasks.run(); - assertThat(listener.stateEnteredLatch.await(8, TimeUnit.SECONDS)).isTrue(); - assertThat(machine.getState().getIds()).containsExactly(States.READY); - Map variables = machine.getExtendedState().getVariables(); - assertThat(variables).hasSize(3); - } - - @Test - public void testRunTwice() throws InterruptedException { - listener.reset(8, 8, 0); - tasks.run(); - assertThat(listener.stateEnteredLatch.await(8, TimeUnit.SECONDS)).isTrue(); - assertThat(machine.getState().getIds()).containsExactly(States.READY); - - Map variables = machine.getExtendedState().getVariables(); - assertThat(variables).hasSize(3); - - listener.reset(8, 8, 0); - tasks.run(); - assertThat(listener.stateEnteredLatch.await(8, TimeUnit.SECONDS)).isTrue(); - assertThat(machine.getState().getIds()).containsExactly(States.READY); - - variables = machine.getExtendedState().getVariables(); - assertThat(variables).hasSize(3); - } - - @Test - @Tag("smoke") - public void testRunSmoke() throws InterruptedException { - for (int i = 0; i < 20; i++) { - log.info("testRunSmoke SMOKE START " + i); - listener.reset(8, 8, 0); - tasks.run(); - - boolean await = listener.stateEnteredLatch.await(8, TimeUnit.SECONDS); - String reason = "Machine was " + machine + " " + StringUtils.collectionToCommaDelimitedString(listener.statesEntered); - assertThat(await).isTrue().withFailMessage(reason); - assertThat(machine.getState().getIds()).containsExactly(States.READY); - log.info("testRunSmoke SMOKE STOP " + i); - } - } - - @Test - public void testFailAutomaticFix() throws InterruptedException { - listener.reset(10, 0, 0); - tasks.fail("T1"); - tasks.run(); - assertThat(listener.stateChangedLatch.await(6, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(10); - assertThat(machine.getState().getIds()).containsExactly(States.READY); - } - - @Test - public void testFailManualFix() throws InterruptedException { - listener.reset(10, 0, 0); - tasks.fail("T2"); - tasks.run(); - assertThat(listener.stateChangedLatch.await(6, TimeUnit.SECONDS)).isTrue(); - - Map variables = machine.getExtendedState().getVariables(); - assertThat(variables).hasSize(3); - - assertThat(machine.getState().getIds()).containsExactly(States.ERROR, States.MANUAL); - listener.reset(1, 0, 0); - tasks.fix(); - assertThat(listener.stateChangedLatch.await(6, TimeUnit.SECONDS)).isTrue(); - assertThat(machine.getState().getIds()).containsExactly(States.READY); - } - - @SuppressWarnings("unchecked") - @BeforeEach - public void setup() throws Exception { - context = new AnnotationConfigApplicationContext(); - context.register(CommonConfiguration.class, Application.class, TestConfig.class); - context.refresh(); - machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); - tasks = context.getBean(Tasks.class); - listener = context.getBean(TestListener.class); - doStartAndAssert(machine); - assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(listener.stateChangedCount).isEqualTo(1); - assertThat(machine.getState().getIds()).containsExactly(States.READY); - } - - @AfterEach - public void clean() { - doStopAndAssert(machine); - context.close(); - context = null; - machine = null; - tasks = null; - listener = null; - } - - static class TestConfig { - - @Autowired - private StateMachine machine; - - @Bean - public StateMachineListener stateMachineListener() { - TestListener listener = new TestListener(); - machine.addStateListener(listener); - return listener; - } - - } - - static class TestListener extends StateMachineListenerAdapter { - - final Object lock = new Object(); - - volatile CountDownLatch stateChangedLatch = new CountDownLatch(1); - volatile CountDownLatch stateEnteredLatch = new CountDownLatch(2); - volatile CountDownLatch stateExitedLatch = new CountDownLatch(0); - volatile CountDownLatch transitionLatch = new CountDownLatch(0); - volatile int stateChangedCount = 0; - volatile int transitionCount = 0; - List> statesEntered = new ArrayList>(); - List> statesExited = new ArrayList>(); - - @Override - public void stateChanged(State from, State to) { - synchronized (lock) { - stateChangedCount++; - stateChangedLatch.countDown(); - } - } - - @Override - public void stateEntered(State state) { - synchronized (lock) { - statesEntered.add(state); - stateEnteredLatch.countDown(); - } - } - - @Override - public void stateExited(State state) { - synchronized (lock) { - statesExited.add(state); - stateExitedLatch.countDown(); - } - } - - @Override - public void transitionEnded(Transition transition) { - synchronized (lock) { - transitionCount++; - transitionLatch.countDown(); - } - } - - public void reset(int c1, int c2, int c3) { - reset(c1, c2, c3, 0); - } - - public void reset(int c1, int c2, int c3, int c4) { - synchronized (lock) { - stateChangedLatch = new CountDownLatch(c1); - stateEnteredLatch = new CountDownLatch(c2); - stateExitedLatch = new CountDownLatch(c3); - transitionLatch = new CountDownLatch(c4); - stateChangedCount = 0; - transitionCount = 0; - statesEntered.clear(); - statesExited.clear(); - } - } - - } - -} diff --git a/spring-statemachine-samples/tasks/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration b/spring-statemachine-samples/tasks/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration deleted file mode 100644 index a487938c..00000000 --- a/spring-statemachine-samples/tasks/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration +++ /dev/null @@ -1 +0,0 @@ -demo.tasks.StateMachineBlockHoundIntegration \ No newline at end of file diff --git a/spring-statemachine-samples/tasks/src/test/resources/log4j2.xml b/spring-statemachine-samples/tasks/src/test/resources/log4j2.xml deleted file mode 100644 index 9c440f57..00000000 --- a/spring-statemachine-samples/tasks/src/test/resources/log4j2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/spring-statemachine-samples/tasks/src/test/resources/logback.xml b/spring-statemachine-samples/tasks/src/test/resources/logback.xml deleted file mode 100644 index cb6447aa..00000000 --- a/spring-statemachine-samples/tasks/src/test/resources/logback.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss} %t %m%n - utf8 - - - - - - - - - - diff --git a/spring-statemachine-samples/turnstile/spring-statemachine-samples-turnstile.gradle b/spring-statemachine-samples/turnstile/spring-statemachine-samples-turnstile.gradle index 5c1c1d38..cfc807c2 100644 --- a/spring-statemachine-samples/turnstile/spring-statemachine-samples-turnstile.gradle +++ b/spring-statemachine-samples/turnstile/spring-statemachine-samples-turnstile.gradle @@ -8,7 +8,7 @@ dependencies { management platform(project(":spring-statemachine-platform")) implementation project(':spring-statemachine-samples-common') implementation project(':spring-statemachine-core') - implementation 'org.springframework.shell:spring-shell-starter' + implementation 'org.springframework.boot:spring-boot-starter' testImplementation(testFixtures(project(':spring-statemachine-core'))) testImplementation (project(':spring-statemachine-test')) testImplementation 'org.hamcrest:hamcrest-core' diff --git a/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Application.java b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Application.java index 3cc20e23..9a58c2f4 100644 --- a/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Application.java +++ b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Application.java @@ -15,66 +15,12 @@ */ package demo.turnstile; -import java.util.EnumSet; - import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Configuration; -import org.springframework.shell.command.annotation.CommandScan; -import org.springframework.statemachine.config.EnableStateMachine; -import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; -import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; -import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; -@CommandScan @SpringBootApplication(scanBasePackages = "demo") public class Application { -//tag::snippetA[] - @Configuration - @EnableStateMachine - static class StateMachineConfig - extends EnumStateMachineConfigurerAdapter { - - @Override - public void configure(StateMachineStateConfigurer states) - throws Exception { - states - .withStates() - .initial(States.LOCKED) - .states(EnumSet.allOf(States.class)); - } - - @Override - public void configure(StateMachineTransitionConfigurer transitions) - throws Exception { - transitions - .withExternal() - .source(States.LOCKED) - .target(States.UNLOCKED) - .event(Events.COIN) - .and() - .withExternal() - .source(States.UNLOCKED) - .target(States.LOCKED) - .event(Events.PUSH); - } - - } -//end::snippetA[] - -//tag::snippetB[] - public enum States { - LOCKED, UNLOCKED - } -//end::snippetB[] - -//tag::snippetC[] - public enum Events { - COIN, PUSH - } -//end::snippetC[] - public static void main(String[] args) { SpringApplication.run(Application.class, args); } diff --git a/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Events.java b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Events.java new file mode 100644 index 00000000..363aa6d1 --- /dev/null +++ b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Events.java @@ -0,0 +1,7 @@ +package demo.turnstile; + +//tag::snippetC[] +public enum Events { + COIN, PUSH +} +//end::snippetC[] \ No newline at end of file diff --git a/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineCommands.java b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineCommands.java index f02e1f50..58d45dbb 100644 --- a/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineCommands.java +++ b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineCommands.java @@ -15,24 +15,30 @@ */ package demo.turnstile; +import demo.BasicCommand; +import demo.Command; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.shell.command.annotation.Command; -import org.springframework.shell.command.annotation.Option; import demo.AbstractStateMachineCommands; -import demo.turnstile.Application.Events; -import demo.turnstile.Application.States; import reactor.core.publisher.Mono; -@Command +@Configuration public class StateMachineCommands extends AbstractStateMachineCommands { - @Command(command = "sm event", description = "Sends an event to a state machine") - public String event(@Option(longNames = { "", "event" }, required = true, description = "The event") final Events event) { - getStateMachine() - .sendEvent(Mono.just(MessageBuilder - .withPayload(event).build())) - .subscribe(); - return "Event " + event + " send"; + @Bean + public Command event() { + return new BasicCommand("event", "Sends an event to a state machine") { + @Override + public String execute(String[] args) { + Events event = Events.valueOf(args[0]); + getStateMachine() + .sendEvent(Mono.just(MessageBuilder + .withPayload(event).build())) + .subscribe(); + return "Event " + event + " sent"; + } + }; } } diff --git a/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineConfiguration.java b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineConfiguration.java new file mode 100644 index 00000000..28ec441f --- /dev/null +++ b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineConfiguration.java @@ -0,0 +1,42 @@ +package demo.turnstile; + +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +import java.util.EnumSet; + +//tag::snippetA[] +@Configuration +@EnableStateMachine +public class StateMachineConfiguration + extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) + throws Exception { + states + .withStates() + .initial(States.LOCKED) + .states(EnumSet.allOf(States.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) + throws Exception { + transitions + .withExternal() + .source(States.LOCKED) + .target(States.UNLOCKED) + .event(Events.COIN) + .and() + .withExternal() + .source(States.UNLOCKED) + .target(States.LOCKED) + .event(Events.PUSH); + } + +} +//end::snippetA[] diff --git a/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/States.java b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/States.java new file mode 100644 index 00000000..9f97acdc --- /dev/null +++ b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/States.java @@ -0,0 +1,7 @@ +package demo.turnstile; + +//tag::snippetB[] +public enum States { + LOCKED, UNLOCKED +} +//end::snippetB[] \ No newline at end of file diff --git a/spring-statemachine-samples/turnstile/src/main/resources/application.properties b/spring-statemachine-samples/turnstile/src/main/resources/application.properties index 810a40c4..70957423 100644 --- a/spring-statemachine-samples/turnstile/src/main/resources/application.properties +++ b/spring-statemachine-samples/turnstile/src/main/resources/application.properties @@ -1 +1 @@ -spring.shell.interactive.enabled=true +spring.main.allow-bean-definition-overriding=true \ No newline at end of file diff --git a/spring-statemachine-samples/turnstile/src/test/java/demo/turnstile/TurnstileTests.java b/spring-statemachine-samples/turnstile/src/test/java/demo/turnstile/TurnstileTests.java deleted file mode 100644 index e3976f30..00000000 --- a/spring-statemachine-samples/turnstile/src/test/java/demo/turnstile/TurnstileTests.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package demo.turnstile; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.statemachine.TestUtils.doStartAndAssert; -import static org.springframework.statemachine.TestUtils.doStopAndAssert; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.statemachine.ObjectStateMachine; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.StateMachineSystemConstants; -import org.springframework.statemachine.listener.StateMachineListener; -import org.springframework.statemachine.listener.StateMachineListenerAdapter; -import org.springframework.statemachine.state.State; -import org.springframework.statemachine.transition.Transition; - -import demo.CommonConfiguration; -import demo.turnstile.Application.Events; -import demo.turnstile.Application.States; - -public class TurnstileTests { - - private AnnotationConfigApplicationContext context; - - private StateMachine machine; - - private TestListener listener; - - private StateMachineCommands commands; - - @Test - public void testNotStarted() throws Exception { - assertThat(commands.state()).isEqualTo("No state"); - } - - @Test - public void testInitialState() throws Exception { - doStartAndAssert(machine); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - listener.stateEnteredLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.LOCKED); - assertThat(listener.statesEntered).hasSize(1); - assertThat(listener.statesEntered.get(0).getId()).isEqualTo(States.LOCKED); - assertThat(listener.statesExited).isEmpty(); - } - - static class Config { - - @Autowired - private StateMachine machine; - - @Bean - public StateMachineListener stateMachineListener() { - TestListener listener = new TestListener(); - machine.addStateListener(listener); - return listener; - } - } - - static class TestListener extends StateMachineListenerAdapter { - - volatile CountDownLatch stateChangedLatch = new CountDownLatch(1); - volatile CountDownLatch stateEnteredLatch = new CountDownLatch(2); - volatile CountDownLatch stateExitedLatch = new CountDownLatch(0); - volatile CountDownLatch transitionLatch = new CountDownLatch(0); - volatile List> transitions = new ArrayList>(); - List> statesEntered = new ArrayList>(); - List> statesExited = new ArrayList>(); - volatile int transitionCount = 0; - - @Override - public void stateChanged(State from, State to) { - stateChangedLatch.countDown(); - } - - @Override - public void stateEntered(State state) { - statesEntered.add(state); - stateEnteredLatch.countDown(); - } - - @Override - public void stateExited(State state) { - statesExited.add(state); - stateExitedLatch.countDown(); - } - - @Override - public void transition(Transition transition) { - transitions.add(transition); - transitionLatch.countDown(); - transitionCount++; - } - - public void reset(int c1, int c2, int c3) { - reset(c1, c2, c3, 0); - } - - public void reset(int c1, int c2, int c3, int c4) { - stateChangedLatch = new CountDownLatch(c1); - stateEnteredLatch = new CountDownLatch(c2); - stateExitedLatch = new CountDownLatch(c3); - transitionLatch = new CountDownLatch(c4); - statesEntered.clear(); - statesExited.clear(); - transitionCount = 0; - transitions.clear(); - } - - } - - @SuppressWarnings("unchecked") - @BeforeEach - public void setup() { - context = new AnnotationConfigApplicationContext(); - context.register(CommonConfiguration.class, Application.class, Config.class); - context.refresh(); - machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); - listener = context.getBean(TestListener.class); - commands = context.getBean(StateMachineCommands.class); - } - - @AfterEach - public void clean() { - doStopAndAssert(machine); - context.close(); - context = null; - machine = null; - } - -} diff --git a/spring-statemachine-samples/washer/spring-statemachine-samples-washer.gradle b/spring-statemachine-samples/washer/spring-statemachine-samples-washer.gradle index 88389f41..6e47fedc 100644 --- a/spring-statemachine-samples/washer/spring-statemachine-samples-washer.gradle +++ b/spring-statemachine-samples/washer/spring-statemachine-samples-washer.gradle @@ -8,7 +8,7 @@ dependencies { management platform(project(":spring-statemachine-platform")) implementation project(':spring-statemachine-samples-common') implementation project(':spring-statemachine-core') - implementation 'org.springframework.shell:spring-shell-core' + implementation 'org.springframework.boot:spring-boot-starter' testImplementation(testFixtures(project(':spring-statemachine-core'))) testImplementation (project(':spring-statemachine-test')) testImplementation 'org.hamcrest:hamcrest-core' diff --git a/spring-statemachine-samples/washer/src/main/java/demo/washer/Application.java b/spring-statemachine-samples/washer/src/main/java/demo/washer/Application.java index b71a78ca..4e0d1989 100644 --- a/spring-statemachine-samples/washer/src/main/java/demo/washer/Application.java +++ b/spring-statemachine-samples/washer/src/main/java/demo/washer/Application.java @@ -16,6 +16,7 @@ package demo.washer; import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; @@ -23,7 +24,7 @@ import org.springframework.statemachine.config.builders.StateMachineStateConfigu import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.config.configurers.StateConfigurer.History; -@Configuration +@SpringBootApplication(scanBasePackages = "demo") public class Application { @Configuration diff --git a/spring-statemachine-samples/washer/src/main/java/demo/washer/StateMachineCommands.java b/spring-statemachine-samples/washer/src/main/java/demo/washer/StateMachineCommands.java index 4eb90960..1b98041f 100644 --- a/spring-statemachine-samples/washer/src/main/java/demo/washer/StateMachineCommands.java +++ b/spring-statemachine-samples/washer/src/main/java/demo/washer/StateMachineCommands.java @@ -15,25 +15,32 @@ */ package demo.washer; +import demo.BasicCommand; +import demo.Command; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.shell.command.annotation.Command; -import org.springframework.shell.command.annotation.Option; import demo.AbstractStateMachineCommands; import demo.washer.Application.Events; import demo.washer.Application.States; import reactor.core.publisher.Mono; -@Command +@Configuration public class StateMachineCommands extends AbstractStateMachineCommands { - @Command(command = "sm event", description = "Sends an event to a state machine") - public String event(@Option(longNames = { "", "event" }, required = true, description = "The event") final Events event) { - getStateMachine() - .sendEvent(Mono.just(MessageBuilder - .withPayload(event).build())) - .subscribe(); - return "Event " + event + " send"; + @Bean + public Command event() { + return new BasicCommand("event", "Sends an event to a state machine") { + @Override + public String execute(String[] args) { + Events event = Events.valueOf(args[0]); + getStateMachine() + .sendEvent(Mono.just(MessageBuilder + .withPayload(event).build())) + .subscribe(); + return "Event " + event + " sent"; + } + }; } - } \ No newline at end of file diff --git a/spring-statemachine-samples/washer/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/spring-statemachine-samples/washer/src/main/resources/META-INF/spring/spring-shell-plugin.xml deleted file mode 100644 index 1112bb35..00000000 --- a/spring-statemachine-samples/washer/src/main/resources/META-INF/spring/spring-shell-plugin.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/spring-statemachine-samples/washer/src/main/resources/application.properties b/spring-statemachine-samples/washer/src/main/resources/application.properties new file mode 100644 index 00000000..70957423 --- /dev/null +++ b/spring-statemachine-samples/washer/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.main.allow-bean-definition-overriding=true \ No newline at end of file diff --git a/spring-statemachine-samples/washer/src/test/java/demo/washer/WasherTests.java b/spring-statemachine-samples/washer/src/test/java/demo/washer/WasherTests.java deleted file mode 100644 index 893cea5a..00000000 --- a/spring-statemachine-samples/washer/src/test/java/demo/washer/WasherTests.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package demo.washer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.statemachine.TestUtils.doSendEventAndConsumeAll; -import static org.springframework.statemachine.TestUtils.doStartAndAssert; -import static org.springframework.statemachine.TestUtils.doStopAndAssert; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.statemachine.ObjectStateMachine; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.StateMachineSystemConstants; -import org.springframework.statemachine.listener.StateMachineListener; -import org.springframework.statemachine.listener.StateMachineListenerAdapter; -import org.springframework.statemachine.state.State; -import org.springframework.statemachine.transition.Transition; - -import demo.CommonConfiguration; -import demo.washer.Application.Events; -import demo.washer.Application.States; - -public class WasherTests { - - private AnnotationConfigApplicationContext context; - - private StateMachine machine; - - private TestListener listener; - - @Test - public void testInitialState() throws Exception { - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - listener.stateEnteredLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.RUNNING, States.WASHING); - assertThat(listener.statesEntered).hasSize(2); - assertThat(listener.statesEntered.get(0).getId()).isEqualTo(States.RUNNING); - assertThat(listener.statesEntered.get(1).getId()).isEqualTo(States.WASHING); - assertThat(listener.statesExited).isEmpty(); - } - - @Test - public void testRinse() throws Exception { - listener.reset(1, 0, 0); - doSendEventAndConsumeAll(machine, Events.RINSE); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.RUNNING, States.RINSING); - } - - @Test - public void testRinseCutPower() throws Exception { - listener.reset(1, 0, 0); - doSendEventAndConsumeAll(machine, Events.RINSE); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - - listener.reset(1, 0, 0); - doSendEventAndConsumeAll(machine, Events.CUTPOWER); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.POWEROFF); - } - - @Test - public void testRinseCutRestorePower() throws Exception { - listener.reset(1, 0, 0); - doSendEventAndConsumeAll(machine, Events.RINSE); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - - listener.reset(1, 0, 0); - doSendEventAndConsumeAll(machine, Events.CUTPOWER); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - - listener.reset(1, 0, 0); - doSendEventAndConsumeAll(machine, Events.RESTOREPOWER); - listener.stateChangedLatch.await(1, TimeUnit.SECONDS); - assertThat(machine.getState().getIds()).containsExactly(States.RUNNING, States.RINSING); - } - - static class Config { - - @Autowired - private StateMachine machine; - - @Bean - public StateMachineListener stateMachineListener() { - TestListener listener = new TestListener(); - machine.addStateListener(listener); - return listener; - } - } - - static class TestListener extends StateMachineListenerAdapter { - - volatile CountDownLatch stateChangedLatch = new CountDownLatch(1); - volatile CountDownLatch stateEnteredLatch = new CountDownLatch(2); - volatile CountDownLatch stateExitedLatch = new CountDownLatch(0); - volatile CountDownLatch transitionLatch = new CountDownLatch(0); - volatile List> transitions = new ArrayList>(); - List> statesEntered = new ArrayList>(); - List> statesExited = new ArrayList>(); - volatile int transitionCount = 0; - - @Override - public void stateChanged(State from, State to) { - stateChangedLatch.countDown(); - } - - @Override - public void stateEntered(State state) { - statesEntered.add(state); - stateEnteredLatch.countDown(); - } - - @Override - public void stateExited(State state) { - statesExited.add(state); - stateExitedLatch.countDown(); - } - - @Override - public void transition(Transition transition) { - transitions.add(transition); - transitionLatch.countDown(); - transitionCount++; - } - - public void reset(int c1, int c2, int c3) { - reset(c1, c2, c3, 0); - } - - public void reset(int c1, int c2, int c3, int c4) { - stateChangedLatch = new CountDownLatch(c1); - stateEnteredLatch = new CountDownLatch(c2); - stateExitedLatch = new CountDownLatch(c3); - transitionLatch = new CountDownLatch(c4); - statesEntered.clear(); - statesExited.clear(); - transitionCount = 0; - transitions.clear(); - } - - } - - @SuppressWarnings("unchecked") - @BeforeEach - public void setup() { - context = new AnnotationConfigApplicationContext(); - context.register(CommonConfiguration.class, Application.class, Config.class); - context.refresh(); - machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); - listener = context.getBean(TestListener.class); - doStartAndAssert(machine); - } - - @AfterEach - public void clean() { - doStopAndAssert(machine); - context.close(); - context = null; - machine = null; - } - -} diff --git a/spring-statemachine-samples/zookeeper/spring-statemachine-samples-zookeeper.gradle b/spring-statemachine-samples/zookeeper/spring-statemachine-samples-zookeeper.gradle index f92feb90..f248b017 100644 --- a/spring-statemachine-samples/zookeeper/spring-statemachine-samples-zookeeper.gradle +++ b/spring-statemachine-samples/zookeeper/spring-statemachine-samples-zookeeper.gradle @@ -9,5 +9,5 @@ dependencies { implementation project(':spring-statemachine-zookeeper') implementation project(':spring-statemachine-samples-common') implementation project(':spring-statemachine-core') - implementation 'org.springframework.shell:spring-shell-core' + implementation 'org.springframework.boot:spring-boot-starter' } diff --git a/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/Application.java b/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/Application.java index 09c3f096..59cb0092 100644 --- a/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/Application.java +++ b/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/Application.java @@ -19,6 +19,7 @@ import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.config.EnableStateMachine; @@ -29,7 +30,7 @@ import org.springframework.statemachine.config.builders.StateMachineTransitionCo import org.springframework.statemachine.ensemble.StateMachineEnsemble; import org.springframework.statemachine.zookeeper.ZookeeperStateMachineEnsemble; -@Configuration +@SpringBootApplication(scanBasePackages = "demo") public class Application { @Configuration diff --git a/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/StateMachineCommands.java b/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/StateMachineCommands.java index 0583c613..8665afc1 100644 --- a/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/StateMachineCommands.java +++ b/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/StateMachineCommands.java @@ -15,22 +15,30 @@ */ package demo.zookeeper; +import demo.BasicCommand; +import demo.Command; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.shell.command.annotation.Command; -import org.springframework.shell.command.annotation.Option; import demo.AbstractStateMachineCommands; import reactor.core.publisher.Mono; -@Command +@Configuration public class StateMachineCommands extends AbstractStateMachineCommands { - @Command(command = "sm event", description = "Sends an event to a state machine") - public String event(@Option(longNames = { "", "event" }, required = true, description = "The event") final String event) { - getStateMachine() - .sendEvent(Mono.just(MessageBuilder - .withPayload(event).build())) - .subscribe(); - return "Event " + event + " send"; + @Bean + public Command event() { + return new BasicCommand("event", "Sends an event to a state machine") { + @Override + public String execute(String[] args) { + String event = args[0]; + getStateMachine() + .sendEvent(Mono.just(MessageBuilder + .withPayload(event).build())) + .subscribe(); + return "Event " + event + " sent"; + } + }; } } diff --git a/spring-statemachine-samples/zookeeper/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/spring-statemachine-samples/zookeeper/src/main/resources/META-INF/spring/spring-shell-plugin.xml deleted file mode 100644 index 1112bb35..00000000 --- a/spring-statemachine-samples/zookeeper/src/main/resources/META-INF/spring/spring-shell-plugin.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/spring-statemachine-samples/zookeeper/src/main/resources/application.properties b/spring-statemachine-samples/zookeeper/src/main/resources/application.properties new file mode 100644 index 00000000..70957423 --- /dev/null +++ b/spring-statemachine-samples/zookeeper/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.main.allow-bean-definition-overriding=true \ No newline at end of file