From 7dc5ed851e71af3caae9aa4f85e53582ba7837d2 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 11 Apr 2025 09:35:04 +0200 Subject: [PATCH 01/46] Upgrade gradle version to 8.13 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9bf7bd339..c6f003026 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 5170c0e2d2fff8b9d413955be57f7093495bc50d Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 11 Apr 2025 09:36:57 +0200 Subject: [PATCH 02/46] Fix warning about missing "@Deprecated" on a deprecated method --- .../persist/AbstractPersistingStateMachineInterceptor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/persist/AbstractPersistingStateMachineInterceptor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/persist/AbstractPersistingStateMachineInterceptor.java index 8037ecc5f..782f6b579 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/persist/AbstractPersistingStateMachineInterceptor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/persist/AbstractPersistingStateMachineInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-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. @@ -51,6 +51,7 @@ * then start it again. * * @author Janne Valkealahti + * @author Mahmoud Ben Hassine * * @param the type of state * @param the type of event @@ -143,6 +144,7 @@ public void setExtendedStateVariablesFunction( * @return the state machine context * @deprecated in favour of {@link #buildStateMachineContext(StateMachine, StateMachine, State, Message)} */ + @Deprecated protected StateMachineContext buildStateMachineContext(StateMachine stateMachine, StateMachine rootStateMachine, State state) { return buildStateMachineContext(stateMachine, rootStateMachine, state, null); From 1a017dced2fa6f4e44a30aeb536082e8bd7eb494 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 15 Apr 2025 14:08:19 +0200 Subject: [PATCH 03/46] 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 | 151 ++++--- .../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 | 117 +++--- .../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, 724 insertions(+), 2015 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 bcf896cc9..7b233d327 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 3d8f6be4c..7a0132c43 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 03b822c34..7b06686e6 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 146d4ca95..8d11b8b7a 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 37476505b..0e1e070b9 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 134b3f7ea..b94a2ef0d 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 495fb4cde..d0f83f4ea 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 0326ed40d..591cbaa68 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 7a6d264fe..0f63ee741 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 4b1bb41f4..787cd19f3 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 a3e1340bd..7c8bf1f12 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 a14b37b9c..8b16f180a 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 5663848c6..4f45cfe27 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 3ea1f51da..b570009d6 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.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 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 f1d13a388..80913fabc 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 void back() { .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 8c5685e30..c05a6b7ed 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 @@ 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(); + @Bean + public Command lcd() { + return new BasicCommand("lcd", "Prints CD player lcd info") { + @Override + public String execute(String[] args) { + return cdPlayer.getLcdStatus(); + } + }; } - @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 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(); } - } - 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 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 play", description = "Press player play button") - public void play() { - cdPlayer.play(); + @Bean + public Command play() { + return new BasicCommand("play", "Press player play button") { + @Override + public String execute(String[] args) { + cdPlayer.play(); + return ""; + } + }; } - @Command(command = "cd stop", description = "Press player stop button") - public void stop() { - cdPlayer.stop(); + @Bean + public Command stop() { + return new BasicCommand("stop", "Press player stop button") { + @Override + public String execute(String[] args) { + cdPlayer.stop(); + return ""; + } + }; } - @Command(command = "cd pause", description = "Press player pause button") - public void pause() { - cdPlayer.pause(); + @Bean + public Command pause() { + return new BasicCommand("pause", "Press player pause button") { + @Override + public String execute(String[] args) { + cdPlayer.pause(); + return ""; + } + }; } - @Command(command = "cd eject", description = "Press player eject button") - public void eject() { - cdPlayer.eject(); + @Bean + public Command eject() { + return new BasicCommand("eject", "Press player eject button") { + @Override + public String execute(String[] args) { + cdPlayer.eject(); + return ""; + } + }; } - @Command(command = "cd forward", description = "Press player forward button") - public void forward() { - cdPlayer.forward(); + @Bean + public Command forward() { + return new BasicCommand("forward", "Press player forward button") { + @Override + public String execute(String[] args) { + cdPlayer.forward(); + return ""; + } + }; } - @Command(command = "cd back", description = "Press player back button") - public void back() { - cdPlayer.back(); + @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 2125ea516..2748781fc 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 1112bb354..000000000 --- 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 000000000..709574239 --- /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 15867ca60..000000000 --- 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 9c440f57c..000000000 --- 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 ba723ca08..2afc5e8a0 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 fb7aa00cc..551c36be9 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.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 6f8eeac42..00dcfddad 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 2991ba340..4ded9e899 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 1112bb354..000000000 --- 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 000000000..709574239 --- /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 bcb3ccf49..000000000 --- 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 523e6ba6a..0eb46c770 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 b1d8fcd43..8f6d4516a 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.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.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 9b5761ed3..2f91c46c2 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 810a40c47..709574239 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 71a659eff..000000000 --- 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 d955870e6..023055970 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 f2ea1580d..a18cbe1b2 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 @@ protected StateMachine getStateMachine() { 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"; - } + @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"; + } + } + }; } - @Command(command = "sm start", description = "Start a state machine") - public String start() { - stateMachine.startReactively().subscribe(); - return "State machine started"; + @Bean + public Command start() { + return new BasicCommand("start", "Start a state machine") { + public String execute(String[] args) { + 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"; + @Bean + public Command stop() { + return new BasicCommand("stop", "Stop a state machine") { + public String execute(String[] args) { + stateMachine.stopReactively().subscribe(); + return "State machine stopped"; + } + }; } - @Command(command = "sm print", description = "Print state machine") - public String print() { - return stateChartModel; + @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; + } + }; } - @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 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(); } - } else { - buf.append("No variables"); - } - return buf.toString(); + }; } - -} \ No newline at end of file +} 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 000000000..4f9be5099 --- /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 897e02990..cc0b2585c 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 { + + /** + * The command name. + * @return the command name + */ + String getName(); -@Component -@Order(Ordered.HIGHEST_PRECEDENCE) -public class StateMachinePromptProvider implements PromptProvider { + /** + * The command description. + * @return the command description + */ + String getDescription(); - @Override - public AttributedString getPrompt() { - return AttributedString.fromAnsi("sm>"); - } + /** + * 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 000000000..dd6f7dc32 --- /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 d2b698434..000000000 --- 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 000000000..3f819c21b --- /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 1112bb354..000000000 --- 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 37d61a407..cf881a1e8 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 ea9dc3d6b..080a714f6 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.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 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 90fa20cbb..f8056aba8 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 b23a46f0b..d04d12f33 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 1112bb354..000000000 --- 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 000000000..709574239 --- /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 a41363a4a..000000000 --- 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 08669f964..000000000 --- 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 a487938c2..000000000 --- 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 9c440f57c..000000000 --- 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 cb6447aa5..000000000 --- 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 5c1c1d380..cfc807c2b 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 3cc20e235..9a58c2f40 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 000000000..363aa6d16 --- /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 f02e1f50f..58d45dbbe 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 000000000..28ec441f4 --- /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 000000000..9f97acdcd --- /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 810a40c47..709574239 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 e3976f308..000000000 --- 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 88389f411..6e47fedcd 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 b71a78ca4..4e0d19895 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.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 4eb90960b..1b98041f6 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 1112bb354..000000000 --- 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 000000000..709574239 --- /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 893cea5a0..000000000 --- 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 f92feb903..f248b0179 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 09c3f096f..59cb00922 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.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.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 0583c6139..8665afc10 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 1112bb354..000000000 --- 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 000000000..709574239 --- /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 From db87f877ac26422ca435398df0c4c65315ff2b1e Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 22 Apr 2025 21:16:32 +0200 Subject: [PATCH 04/46] Update test dependencies for the latest Spring Boot 3.5 snapshots Spring Boot 3.5 updated JUnit Jupiter from v5.11 to v5.12. With this new version, the junit-platform-launcher dependency must be declared explicitly. --- .../spring-statemachine-autoconfigure.gradle | 4 +++- .../spring-statemachine-build-tests.gradle | 2 +- .../spring-statemachine-cluster.gradle | 2 +- spring-statemachine-core/spring-statemachine-core.gradle | 5 ++++- .../jpa/spring-statemachine-data-jpa.gradle | 5 ++++- .../mongodb/spring-statemachine-data-mongodb.gradle | 5 ++++- .../redis/spring-statemachine-data-redis.gradle | 5 ++++- spring-statemachine-data/spring-statemachine-data.gradle | 8 ++++++-- spring-statemachine-kryo/spring-statemachine-kryo.gradle | 2 +- .../spring-statemachine-recipes.gradle | 2 +- spring-statemachine-samples/build.gradle | 4 +++- .../datajpa/spring-statemachine-samples-datajpa.gradle | 2 +- ...spring-statemachine-samples-datajpamultipersist.gradle | 2 +- .../spring-statemachine-samples-datapersist.gradle | 2 +- .../deploy/spring-statemachine-samples-deploy.gradle | 2 +- .../spring-statemachine-samples-eventservice.gradle | 2 +- .../spring-statemachine-samples-monitoring.gradle | 2 +- .../spring-statemachine-samples-ordershipping.gradle | 2 +- .../persist/spring-statemachine-samples-persist.gradle | 2 +- .../showcase/spring-statemachine-samples-showcase.gradle | 2 +- .../tasks/spring-statemachine-samples-tasks.gradle | 2 +- .../spring-statemachine-samples-turnstile.gradle | 2 +- .../spring-statemachine-samples-turnstilereactive.gradle | 2 +- .../washer/spring-statemachine-samples-washer.gradle | 2 +- spring-statemachine-uml/spring-statemachine-uml.gradle | 2 +- .../spring-statemachine-zookeeper.gradle | 2 +- 26 files changed, 47 insertions(+), 27 deletions(-) diff --git a/spring-statemachine-autoconfigure/spring-statemachine-autoconfigure.gradle b/spring-statemachine-autoconfigure/spring-statemachine-autoconfigure.gradle index a0410b5a5..1dbb54b56 100644 --- a/spring-statemachine-autoconfigure/spring-statemachine-autoconfigure.gradle +++ b/spring-statemachine-autoconfigure/spring-statemachine-autoconfigure.gradle @@ -18,6 +18,8 @@ dependencies { optional 'org.springframework.boot:spring-boot-starter-data-redis' optional 'org.springframework.boot:spring-boot-starter-data-mongodb' testImplementation 'org.springframework.boot:spring-boot-test' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' + testImplementation 'org.assertj:assertj-core' testRuntimeOnly 'com.h2database:h2' } diff --git a/spring-statemachine-build-tests/spring-statemachine-build-tests.gradle b/spring-statemachine-build-tests/spring-statemachine-build-tests.gradle index 1994d5c23..6b580ad5c 100644 --- a/spring-statemachine-build-tests/spring-statemachine-build-tests.gradle +++ b/spring-statemachine-build-tests/spring-statemachine-build-tests.gradle @@ -17,8 +17,8 @@ dependencies { testRuntimeOnly 'org.springframework.boot:spring-boot-starter-data-mongodb' testRuntimeOnly 'org.springframework.boot:spring-boot-starter-data-redis' testRuntimeOnly 'redis.clients:jedis' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa' testImplementation 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter' diff --git a/spring-statemachine-cluster/spring-statemachine-cluster.gradle b/spring-statemachine-cluster/spring-statemachine-cluster.gradle index 8e50c4ac4..e3ea418cb 100644 --- a/spring-statemachine-cluster/spring-statemachine-cluster.gradle +++ b/spring-statemachine-cluster/spring-statemachine-cluster.gradle @@ -10,6 +10,6 @@ dependencies { api 'org.springframework.integration:spring-integration-zookeeper' testImplementation 'org.apache.curator:curator-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-core/spring-statemachine-core.gradle b/spring-statemachine-core/spring-statemachine-core.gradle index c8739ca14..ebe499190 100644 --- a/spring-statemachine-core/spring-statemachine-core.gradle +++ b/spring-statemachine-core/spring-statemachine-core.gradle @@ -14,7 +14,10 @@ dependencies { compileOnly 'com.google.code.findbugs:jsr305' testImplementation 'org.springframework:spring-web' testImplementation 'org.springframework:spring-webmvc' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-test' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' + testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.awaitility:awaitility' testImplementation 'org.springframework.security:spring-security-config' diff --git a/spring-statemachine-data/jpa/spring-statemachine-data-jpa.gradle b/spring-statemachine-data/jpa/spring-statemachine-data-jpa.gradle index 0644f30e5..63435a8d6 100644 --- a/spring-statemachine-data/jpa/spring-statemachine-data-jpa.gradle +++ b/spring-statemachine-data/jpa/spring-statemachine-data-jpa.gradle @@ -14,7 +14,10 @@ dependencies { testImplementation(testFixtures(project(':spring-statemachine-core'))) testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.hsqldb:hsqldb' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-test' + testImplementation 'org.springframework.boot:spring-boot-test-autoconfigure' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.springframework.boot:spring-boot-starter-data-jpa' testRuntimeOnly 'org.springframework.boot:spring-boot-starter-web' } diff --git a/spring-statemachine-data/mongodb/spring-statemachine-data-mongodb.gradle b/spring-statemachine-data/mongodb/spring-statemachine-data-mongodb.gradle index da6477054..ce90495a0 100644 --- a/spring-statemachine-data/mongodb/spring-statemachine-data-mongodb.gradle +++ b/spring-statemachine-data/mongodb/spring-statemachine-data-mongodb.gradle @@ -12,7 +12,10 @@ dependencies { testImplementation(testFixtures(project(':spring-statemachine-data-common'))) testImplementation(testFixtures(project(':spring-statemachine-core'))) testImplementation 'io.projectreactor:reactor-test' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-test' + testImplementation 'org.springframework.boot:spring-boot-test-autoconfigure' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.springframework.boot:spring-boot-starter-data-mongodb' testRuntimeOnly 'org.springframework.boot:spring-boot-starter-web' } diff --git a/spring-statemachine-data/redis/spring-statemachine-data-redis.gradle b/spring-statemachine-data/redis/spring-statemachine-data-redis.gradle index 81ecb33ae..b3b851a6e 100644 --- a/spring-statemachine-data/redis/spring-statemachine-data-redis.gradle +++ b/spring-statemachine-data/redis/spring-statemachine-data-redis.gradle @@ -12,7 +12,10 @@ dependencies { testImplementation(testFixtures(project(':spring-statemachine-data-common'))) testImplementation(testFixtures(project(':spring-statemachine-core'))) testImplementation 'io.projectreactor:reactor-test' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-test' + testImplementation 'org.springframework.boot:spring-boot-test-autoconfigure' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.apache.commons:commons-pool2' testRuntimeOnly 'redis.clients:jedis' testRuntimeOnly 'org.springframework.boot:spring-boot-starter-data-redis' diff --git a/spring-statemachine-data/spring-statemachine-data.gradle b/spring-statemachine-data/spring-statemachine-data.gradle index c46edfadd..66e8d7b68 100644 --- a/spring-statemachine-data/spring-statemachine-data.gradle +++ b/spring-statemachine-data/spring-statemachine-data.gradle @@ -15,7 +15,11 @@ dependencies { testFixturesImplementation (project(':spring-statemachine-test')) testImplementation(testFixtures(project(':spring-statemachine-core'))) testImplementation 'io.projectreactor:reactor-test' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testFixturesImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-test' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' + testFixturesImplementation 'org.springframework.boot:spring-boot-test' + testFixturesImplementation 'org.junit.jupiter:junit-jupiter-engine' + testFixturesImplementation 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.springframework.boot:spring-boot-starter-web' } diff --git a/spring-statemachine-kryo/spring-statemachine-kryo.gradle b/spring-statemachine-kryo/spring-statemachine-kryo.gradle index 3a871406a..0ef82ec2d 100644 --- a/spring-statemachine-kryo/spring-statemachine-kryo.gradle +++ b/spring-statemachine-kryo/spring-statemachine-kryo.gradle @@ -9,6 +9,6 @@ dependencies { api project(':spring-statemachine-core') api 'com.esotericsoftware:kryo-shaded' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-recipes/spring-statemachine-recipes.gradle b/spring-statemachine-recipes/spring-statemachine-recipes.gradle index 208e2c566..ebd5638f1 100644 --- a/spring-statemachine-recipes/spring-statemachine-recipes.gradle +++ b/spring-statemachine-recipes/spring-statemachine-recipes.gradle @@ -10,8 +10,8 @@ dependencies { testImplementation(testFixtures(project(':spring-statemachine-core'))) testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.springframework:spring-test' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' testImplementation 'io.projectreactor.tools:blockhound' testImplementation 'org.assertj:assertj-core' } diff --git a/spring-statemachine-samples/build.gradle b/spring-statemachine-samples/build.gradle index 49c2e8557..c4757e3b9 100644 --- a/spring-statemachine-samples/build.gradle +++ b/spring-statemachine-samples/build.gradle @@ -20,7 +20,9 @@ project('spring-statemachine-samples-turnstilereactive') { api 'org.springframework.boot:spring-boot-starter-webflux' testImplementation(testFixtures(project(":spring-statemachine-core"))) testImplementation 'io.projectreactor:reactor-test' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-test' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } } diff --git a/spring-statemachine-samples/datajpa/spring-statemachine-samples-datajpa.gradle b/spring-statemachine-samples/datajpa/spring-statemachine-samples-datajpa.gradle index 89a912255..7d036bb28 100644 --- a/spring-statemachine-samples/datajpa/spring-statemachine-samples-datajpa.gradle +++ b/spring-statemachine-samples/datajpa/spring-statemachine-samples-datajpa.gradle @@ -21,6 +21,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-samples/datajpamultipersist/spring-statemachine-samples-datajpamultipersist.gradle b/spring-statemachine-samples/datajpamultipersist/spring-statemachine-samples-datajpamultipersist.gradle index 8672d6497..9bc16547b 100644 --- a/spring-statemachine-samples/datajpamultipersist/spring-statemachine-samples-datajpamultipersist.gradle +++ b/spring-statemachine-samples/datajpamultipersist/spring-statemachine-samples-datajpamultipersist.gradle @@ -21,6 +21,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-samples/datapersist/spring-statemachine-samples-datapersist.gradle b/spring-statemachine-samples/datapersist/spring-statemachine-samples-datapersist.gradle index c00069440..242a831c7 100644 --- a/spring-statemachine-samples/datapersist/spring-statemachine-samples-datapersist.gradle +++ b/spring-statemachine-samples/datapersist/spring-statemachine-samples-datapersist.gradle @@ -25,6 +25,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-samples/deploy/spring-statemachine-samples-deploy.gradle b/spring-statemachine-samples/deploy/spring-statemachine-samples-deploy.gradle index e4d3f5ea1..a878654e6 100644 --- a/spring-statemachine-samples/deploy/spring-statemachine-samples-deploy.gradle +++ b/spring-statemachine-samples/deploy/spring-statemachine-samples-deploy.gradle @@ -18,6 +18,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-samples/eventservice/spring-statemachine-samples-eventservice.gradle b/spring-statemachine-samples/eventservice/spring-statemachine-samples-eventservice.gradle index 4d7ed57a7..d35086a20 100644 --- a/spring-statemachine-samples/eventservice/spring-statemachine-samples-eventservice.gradle +++ b/spring-statemachine-samples/eventservice/spring-statemachine-samples-eventservice.gradle @@ -20,6 +20,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-samples/monitoring/spring-statemachine-samples-monitoring.gradle b/spring-statemachine-samples/monitoring/spring-statemachine-samples-monitoring.gradle index 00c00f10a..61d592dd8 100644 --- a/spring-statemachine-samples/monitoring/spring-statemachine-samples-monitoring.gradle +++ b/spring-statemachine-samples/monitoring/spring-statemachine-samples-monitoring.gradle @@ -21,6 +21,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-samples/ordershipping/spring-statemachine-samples-ordershipping.gradle b/spring-statemachine-samples/ordershipping/spring-statemachine-samples-ordershipping.gradle index 1ee21293a..81349ba58 100644 --- a/spring-statemachine-samples/ordershipping/spring-statemachine-samples-ordershipping.gradle +++ b/spring-statemachine-samples/ordershipping/spring-statemachine-samples-ordershipping.gradle @@ -18,6 +18,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-samples/persist/spring-statemachine-samples-persist.gradle b/spring-statemachine-samples/persist/spring-statemachine-samples-persist.gradle index 2afc5e8a0..575b40277 100644 --- a/spring-statemachine-samples/persist/spring-statemachine-samples-persist.gradle +++ b/spring-statemachine-samples/persist/spring-statemachine-samples-persist.gradle @@ -19,6 +19,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-samples/showcase/spring-statemachine-samples-showcase.gradle b/spring-statemachine-samples/showcase/spring-statemachine-samples-showcase.gradle index 0eb46c770..25f2bca42 100644 --- a/spring-statemachine-samples/showcase/spring-statemachine-samples-showcase.gradle +++ b/spring-statemachine-samples/showcase/spring-statemachine-samples-showcase.gradle @@ -17,6 +17,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-samples/tasks/spring-statemachine-samples-tasks.gradle b/spring-statemachine-samples/tasks/spring-statemachine-samples-tasks.gradle index cf881a1e8..70ba6fc1e 100644 --- a/spring-statemachine-samples/tasks/spring-statemachine-samples-tasks.gradle +++ b/spring-statemachine-samples/tasks/spring-statemachine-samples-tasks.gradle @@ -17,7 +17,7 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' testImplementation 'io.projectreactor.tools:blockhound' } diff --git a/spring-statemachine-samples/turnstile/spring-statemachine-samples-turnstile.gradle b/spring-statemachine-samples/turnstile/spring-statemachine-samples-turnstile.gradle index cfc807c2b..709b16a1b 100644 --- a/spring-statemachine-samples/turnstile/spring-statemachine-samples-turnstile.gradle +++ b/spring-statemachine-samples/turnstile/spring-statemachine-samples-turnstile.gradle @@ -17,6 +17,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-samples/turnstilereactive/spring-statemachine-samples-turnstilereactive.gradle b/spring-statemachine-samples/turnstilereactive/spring-statemachine-samples-turnstilereactive.gradle index cc18f48ba..cd224e485 100644 --- a/spring-statemachine-samples/turnstilereactive/spring-statemachine-samples-turnstilereactive.gradle +++ b/spring-statemachine-samples/turnstilereactive/spring-statemachine-samples-turnstilereactive.gradle @@ -20,7 +20,7 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' testImplementation 'io.projectreactor.tools:blockhound' } diff --git a/spring-statemachine-samples/washer/spring-statemachine-samples-washer.gradle b/spring-statemachine-samples/washer/spring-statemachine-samples-washer.gradle index 6e47fedcd..51aef656b 100644 --- a/spring-statemachine-samples/washer/spring-statemachine-samples-washer.gradle +++ b/spring-statemachine-samples/washer/spring-statemachine-samples-washer.gradle @@ -17,6 +17,6 @@ dependencies { testImplementation 'org.springframework:spring-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/spring-statemachine-uml/spring-statemachine-uml.gradle b/spring-statemachine-uml/spring-statemachine-uml.gradle index da00a62b4..68f571a47 100644 --- a/spring-statemachine-uml/spring-statemachine-uml.gradle +++ b/spring-statemachine-uml/spring-statemachine-uml.gradle @@ -31,7 +31,7 @@ dependencies { testImplementation 'org.assertj:assertj-core' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.springframework:spring-test' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' testImplementation 'org.awaitility:awaitility' } diff --git a/spring-statemachine-zookeeper/spring-statemachine-zookeeper.gradle b/spring-statemachine-zookeeper/spring-statemachine-zookeeper.gradle index 8a53368bb..804555a35 100644 --- a/spring-statemachine-zookeeper/spring-statemachine-zookeeper.gradle +++ b/spring-statemachine-zookeeper/spring-statemachine-zookeeper.gradle @@ -12,6 +12,6 @@ dependencies { testImplementation (project(':spring-statemachine-test')) testImplementation 'org.apache.curator:curator-test' testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher' } From a707ff00ea733a881af5a13c84888e3880af47d7 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 28 May 2025 22:28:56 +0200 Subject: [PATCH 05/46] Fix transitive dependency to mongodb-driver-sync --- gradle.properties | 1 + .../spring-statemachine-autoconfigure.gradle | 5 ++++- .../spring-statemachine-build-tests.gradle | 5 ++++- .../mongodb/spring-statemachine-data-mongodb.gradle | 5 ++++- spring-statemachine-platform/build.gradle | 1 + .../spring-statemachine-platform.gradle | 1 + spring-statemachine-samples/build.gradle | 3 +++ .../spring-statemachine-samples-datapersist.gradle | 5 ++++- 8 files changed, 22 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 787cd19f3..76e1723ea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,6 +2,7 @@ version=4.0.1-SNAPSHOT springBootVersion=3.5.0-SNAPSHOT jakartaPersistenceVersion=3.1.0 +mongoDriverSyncVersion=5.5.0 kryoVersion=4.0.3 eclipseEmfXmiVersion=2.11.1 eclipseUml2CommonVersion=2.0.0-v20140602-0749 diff --git a/spring-statemachine-autoconfigure/spring-statemachine-autoconfigure.gradle b/spring-statemachine-autoconfigure/spring-statemachine-autoconfigure.gradle index 1dbb54b56..399469be4 100644 --- a/spring-statemachine-autoconfigure/spring-statemachine-autoconfigure.gradle +++ b/spring-statemachine-autoconfigure/spring-statemachine-autoconfigure.gradle @@ -16,10 +16,13 @@ dependencies { optional 'io.micrometer:micrometer-core' optional 'org.springframework.boot:spring-boot-starter-data-jpa' optional 'org.springframework.boot:spring-boot-starter-data-redis' - optional 'org.springframework.boot:spring-boot-starter-data-mongodb' + optional('org.springframework.boot:spring-boot-starter-data-mongodb') { + exclude group: 'org.mongodb', module: 'mongodb-driver-sync' + } testImplementation 'org.springframework.boot:spring-boot-test' testImplementation 'org.junit.jupiter:junit-jupiter-engine' testImplementation 'org.junit.platform:junit-platform-launcher' testImplementation 'org.assertj:assertj-core' testRuntimeOnly 'com.h2database:h2' + testRuntimeOnly 'org.mongodb:mongodb-driver-sync' } diff --git a/spring-statemachine-build-tests/spring-statemachine-build-tests.gradle b/spring-statemachine-build-tests/spring-statemachine-build-tests.gradle index 6b580ad5c..99440020a 100644 --- a/spring-statemachine-build-tests/spring-statemachine-build-tests.gradle +++ b/spring-statemachine-build-tests/spring-statemachine-build-tests.gradle @@ -14,9 +14,12 @@ dependencies { testImplementation(testFixtures(project(":spring-statemachine-core"))) testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.apache.commons:commons-pool2' - testRuntimeOnly 'org.springframework.boot:spring-boot-starter-data-mongodb' + testRuntimeOnly('org.springframework.boot:spring-boot-starter-data-mongodb') { + exclude group: 'org.mongodb', module: 'mongodb-driver-sync' + } testRuntimeOnly 'org.springframework.boot:spring-boot-starter-data-redis' testRuntimeOnly 'redis.clients:jedis' + testRuntimeOnly 'org.mongodb:mongodb-driver-sync' testImplementation 'org.junit.jupiter:junit-jupiter-engine' testImplementation 'org.junit.platform:junit-platform-launcher' testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa' diff --git a/spring-statemachine-data/mongodb/spring-statemachine-data-mongodb.gradle b/spring-statemachine-data/mongodb/spring-statemachine-data-mongodb.gradle index ce90495a0..8227ec296 100644 --- a/spring-statemachine-data/mongodb/spring-statemachine-data-mongodb.gradle +++ b/spring-statemachine-data/mongodb/spring-statemachine-data-mongodb.gradle @@ -16,6 +16,9 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-test-autoconfigure' testImplementation 'org.junit.jupiter:junit-jupiter-engine' testImplementation 'org.junit.platform:junit-platform-launcher' - testRuntimeOnly 'org.springframework.boot:spring-boot-starter-data-mongodb' + testRuntimeOnly('org.springframework.boot:spring-boot-starter-data-mongodb') { + exclude group: 'org.mongodb', module: 'mongodb-driver-sync' + } + testRuntimeOnly 'org.mongodb:mongodb-driver-sync' testRuntimeOnly 'org.springframework.boot:spring-boot-starter-web' } diff --git a/spring-statemachine-platform/build.gradle b/spring-statemachine-platform/build.gradle index 7c8bf1f12..bb7ff8ece 100644 --- a/spring-statemachine-platform/build.gradle +++ b/spring-statemachine-platform/build.gradle @@ -23,5 +23,6 @@ dependencies { api "org.awaitility:awaitility:$awaitilityVersion" api "io.projectreactor.tools:blockhound:$reactorBlockHoundVersion" api "io.projectreactor.tools:blockhound-junit-platform:$reactorBlockHoundVersion" + api "org.mongodb:mongodb-driver-sync:$mongoDriverSyncVersion" } } \ No newline at end of file diff --git a/spring-statemachine-platform/spring-statemachine-platform.gradle b/spring-statemachine-platform/spring-statemachine-platform.gradle index 8b16f180a..66d1f5909 100644 --- a/spring-statemachine-platform/spring-statemachine-platform.gradle +++ b/spring-statemachine-platform/spring-statemachine-platform.gradle @@ -24,5 +24,6 @@ dependencies { api "org.apache.curator:curator-test:$curatorVersion" api "io.projectreactor.tools:blockhound:$reactorBlockHoundVersion" api "io.projectreactor.tools:blockhound-junit-platform:$reactorBlockHoundVersion" + api "org.mongodb:mongodb-driver-sync:$mongoDriverSyncVersion" } } diff --git a/spring-statemachine-samples/build.gradle b/spring-statemachine-samples/build.gradle index c4757e3b9..bd862de03 100644 --- a/spring-statemachine-samples/build.gradle +++ b/spring-statemachine-samples/build.gradle @@ -230,6 +230,9 @@ project('spring-statemachine-samples-datapersist') { api('org.springframework.boot:spring-boot-starter-data-jpa') api('org.springframework.boot:spring-boot-starter-data-redis') api('org.springframework.boot:spring-boot-starter-data-mongodb') + api('org.springframework.boot:spring-boot-starter-data-mongodb') { + exclude group: 'org.mongodb', module: 'mongodb-driver-sync' + } api('org.springframework.boot:spring-boot-devtools') api('com.h2database:h2') testImplementation(testFixtures(project(":spring-statemachine-core"))) diff --git a/spring-statemachine-samples/datapersist/spring-statemachine-samples-datapersist.gradle b/spring-statemachine-samples/datapersist/spring-statemachine-samples-datapersist.gradle index 242a831c7..c6ec6b455 100644 --- a/spring-statemachine-samples/datapersist/spring-statemachine-samples-datapersist.gradle +++ b/spring-statemachine-samples/datapersist/spring-statemachine-samples-datapersist.gradle @@ -15,7 +15,10 @@ dependencies { implementation('org.springframework.boot:spring-boot-starter-thymeleaf') implementation('org.springframework.boot:spring-boot-starter-data-jpa') implementation('org.springframework.boot:spring-boot-starter-data-redis') - implementation('org.springframework.boot:spring-boot-starter-data-mongodb') + implementation('org.springframework.boot:spring-boot-starter-data-mongodb') { + exclude group: 'org.mongodb', module: 'mongodb-driver-sync' + } + testRuntimeOnly 'org.mongodb:mongodb-driver-sync' implementation('org.springframework.boot:spring-boot-devtools') implementation('com.h2database:h2') testImplementation(testFixtures(project(':spring-statemachine-core'))) From 7b19ac1b6b572cdcf3b9be6726067136d0ffe560 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 28 May 2025 22:29:20 +0200 Subject: [PATCH 06/46] Update gradle to version 8.14.1 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c6f003026..942039f27 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 50fca01fcd3a45970178fd1034d145509adc31ee Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 28 May 2025 22:58:23 +0200 Subject: [PATCH 07/46] Update Spring Boot version to 3.5.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 76e1723ea..9c3905d3e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ version=4.0.1-SNAPSHOT -springBootVersion=3.5.0-SNAPSHOT +springBootVersion=3.5.0 jakartaPersistenceVersion=3.1.0 mongoDriverSyncVersion=5.5.0 From 4235dcd8254295db37d9b2d00a4174cce0d33caa Mon Sep 17 00:00:00 2001 From: Matthias Nemeth <43153222+manedev79@users.noreply.github.com> Date: Mon, 18 Dec 2023 09:39:44 +0100 Subject: [PATCH 08/46] Refer to main branch instead of master --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6868a2b1..5b9a76191 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,8 +40,8 @@ All commits must include a Signed-off-by trailer at the end of each commit messa For additional details, please refer to the blog post [Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring). -# Create your branch from master -Create your topic branch to be submitted as a pull request from master. The Spring team will consider your pull request for backporting on a case-by-case basis; you don't need to worry about submitting anything for backporting. +# Create your branch from main +Create your topic branch to be submitted as a pull request from main. The Spring team will consider your pull request for backporting on a case-by-case basis; you don't need to worry about submitting anything for backporting. # Use short branch names Branches used when submitting pull requests should preferably be named according to JIRA issues, e.g. 'SEC-1234'. Otherwise, use succinct, lower-case, dash (-) delimited names, such as 'fix-warnings', 'fix-typo', etc. This is important, because branch names show up in the merge commits that result from accepting pull requests, and should be as expressive and concise as possible. From c07d0bf5c3486b4c4de9ac4b3ca3b35ea7471e39 Mon Sep 17 00:00:00 2001 From: Jesper Josefsson Date: Wed, 25 Jan 2023 15:01:58 +0200 Subject: [PATCH 09/46] Fix typo --- docs/src/reference/asciidoc/recipes.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/reference/asciidoc/recipes.adoc b/docs/src/reference/asciidoc/recipes.adoc index 59f31ca85..8987ea04a 100644 --- a/docs/src/reference/asciidoc/recipes.adoc +++ b/docs/src/reference/asciidoc/recipes.adoc @@ -40,7 +40,7 @@ You can find a sample that shows how to use this recipe at [[statemachine-recipes-tasks]] == Tasks -The tasks recipe is a concept to run DAG (Directed Acrylic Graph) of `Runnable` instances that use +The tasks recipe is a concept to run DAG (Directed Acyclic Graph) of `Runnable` instances that use a state machine. This recipe has been developed from ideas introduced in <> sample. From 3c63147cda94e45beeab06000cd0fd2e92e9ae94 Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Mon, 6 Nov 2023 11:17:51 +0800 Subject: [PATCH 10/46] fix variable name --- .../main/java/demo/datapersist/StateMachineConfig.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-statemachine-samples/datapersist/src/main/java/demo/datapersist/StateMachineConfig.java b/spring-statemachine-samples/datapersist/src/main/java/demo/datapersist/StateMachineConfig.java index 1bb1fc16b..ff6c25c82 100644 --- a/spring-statemachine-samples/datapersist/src/main/java/demo/datapersist/StateMachineConfig.java +++ b/spring-statemachine-samples/datapersist/src/main/java/demo/datapersist/StateMachineConfig.java @@ -60,8 +60,8 @@ public static class MongoPersisterConfig { @Bean public StateMachineRuntimePersister stateMachineRuntimePersister( - MongoDbStateMachineRepository jpaStateMachineRepository) { - return new MongoDbPersistingStateMachineInterceptor<>(jpaStateMachineRepository); + MongoDbStateMachineRepository mongoDbStateMachineRepository) { + return new MongoDbPersistingStateMachineInterceptor<>(mongoDbStateMachineRepository); } } //end::snippetB[] @@ -73,8 +73,8 @@ public static class RedisPersisterConfig { @Bean public StateMachineRuntimePersister stateMachineRuntimePersister( - RedisStateMachineRepository jpaStateMachineRepository) { - return new RedisPersistingStateMachineInterceptor<>(jpaStateMachineRepository); + RedisStateMachineRepository redisStateMachineRepository) { + return new RedisPersistingStateMachineInterceptor<>(redisStateMachineRepository); } } //end::snippetC[] From be2009a068238c09ac59de2f73b78ef40188ba80 Mon Sep 17 00:00:00 2001 From: "Gerlach, Winfried" Date: Wed, 18 Dec 2024 13:10:44 +0100 Subject: [PATCH 11/46] fix typo in recipes.adoc --- docs/src/reference/asciidoc/recipes.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/reference/asciidoc/recipes.adoc b/docs/src/reference/asciidoc/recipes.adoc index 8987ea04a..49b51eb99 100644 --- a/docs/src/reference/asciidoc/recipes.adoc +++ b/docs/src/reference/asciidoc/recipes.adoc @@ -40,7 +40,7 @@ You can find a sample that shows how to use this recipe at [[statemachine-recipes-tasks]] == Tasks -The tasks recipe is a concept to run DAG (Directed Acyclic Graph) of `Runnable` instances that use +The tasks recipe is a concept to run a DAG (Directed Acyclic Graph) of `Runnable` instances that use a state machine. This recipe has been developed from ideas introduced in <> sample. From 7bc67e8e17a51494fb230ac8954a8fce679ae52e Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 28 May 2025 23:54:23 +0200 Subject: [PATCH 12/46] Fix typo in sm-config.adoc Credits to @Jun10ng in PR #1004 --- docs/src/reference/asciidoc/sm-config.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/reference/asciidoc/sm-config.adoc b/docs/src/reference/asciidoc/sm-config.adoc index 62a96b531..e441094db 100644 --- a/docs/src/reference/asciidoc/sm-config.adoc +++ b/docs/src/reference/asciidoc/sm-config.adoc @@ -50,7 +50,7 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetAA] You can also use strings instead of enumerations as states and events by using `StateMachineConfigurerAdapter`, as shown in the next example. Most -of the configuration examples ues enumerations, but, generally speaking, +of the configuration examples use enumerations, but, generally speaking, you can interchange strings and enumerations. ==== From c41cda42e547a39388cf730c7592a8b17dafe4d5 Mon Sep 17 00:00:00 2001 From: Jan Wilhelms Date: Tue, 24 Aug 2021 08:36:29 +0200 Subject: [PATCH 13/46] fixed some typos --- .../asciidoc/appendix-reactormigration-examples.adoc | 2 +- docs/src/reference/asciidoc/appendix-reactormigration.adoc | 6 +++--- docs/src/reference/asciidoc/sm-repository.adoc | 2 +- docs/src/reference/asciidoc/whatsnew.adoc | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/reference/asciidoc/appendix-reactormigration-examples.adoc b/docs/src/reference/asciidoc/appendix-reactormigration-examples.adoc index 1decc9a81..b20820971 100644 --- a/docs/src/reference/asciidoc/appendix-reactormigration-examples.adoc +++ b/docs/src/reference/asciidoc/appendix-reactormigration-examples.adoc @@ -2,4 +2,4 @@ While most of an examples are still same, we've overhauled some of them and created some new: -* Tunrstile Reactive <> +* Turnstile Reactive <> diff --git a/docs/src/reference/asciidoc/appendix-reactormigration.adoc b/docs/src/reference/asciidoc/appendix-reactormigration.adoc index fa0219a8e..5ecb8cfc5 100644 --- a/docs/src/reference/asciidoc/appendix-reactormigration.adoc +++ b/docs/src/reference/asciidoc/appendix-reactormigration.adoc @@ -1,10 +1,10 @@ [appendix] [[appendix-reactormigrationguide]] == Reactor Migration Guide -Main task for a work for `3.x` has been to both internally and externally to move and change +Main task for a work for `3.x` has been to both internally and externally move and change as much as we can from imperative code into a reactive world. This means that some -of a main interfaces has added a new reative methods and most of a internal execution locig -(where applicable) has been moved over to handled by a reactor. Essentially what this means is that thread handling model is considerably different compared to `2.x`. Following chapters +of the main interfaces have added new reative methods and most of a internal execution logic +(where applicable) has been moved over to be handled by a reactor. Essentially what this means is that thread handling model is considerably different compared to `2.x`. Following chapters go throught all these changes. include::appendix-reactormigration-communicating.adoc[] diff --git a/docs/src/reference/asciidoc/sm-repository.adoc b/docs/src/reference/asciidoc/sm-repository.adoc index 7000d642f..c524a6209 100644 --- a/docs/src/reference/asciidoc/sm-repository.adoc +++ b/docs/src/reference/asciidoc/sm-repository.adoc @@ -63,7 +63,7 @@ model regarding used table names. Previously, generated table names always had a prefix of `JPA_REPOSITORY_`, derived from entity class names. As this caused breaking issues with databases imposing restrictions on database object lengths, all entity classes have -spesific definitions to force table names. For example, +specific definitions to force table names. For example, `JPA_REPOSITORY_STATE` is now 'STATE' -- and so on with other ntity classes. diff --git a/docs/src/reference/asciidoc/whatsnew.adoc b/docs/src/reference/asciidoc/whatsnew.adoc index e7280e1af..beb169856 100644 --- a/docs/src/reference/asciidoc/whatsnew.adoc +++ b/docs/src/reference/asciidoc/whatsnew.adoc @@ -73,7 +73,7 @@ in a future releases. [IMPORTANT] ==== Please read an appendix <> carefully as it will steer you -through a process of migrating into `3.x` for cases we're not handling internallyl. +through a process of migrating into `3.x` for cases we're not handling internally. ==== At this point most of a documentation has been changed to showcase reactive interfaces From 88ac89d1eb2f52010b9eb9613d1ac272df689378 Mon Sep 17 00:00:00 2001 From: Eric Haag Date: Mon, 22 Jan 2024 16:10:33 -0600 Subject: [PATCH 14/46] Update Revved up by Develocity badge --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index a1da61e2e..cd3a7f6f2 100644 --- a/README.adoc +++ b/README.adoc @@ -1,6 +1,6 @@ = Spring Statemachine -image:https://github.com/spring-projects/spring-statemachine/workflows/CI/badge.svg[link="https://github.com/spring-projects/spring-statemachine/actions"] (GitHub default) image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?&search.rootProjectNames=spring-statemachine"] +image:https://github.com/spring-projects/spring-statemachine/workflows/CI/badge.svg[link="https://github.com/spring-projects/spring-statemachine/actions"] (GitHub default) image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?&search.rootProjectNames=spring-statemachine"] The Spring Statemachine project aims to provide a common infrastructure to work with state machine concepts in Spring applications. From f31d2b1a77753afb2121f5d14942293a47745d70 Mon Sep 17 00:00:00 2001 From: Matias Rodal Date: Tue, 10 Sep 2024 09:51:16 -0300 Subject: [PATCH 15/46] Fix https://github.com/spring-projects/spring-statemachine/issues/1162 --- .../statemachine/support/ReactiveStateMachineExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ReactiveStateMachineExecutor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ReactiveStateMachineExecutor.java index 26f063bf2..93811fa79 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ReactiveStateMachineExecutor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ReactiveStateMachineExecutor.java @@ -125,7 +125,7 @@ protected Mono doPreStartReactively() { Mono mono = startTriggers(); if (triggerDisposable == null) { - triggerDisposable = triggerFlux.subscribe(); + triggerDisposable = triggerFlux.contextCapture().subscribe(); } if (!initialHandled.getAndSet(true)) { From bf909f63b14af6d8c2b5ff37b7b2b90109c90321 Mon Sep 17 00:00:00 2001 From: eagletalon139 Date: Thu, 12 Aug 2021 07:31:02 -0500 Subject: [PATCH 16/46] Update sm-examples-turnstilereactive.adoc --- docs/src/reference/asciidoc/sm-examples-turnstilereactive.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/reference/asciidoc/sm-examples-turnstilereactive.adoc b/docs/src/reference/asciidoc/sm-examples-turnstilereactive.adoc index 8d8f2fcf6..98191256f 100644 --- a/docs/src/reference/asciidoc/sm-examples-turnstilereactive.adoc +++ b/docs/src/reference/asciidoc/sm-examples-turnstilereactive.adoc @@ -1,7 +1,7 @@ [[statemachine-examples-turnstilereactive]] == Turnstile Reactive -Turnstile reactive is an enhacement to <> sample using +Turnstile reactive is an enhancement to <> sample using same _StateMachine_ concept and adding a reactive web layer communicating reactively with a _StateMachine_ reactive interfaces. From e3e11903ce4856a1f7ec5e6fb40bb7b1e630b9ec Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 27 Jun 2025 10:26:12 +0200 Subject: [PATCH 17/46] Upgrade Spring Boot to version 3.5.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9c3905d3e..4f98ffe0b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ version=4.0.1-SNAPSHOT -springBootVersion=3.5.0 +springBootVersion=3.5.1 jakartaPersistenceVersion=3.1.0 mongoDriverSyncVersion=5.5.0 From ac5aa3313c29b5cd4ad3adc3334a87574fccfa9d Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 27 Jun 2025 11:04:12 +0200 Subject: [PATCH 18/46] Update CI workflow --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59cc9d218..276a83572 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4.2.2 - name: Setup JDK - uses: actions/setup-java@v4.7.0 + uses: actions/setup-java@v4.7.1 with: distribution: adopt java-version: 17 @@ -40,7 +40,7 @@ jobs: */*/build/reports - name: Upload Build Logs if: ${{ failure() }} - uses: actions/upload-artifact@v4.6.0 + uses: actions/upload-artifact@v4.6.2 with: name: buildlogs path: build/buildlogs.tar.gz @@ -51,14 +51,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 - - uses: actions/setup-java@v4.7.0 + - uses: actions/setup-java@v4.7.1 with: distribution: adopt java-version: 17 cache: gradle - - uses: jfrog/setup-jfrog-cli@v4.5.6 + - uses: jfrog/setup-jfrog-cli@v4.5.11 with: - version: 2.73.2 + version: 2.77.0 env: JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} @@ -68,14 +68,14 @@ jobs: # gets used from action defaults. - name: Configure JFrog Cli env: - JFROG_CLI_SERVER_ID: "repo.spring.io" + JFROG_CLI_SERVER_ID: "https://repo.spring.io" run: | jf gradlec \ --use-wrapper \ --uses-plugin \ --deploy-ivy-desc=false \ - --server-id-resolve=repo.spring.io \ - --server-id-deploy=repo.spring.io \ + --server-id-resolve=https://repo.spring.io \ + --server-id-deploy=https://repo.spring.io \ --repo-resolve=snapshot \ --repo-deploy=snapshot echo JFROG_CLI_BUILD_NAME=spring-statemachine-main >> $GITHUB_ENV From b0b6995fb56fb75238036106acfbf3cbae830b2f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 27 Jun 2025 11:39:16 +0200 Subject: [PATCH 19/46] Replace JFrog CLI usage with Spring IO actions --- .github/workflows/ci.yml | 72 ++++++++++++---------------------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 276a83572..273e1c8c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,18 +16,13 @@ jobs: steps: - uses: actions/checkout@v4.2.2 - - name: Setup JDK - uses: actions/setup-java@v4.7.1 + - name: Spring Gradle Build Action + uses: spring-io/spring-gradle-build-action@v2 with: - distribution: adopt - java-version: 17 - cache: gradle + java-version: '17' + distribution: 'temurin' - name: Build with Gradle run: ./gradlew build - env: - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - name: Tar Build Logs if: ${{ failure() }} run: | @@ -51,47 +46,22 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 - - uses: actions/setup-java@v4.7.1 + - name: Spring Gradle Build Action + uses: spring-io/spring-gradle-build-action@v2 with: - distribution: adopt - java-version: 17 - cache: gradle - - uses: jfrog/setup-jfrog-cli@v4.5.11 - with: - version: 2.77.0 - env: - JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - - # setup non persistent config for jfrog gradle to use server defined in - # secret which points via virtual repo into spring local repo. - # modify default build name and number as we don't like how those - # gets used from action defaults. - - name: Configure JFrog Cli - env: - JFROG_CLI_SERVER_ID: "https://repo.spring.io" - run: | - jf gradlec \ - --use-wrapper \ - --uses-plugin \ - --deploy-ivy-desc=false \ - --server-id-resolve=https://repo.spring.io \ - --server-id-deploy=https://repo.spring.io \ - --repo-resolve=snapshot \ - --repo-deploy=snapshot - echo JFROG_CLI_BUILD_NAME=spring-statemachine-main >> $GITHUB_ENV - echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV + java-version: '17' + distribution: 'temurin' + - name: Build with Gradle + run: ./gradlew clean build -x test - # as we already tested just build and publish - - name: Build and Publish Snapshot - run: | - jf gradle clean build -x test artifactoryPublish - env: - ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }} - ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSPHRASE }} - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - # publish build info so that we can see it in artifactory "builds" - - name: Publish Build Info - run: | - jf rt build-publish + - name: Deploy + uses: spring-io/artifactory-deploy-action@v0.0.2 + with: + uri: 'https://repo.spring.io' + username: ${{ secrets.ARTIFACTORY_USERNAME }} + password: ${{ secrets.ARTIFACTORY_PASSWORD }} + build-name: 'spring-statemachine-main' + repository: 'libs-snapshot-local' + folder: 'deployment-repository' + signing-key: ${{ secrets.GPG_PRIVATE_KEY }} + signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} From 62c6a72c255836a9fd4436c666355ec5e8c36c72 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 27 Jun 2025 18:29:51 +0200 Subject: [PATCH 20/46] Upgrade Spring Boot to version 3.5.3 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4f98ffe0b..cfe86b24a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ version=4.0.1-SNAPSHOT -springBootVersion=3.5.1 +springBootVersion=3.5.3 jakartaPersistenceVersion=3.1.0 mongoDriverSyncVersion=5.5.0 From 3034d03409015f77594b3e9f45de08872680ff95 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 27 Jun 2025 18:31:03 +0200 Subject: [PATCH 21/46] Update maven publishing plugin --- .github/workflows/ci.yml | 2 +- build.gradle | 1 + publish-maven.gradle | 60 ---------------------------------------- 3 files changed, 2 insertions(+), 61 deletions(-) delete mode 100644 publish-maven.gradle diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 273e1c8c0..0a9aeba58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: java-version: '17' distribution: 'temurin' - name: Build with Gradle - run: ./gradlew clean build -x test + run: ./gradlew -Dmaven.repo.local=$(pwd)/deployment-repository -x test clean build publishToMavenLocal - name: Deploy uses: spring-io/artifactory-deploy-action@v0.0.2 diff --git a/build.gradle b/build.gradle index b0178e5c3..fb499bf40 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id "base" id 'org.springframework.statemachine.root' + id 'maven-publish' } description = 'Spring Statemachine' diff --git a/publish-maven.gradle b/publish-maven.gradle deleted file mode 100644 index 902e2bf7b..000000000 --- a/publish-maven.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'maven' - -ext.optionalDeps = [] -ext.providedDeps = [] - -ext.optional = { optionalDeps << it } -ext.provided = { providedDeps << it } - -install { - repositories.mavenInstaller { - customizePom(pom, project) - } -} - -def customizePom(pom, gradleProject) { - pom.whenConfigured { generatedPom -> - // respect 'optional' and 'provided' dependencies - gradleProject.optionalDeps.each { dep -> - generatedPom.dependencies.findAll { it.artifactId == dep.name }*.optional = true - } - gradleProject.providedDeps.each { dep -> - generatedPom.dependencies.findAll { it.artifactId == dep.name }*.scope = 'provided' - } - - // eliminate test-scoped dependencies (no need in maven central poms) - generatedPom.dependencies.removeAll { dep -> - dep.scope == 'test' - } - - // add all items necessary for maven central publication - generatedPom.project { - name = gradleProject.description - description = gradleProject.description - url = 'https://github.com/spring-projects/spring-statemachine' - organization { - name = 'SpringSource' - url = 'https://spring.io/spring-statemachine' - } - licenses { - license { - name 'The Apache Software License, Version 2.0' - url 'https://www.apache.org/licenses/LICENSE-2.0.txt' - distribution 'repo' - } - } - scm { - url = 'https://github.com/spring-projects/spring-statemachine' - connection = 'scm:git:git://github.com/spring-projects/spring-statemachine' - developerConnection = 'scm:git:git://github.com/spring-projects/spring-statemachine' - } - developers { - developer { - id = 'jvalkeal' - name = 'Janne Valkealahti' - email = 'janne.valkealahti@gmail.com' - } - } - } - } -} From 1d7477a7536889a476cc7b6d67dadaacfaa34989 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 28 Jun 2025 11:04:52 +0200 Subject: [PATCH 22/46] Exclude docs from build --- settings.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/settings.gradle b/settings.gradle index e1bba595f..1cca403cd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -76,8 +76,6 @@ include 'spring-statemachine-data:jpa' include 'spring-statemachine-data:redis' include 'spring-statemachine-data:mongodb' -include 'docs' - rootProject.children.each { project -> if (project.name == 'spring-statemachine-data') { project.name = 'spring-statemachine-data-common' From 0e9044e76736930d096a835d8375bf5328bbc16e Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sun, 29 Jun 2025 08:14:51 +0200 Subject: [PATCH 23/46] Add artifactory staging workflow --- .github/workflows/stage-release.yml | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/stage-release.yml diff --git a/.github/workflows/stage-release.yml b/.github/workflows/stage-release.yml new file mode 100644 index 000000000..e63dfbe37 --- /dev/null +++ b/.github/workflows/stage-release.yml @@ -0,0 +1,37 @@ +name: Stage Release in Artifactory + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: "Version to stage, e.g. 4.0.1" + required: true + +jobs: + + stage-release: + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4.2.2 + + - name: Spring Gradle Build Action + uses: spring-io/spring-gradle-build-action@v2 + with: + java-version: '17' + distribution: 'temurin' + + - name: Build with Gradle + run: ./gradlew -Pversion=${{ github.event.inputs.releaseVersion }} -Dmaven.repo.local=$(pwd)/deployment-repository -x test build publishToMavenLocal + + - name: Deploy to Artifactory + uses: spring-io/artifactory-deploy-action@v0.0.2 + with: + uri: 'https://repo.spring.io' + username: ${{ secrets.ARTIFACTORY_USERNAME }} + password: ${{ secrets.ARTIFACTORY_PASSWORD }} + build-name: 'spring-statemachine-${{ github.event.inputs.releaseVersion }}' + repository: 'libs-staging-local' + folder: 'deployment-repository' + signing-key: ${{ secrets.GPG_PRIVATE_KEY }} + signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} \ No newline at end of file From 06e16babf0221a3b2f725647c4075c8421d16f33 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sun, 29 Jun 2025 09:42:19 +0200 Subject: [PATCH 24/46] Update release workflow --- .github/workflows/release-ga.yml | 224 +++---------------------------- 1 file changed, 22 insertions(+), 202 deletions(-) diff --git a/.github/workflows/release-ga.yml b/.github/workflows/release-ga.yml index 839bebbed..cbe64f02f 100644 --- a/.github/workflows/release-ga.yml +++ b/.github/workflows/release-ga.yml @@ -1,210 +1,30 @@ -# full release workflow which does a staging build, github tagging, -# promotion in artifactory and release into central. -name: Release GA +name: Release to Maven Central on: workflow_dispatch: + inputs: + buildName: + description: "Artifactory build name" + required: true + buildNumber: + description: "Artifactory build number" + required: true -# there's 3 jobs, staging, promote and central. -# promote waits staging and manual approval and -# central waits promote and manual approval. jobs: - # build and release to staging repo. - # stash artifactory build id so that promote and central - # jobs can work on it. - staging: + release: runs-on: ubuntu-latest - outputs: - project-version: ${{ steps.output.outputs.project-version }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: 17 - - uses: jfrog/setup-jfrog-cli@v4 - with: - version: 2.52.10 - env: - JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - # prepare env for cli to get a staging build working - - name: Configure JFrog Cli - run: | - jf gradlec \ - --use-wrapper \ - --deploy-ivy-desc=false \ - --server-id-resolve=repo.spring.io \ - --server-id-deploy=repo.spring.io \ - --repo-resolve=libs-release \ - --repo-deploy=libs-staging-local - echo JFROG_CLI_BUILD_NAME=spring-statemachine-main-release >> $GITHUB_ENV - echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV - # switch from snapshot to a release version and extract project - # version to get used with tagging - - name: Configure Release Version - run: | - jf gradle releaseVersion - echo PROJECT_VERSION=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}') >> $GITHUB_ENV - env: - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - # build and publish to staging repo. - # we've allready tested with snapshots so no need to test - # with a release build as we are not a release train. - - name: Build and Publish - run: | - jf gradle clean build artifactoryPublish - jf rt build-publish - env: - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - # we've now done a release build, branch and tag it in github - - name: Tag Release - uses: jvalkeal/build-zoo-handler@v0.0.4 - with: - tag-release-branch: ${{ env.PROJECT_VERSION }} - tag-release-tag: ${{ env.PROJECT_VERSION }} - tag-release-tag-prefix: v - - name: Output - id: output - env: - PROJECT_VERSION: ${{ env.PROJECT_VERSION }} - run: | - echo "::set-output name=project-version::$PROJECT_VERSION" - - # wait manual approval. - # promote build from staging to releases - promote: - runs-on: ubuntu-latest - needs: staging - environment: promote - steps: - # need repo to push release branch and a tag - - uses: actions/checkout@v2 - - uses: jfrog/setup-jfrog-cli@v4 - with: - version: 2.52.10 - env: - JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - # prepare env for cli to promote - - name: Configure JFrog Cli - run: | - jf gradlec \ - --use-wrapper \ - --deploy-ivy-desc=false \ - --server-id-resolve=repo.spring.io \ - --server-id-deploy=repo.spring.io \ - --repo-resolve=libs-release \ - --repo-deploy=libs-staging-local - echo JFROG_CLI_BUILD_NAME=spring-statemachine-main-release >> $GITHUB_ENV - echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV - # promoting build from staging repo into release - - name: Promote Build - run: | - jf rt build-promote libs-release-local - # we've promoted so change repo to next dev version - - name: Switch to Next Dev Version - run: | - jf gradle nextVersion - env: - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - - uses: jvalkeal/build-zoo-handler@v0.0.4 - with: - commit-changes-branch: main - commit-changes-message: Next development version - - # gh release before central - ghrelease: - runs-on: ubuntu-latest - needs: [staging, promote] - steps: - - uses: actions/checkout@v2 - - name: Install Tooling - run: | - curl -sSL https://github.com/cbroglie/mustache/releases/download/v1.2.2/mustache_1.2.2_linux_amd64.tar.gz | sudo tar -C /usr/local/bin/ --no-same-owner -xzv mustache - - name: GitHub Release - env: - PROJECT_VERSION: ${{needs.staging.outputs.project-version}} - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - RELEASE_NOTES_FILE: ${{runner.temp}}/release_notes.md5 - RELEASE_NOTES_DATA: ${{runner.temp}}/release_notes_data.json - RELEASE_NOTES_HEADERS: ${{runner.temp}}/release_notes_headers.json - RELEASE_NOTES_FOOTERS: ${{runner.temp}}/release_notes_footers.json - RELEASE_NOTES_ISSUES: ${{runner.temp}}/release_notes_issues.json - run: | - gh issue list \ - --repo spring-projects/spring-statemachine \ - --milestone $PROJECT_VERSION \ - --label automation/rlnotes-header \ - --state all --json title,body \ - --jq '{headers:map(.),headerslength:(length)}' \ - > $RELEASE_NOTES_HEADERS - gh issue list \ - --repo spring-projects/spring-statemachine \ - --milestone $PROJECT_VERSION \ - --label automation/rlnotes-footer \ - --state all --json title,body \ - --jq '{footers:map(.),footerslength:(length)}' \ - > $RELEASE_NOTES_FOOTERS - gh issue list \ - --repo spring-projects/spring-statemachine \ - --milestone $PROJECT_VERSION \ - --state all --json number,title,labels \ - --jq '{issues:map(select((.labels | length == 0) or (any(.labels[].name; startswith("automation/rlnotes")|not))))}' \ - > $RELEASE_NOTES_ISSUES - jq -s '{issues:(.[0].issues),headers:(.[1].headers),headerslength:(.[1].headerslength),footers:(.[2].footers), footerslength:(.[2].footerslength)}' \ - $RELEASE_NOTES_ISSUES \ - $RELEASE_NOTES_HEADERS \ - $RELEASE_NOTES_FOOTERS \ - > $RELEASE_NOTES_DATA - mustache $RELEASE_NOTES_DATA .github/rlnotes.mustache > $RELEASE_NOTES_FILE - gh release create v$PROJECT_VERSION \ - --draft \ - --title "$PROJECT_VERSION" \ - --notes-file $RELEASE_NOTES_FILE - - # wait manual approval. - # pull released artifacts from repo and do a dance with central sync where we - # create checksum and signature files, create staging repo and upload - # files into it, and then finally close and release that repo. - central: - runs-on: ubuntu-latest - needs: ghrelease - environment: central - steps: - # need repo for spec file - - uses: actions/checkout@v2 - - uses: jfrog/setup-jfrog-cli@v4 - with: - version: 2.52.10 - env: - JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - # prepare env for cli do download released files - - name: Configure JFrog Cli - run: | - echo JFROG_CLI_BUILD_NAME=spring-statemachine-main-release >> $GITHUB_ENV - echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV - # download released files for a build. - # spec file defines files we actually need for central - # as release in artifactory contains some test and samples modules - # which we don't want in central. - - name: Download Release Files - run: | - jf rt download \ - --spec .github/release-files-spec.json \ - --spec-vars "buildname=$JFROG_CLI_BUILD_NAME;buildnumber=$JFROG_CLI_BUILD_NUMBER" - # last step, sync to central. - - uses: spring-io/nexus-sync-action@main - with: - username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }} - password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }} - staging-profile-name: ${{ secrets.OSSRH_STAGING_PROFILE_NAME }} - create: true - upload: true - close: true - release: true - generate-checksums: true + - uses: actions/checkout@v4.2.2 + - name: Set Up JFrog CLI + uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1 + env: + JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} + - name: Download Release Artifacts + shell: bash + run: jf rt download --spec .github/release-files-spec.json --spec-vars 'buildname=${{ github.event.inputs.buildName }};buildnumber=${{ github.event.inputs.buildNumber }}' + - name: Sync to Maven Central + uses: spring-io/central-publish-action@0cdd90d12e6876341e82860d951e1bcddc1e51b6 # v0.2.0 + with: + token: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} + token-name: ${{ secrets.CENTRAL_TOKEN_USERNAME }} From 2a365a865c0bb3b17fdcfc7a0537c9f237bb076c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sun, 29 Jun 2025 10:25:41 +0200 Subject: [PATCH 25/46] Increase timeout in release workflow --- .github/workflows/release-ga.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-ga.yml b/.github/workflows/release-ga.yml index cbe64f02f..f76662220 100644 --- a/.github/workflows/release-ga.yml +++ b/.github/workflows/release-ga.yml @@ -28,3 +28,4 @@ jobs: with: token: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} token-name: ${{ secrets.CENTRAL_TOKEN_USERNAME }} + timeout: 60m From 665f551729a366b83e227f66a4ab6f2bcc1a8242 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sun, 29 Jun 2025 10:37:29 +0200 Subject: [PATCH 26/46] Release version 4.0.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cfe86b24a..f0d59e086 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=4.0.1-SNAPSHOT +version=4.0.1 springBootVersion=3.5.3 jakartaPersistenceVersion=3.1.0 From 0a10a373f3f6781af9ba01f21b34e61ed2035dc5 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sun, 29 Jun 2025 10:38:28 +0200 Subject: [PATCH 27/46] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index f0d59e086..18767a4bd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=4.0.1 +version=4.0.2-SNAPSHOT springBootVersion=3.5.3 jakartaPersistenceVersion=3.1.0 From 92bfea996cd73ec4b3e41af69f9fd56dd791940c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sun, 29 Jun 2025 11:07:43 +0200 Subject: [PATCH 28/46] Update Spring Boot version to latest snapshot --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 18767a4bd..26958760d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ version=4.0.2-SNAPSHOT -springBootVersion=3.5.3 +springBootVersion=3.5.4-SNAPSHOT jakartaPersistenceVersion=3.1.0 mongoDriverSyncVersion=5.5.0 From 81771026c6fe64174b58008758c4ca359674d0f6 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sun, 29 Jun 2025 11:51:54 +0200 Subject: [PATCH 29/46] Put back docs as part of the build --- settings.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/settings.gradle b/settings.gradle index 1cca403cd..e1bba595f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -76,6 +76,8 @@ include 'spring-statemachine-data:jpa' include 'spring-statemachine-data:redis' include 'spring-statemachine-data:mongodb' +include 'docs' + rootProject.children.each { project -> if (project.name == 'spring-statemachine-data') { project.name = 'spring-statemachine-data-common' From f9311f16ade02d402ff7e0d24a39dd877640b7a1 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 28 Aug 2025 11:34:12 +0200 Subject: [PATCH 30/46] Update README to reflect maintenance mode status Resolves #1197 Signed-off-by: Mahmoud Ben Hassine --- README.adoc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.adoc b/README.adoc index cd3a7f6f2..edb284ae8 100644 --- a/README.adoc +++ b/README.adoc @@ -1,3 +1,9 @@ += Project status: maintenance mode + +This project is not actively maintained anymore. Please refer to the https://spring.io/blog/2025/04/21/spring-cloud-data-flow-commercial[announcement]. + +Spring Statemachine 4.0.x will be maintained with patch releases until it is out of OSS support, please check the https://spring.io/projects/spring-statemachine#support[support page] for more details about supported versions. + = Spring Statemachine image:https://github.com/spring-projects/spring-statemachine/workflows/CI/badge.svg[link="https://github.com/spring-projects/spring-statemachine/actions"] (GitHub default) image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?&search.rootProjectNames=spring-statemachine"] From 4b29c2b83e09690cea98849cf7e2de00a7bf2df0 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 28 Aug 2025 11:38:31 +0200 Subject: [PATCH 31/46] Upgrade gradle version to 8.14.3 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 942039f27..aa02b02fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From d9a84e268700dd52ca6ef61e05cc14ce6a4fe648 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 28 Aug 2025 12:46:54 +0200 Subject: [PATCH 32/46] Upgrade Github workflows --- .github/workflows/central-sync-close.yml | 22 --- .github/workflows/central-sync-create.yml | 54 ------- .github/workflows/central-sync-release.yml | 22 --- .github/workflows/ci-boot.yml | 49 ------- .github/workflows/ci.yml | 2 - .github/workflows/deploy-docs.yml | 66 +++++++++ .github/workflows/pr.yml | 20 ++- .github/workflows/release-milestone.yml | 160 --------------------- 8 files changed, 74 insertions(+), 321 deletions(-) delete mode 100644 .github/workflows/central-sync-close.yml delete mode 100644 .github/workflows/central-sync-create.yml delete mode 100644 .github/workflows/central-sync-release.yml delete mode 100644 .github/workflows/ci-boot.yml create mode 100644 .github/workflows/deploy-docs.yml delete mode 100644 .github/workflows/release-milestone.yml diff --git a/.github/workflows/central-sync-close.yml b/.github/workflows/central-sync-close.yml deleted file mode 100644 index f42f09d26..000000000 --- a/.github/workflows/central-sync-close.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Central Sync Close - -on: - workflow_dispatch: - inputs: - stagedRepositoryId: - description: "Staged repository id" - required: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - # Request close promotion repo - - uses: spring-io/nexus-sync-action@main - with: - username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }} - password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }} - staging-profile-name: ${{ secrets.OSSRH_STAGING_PROFILE_NAME }} - staging-repo-id: ${{ github.event.inputs.stagedRepositoryId }} - close: true diff --git a/.github/workflows/central-sync-create.yml b/.github/workflows/central-sync-create.yml deleted file mode 100644 index 3bcb93c73..000000000 --- a/.github/workflows/central-sync-create.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Central Sync Create - -on: - workflow_dispatch: - inputs: - buildName: - description: "Artifactory build name" - required: true - buildNumber: - description: "Artifactory build number" - required: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - # to get spec file in .github - - uses: actions/checkout@v2 - - # Setup jfrog cli - - uses: jfrog/setup-jfrog-cli@v4 - with: - version: 2.52.10 - env: - JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - - # Extract build id from input - - name: Extract Build Id - run: | - echo JFROG_CLI_BUILD_NAME=${{ github.event.inputs.buildName }} >> $GITHUB_ENV - echo JFROG_CLI_BUILD_NUMBER=${{ github.event.inputs.buildNumber }} >> $GITHUB_ENV - - # Download released files - - name: Download Release Files - run: | - jf rt download \ - --spec .github/release-files-spec.json \ - --spec-vars "buildname=$JFROG_CLI_BUILD_NAME;buildnumber=$JFROG_CLI_BUILD_NUMBER" - - # Create checksums, signatures and create staging repo on central and upload - - uses: spring-io/nexus-sync-action@main - id: nexus - with: - username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }} - password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }} - staging-profile-name: ${{ secrets.OSSRH_STAGING_PROFILE_NAME }} - create: true - upload: true - generate-checksums: true - - # Print staging repo id - - name: Print Staging Repo Id - run: echo ${{ steps.nexus.outputs.staged-repository-id }} diff --git a/.github/workflows/central-sync-release.yml b/.github/workflows/central-sync-release.yml deleted file mode 100644 index bcb25161e..000000000 --- a/.github/workflows/central-sync-release.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Central Sync Release - -on: - workflow_dispatch: - inputs: - stagedRepositoryId: - description: "Staged repository id" - required: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - # Request release promotion repo - - uses: spring-io/nexus-sync-action@main - with: - username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }} - password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }} - staging-profile-name: ${{ secrets.OSSRH_STAGING_PROFILE_NAME }} - staging-repo-id: ${{ github.event.inputs.stagedRepositoryId }} - release: true diff --git a/.github/workflows/ci-boot.yml b/.github/workflows/ci-boot.yml deleted file mode 100644 index 4008ea580..000000000 --- a/.github/workflows/ci-boot.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: CI Boot - -on: - workflow_dispatch: - schedule: - - cron: '0 0 * * 1' - -jobs: - - # build and test, then upload logs if failure - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - java: [ 17 ] - boot: [ 3.3.9, 3.4.3 ] - fail-fast: false - - steps: - - uses: actions/checkout@v4 - - name: Setup JDK - uses: actions/setup-java@v4 - with: - distribution: adopt - java-version: ${{ matrix.java }} - cache: gradle - - name: Build boot ${{ matrix.boot }} java ${{ matrix.java }} - run: ./gradlew clean build -PspringBootVersion=${{ matrix.boot }} - env: - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - - name: Tar Build Logs - if: ${{ failure() }} - run: | - mkdir -p build - tar \ - -zc \ - --ignore-failed-read \ - --file build/buildlogs.tar.gz \ - */build/reports \ - */*/build/reports - - name: Upload Build Logs - if: ${{ failure() }} - uses: actions/upload-artifact@v4.4.3 - with: - name: buildlogs-${{ matrix.boot }}-${{ matrix.java }} - path: build/buildlogs.tar.gz diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a9aeba58..75909cd06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,5 +63,3 @@ jobs: build-name: 'spring-statemachine-main' repository: 'libs-snapshot-local' folder: 'deployment-repository' - signing-key: ${{ secrets.GPG_PRIVATE_KEY }} - signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 000000000..2a4f05ca0 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,66 @@ +name: Deploy Docs + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: "Release version (4.0.x)" + required: true + +jobs: + + publish-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + - name: Spring Gradle Build Action + uses: spring-io/spring-gradle-build-action@v2 + with: + java-version: '17' + distribution: 'temurin' + + - name: Capture project version + run: echo PROJECT_VERSION=${{ github.event.inputs.releaseVersion }} >> $GITHUB_ENV + + - name: Build with Gradle + run: ./gradlew -Pversion=$PROJECT_VERSION -x test build + + - name: Setup SSH key + if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} + env: + DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} + DOCS_SSH_HOST_KEY: ${{ secrets.DOCS_SSH_HOST_KEY }} + run: | + mkdir "$HOME/.ssh" + echo "$DOCS_SSH_KEY" > "$HOME/.ssh/key" + chmod 600 "$HOME/.ssh/key" + echo "$DOCS_SSH_HOST_KEY" > "$HOME/.ssh/known_hosts" + + - name: Prepare release directory + if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} + env: + DOCS_HOST: ${{ secrets.DOCS_HOST }} + DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} + run: | + DOCS_PATH="/opt/www/domains/spring.io/docs/htdocs/spring-statemachine/docs" + ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST "cd $DOCS_PATH && mkdir -p $PROJECT_VERSION" + + - name: Deploy Java docs + if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} + env: + DOCS_HOST: ${{ secrets.DOCS_HOST }} + DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} + working-directory: build + run: | + DOCS_PATH="/opt/www/domains/spring.io/docs/htdocs/spring-statemachine/docs" + scp -i $HOME/.ssh/key -r api $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$PROJECT_VERSION + + - name: Deploy reference docs + if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} + env: + DOCS_HOST: ${{ secrets.DOCS_HOST }} + DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} + working-directory: docs/build/docs + run: | + DOCS_PATH="/opt/www/domains/spring.io/docs/htdocs/spring-statemachine/docs" + scp -i $HOME/.ssh/key -r asciidoc $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$PROJECT_VERSION/reference diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ccab02e91..973fde2a4 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -9,21 +9,17 @@ jobs: # build and test, then upload logs if failure build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - java: [ 17 ] - fail-fast: false + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup JDK - uses: actions/setup-java@v1 + - uses: actions/checkout@v4.2.2 + - name: Spring Gradle Build Action + uses: spring-io/spring-gradle-build-action@v2 with: - java-version: ${{ matrix.java }} + java-version: '17' + distribution: 'temurin' - name: Build with Gradle - run: ./gradlew clean build + run: ./gradlew build - name: Tar Build Logs if: ${{ failure() }} run: | @@ -36,7 +32,7 @@ jobs: */*/build/reports - name: Upload Build Logs if: ${{ failure() }} - uses: actions/upload-artifact@v4.4.3 + uses: actions/upload-artifact@v4.6.2 with: name: buildlogs path: build/buildlogs.tar.gz diff --git a/.github/workflows/release-milestone.yml b/.github/workflows/release-milestone.yml deleted file mode 100644 index 0227eb1de..000000000 --- a/.github/workflows/release-milestone.yml +++ /dev/null @@ -1,160 +0,0 @@ -# full release workflow which does a staging build, github tagging, -# promotion in artifactory -name: Release Milestone - -on: - workflow_dispatch: - inputs: - milestone: - description: 'Milestone version like, M1 or RC1, etc' - required: true - -# there's 3 jobs, staging, promote and central. -# promote waits staging and manual approval and -# central waits promote and manual approval. -jobs: - - # build and release to staging repo. - # stash artifactory build id so that promote and central - # jobs can work on it. - staging: - runs-on: ubuntu-latest - outputs: - project-version: ${{ steps.output.outputs.project-version }} - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: 17 - - uses: jfrog/setup-jfrog-cli@v4 - with: - version: 2.52.10 - env: - JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - # prepare env for cli to get a staging build working - - name: Configure JFrog Cli - run: | - jf gradlec \ - --use-wrapper \ - --deploy-ivy-desc=false \ - --server-id-resolve=repo.spring.io \ - --server-id-deploy=repo.spring.io \ - --repo-resolve=libs-milestone \ - --repo-deploy=libs-staging-local - echo JFROG_CLI_BUILD_NAME=spring-statemachine-main-milestone >> $GITHUB_ENV - echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV - # switch from snapshot to a release version and extract project - # version to get used with tagging - - name: Configure Milestone Version - run: | - jf gradle milestoneVersion -PstatemachineMilestone=${{ github.event.inputs.milestone }} - echo PROJECT_VERSION=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}') >> $GITHUB_ENV - env: - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - # build and publish to staging repo. - # we've allready tested with snapshots so no need to test - # with a release build as we are not a release train. - - name: Build and Publish - run: | - jf gradle clean build artifactoryPublish - jf rt build-publish - env: - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - # we've now done a release build, branch and tag it in github - - name: Tag Release - uses: jvalkeal/build-zoo-handler@v0.0.4 - with: - tag-release-branch: ${{ env.PROJECT_VERSION }} - tag-release-tag: ${{ env.PROJECT_VERSION }} - tag-release-tag-prefix: v - - name: Output - id: output - env: - PROJECT_VERSION: ${{ env.PROJECT_VERSION }} - run: | - echo "::set-output name=project-version::$PROJECT_VERSION" - - # wait manual approval. - # promote build from staging to milestones - promote: - runs-on: ubuntu-latest - needs: staging - environment: promote - steps: - # need repo to push release branch and a tag - - uses: actions/checkout@v2 - - uses: jfrog/setup-jfrog-cli@v4 - with: - version: 2.52.10 - env: - JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - # prepare env for cli to promote - - name: Configure JFrog Cli - run: | - jf gradlec \ - --use-wrapper \ - --deploy-ivy-desc=false \ - --server-id-resolve=repo.spring.io \ - --server-id-deploy=repo.spring.io \ - --repo-resolve=libs-milestone \ - --repo-deploy=libs-staging-local - echo JFROG_CLI_BUILD_NAME=spring-statemachine-main-milestone >> $GITHUB_ENV - echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV - # promoting build from staging repo into release - - name: Promote Build - run: | - jf rt build-promote libs-milestone-local - - ghrelease: - runs-on: ubuntu-latest - needs: [staging, promote] - steps: - - uses: actions/checkout@v2 - - name: Install Tooling - run: | - curl -sSL https://github.com/cbroglie/mustache/releases/download/v1.2.2/mustache_1.2.2_linux_amd64.tar.gz | sudo tar -C /usr/local/bin/ --no-same-owner -xzv mustache - - name: GitHub Release - env: - PROJECT_VERSION: ${{needs.staging.outputs.project-version}} - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - RELEASE_NOTES_FILE: ${{runner.temp}}/release_notes.md5 - RELEASE_NOTES_DATA: ${{runner.temp}}/release_notes_data.json - RELEASE_NOTES_HEADERS: ${{runner.temp}}/release_notes_headers.json - RELEASE_NOTES_FOOTERS: ${{runner.temp}}/release_notes_footers.json - RELEASE_NOTES_ISSUES: ${{runner.temp}}/release_notes_issues.json - run: | - gh issue list \ - --repo spring-projects/spring-statemachine \ - --milestone $PROJECT_VERSION \ - --label automation/rlnotes-header \ - --state all --json title,body \ - --jq '{headers:map(.),headerslength:(length)}' \ - > $RELEASE_NOTES_HEADERS - gh issue list \ - --repo spring-projects/spring-statemachine \ - --milestone $PROJECT_VERSION \ - --label automation/rlnotes-footer \ - --state all --json title,body \ - --jq '{footers:map(.),footerslength:(length)}' \ - > $RELEASE_NOTES_FOOTERS - gh issue list \ - --repo spring-projects/spring-statemachine \ - --milestone $PROJECT_VERSION \ - --state all --json number,title,labels \ - --jq '{issues:map(select((.labels | length == 0) or (any(.labels[].name; startswith("automation/rlnotes")|not))))}' \ - > $RELEASE_NOTES_ISSUES - jq -s '{issues:(.[0].issues),headers:(.[1].headers),headerslength:(.[1].headerslength),footers:(.[2].footers), footerslength:(.[2].footerslength)}' \ - $RELEASE_NOTES_ISSUES \ - $RELEASE_NOTES_HEADERS \ - $RELEASE_NOTES_FOOTERS \ - > $RELEASE_NOTES_DATA - mustache $RELEASE_NOTES_DATA .github/rlnotes.mustache > $RELEASE_NOTES_FILE - gh release create v$PROJECT_VERSION \ - --draft \ - --prerelease \ - --title "$PROJECT_VERSION" \ - --notes-file $RELEASE_NOTES_FILE From f5fcd1f0c706aef8dce9db6ec1fe20f0c7febe1a Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 28 Aug 2025 13:01:49 +0200 Subject: [PATCH 33/46] Update repository resolution order --- build.gradle | 10 ++-------- settings.gradle | 9 ++------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index fb499bf40..14d76667d 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,6 @@ description = 'Spring Statemachine' repositories { maven { url 'https://repo.spring.io/snapshot' } - maven { url 'https://repo.spring.io/milestone' } maven { url 'https://repo.spring.io/release' } mavenCentral() } @@ -17,14 +16,9 @@ allprojects { group = 'org.springframework.statemachine' repositories { - mavenCentral() + maven { url 'https://repo.spring.io/snapshot' } maven { url 'https://repo.spring.io/release' } - if (version.contains('-')) { - maven { url "https://repo.spring.io/milestone" } - } - if (version.endsWith('-SNAPSHOT')) { - maven { url "https://repo.spring.io/snapshot" } - } + mavenCentral() } configurations.all { diff --git a/settings.gradle b/settings.gradle index e1bba595f..9d1150ee5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,14 +1,9 @@ pluginManagement { repositories { + maven { url 'https://repo.spring.io/snapshot' } + maven { url 'https://repo.spring.io/release' } mavenCentral() gradlePluginPortal() - maven { url 'https://repo.spring.io/release' } - if (version.contains('-')) { - maven { url 'https://repo.spring.io/milestone' } - } - if (version.endsWith('-SNAPSHOT')) { - maven { url 'https://repo.spring.io/snapshot' } - } } plugins { id 'org.springframework.boot' version "$springBootVersion" From 5dbb3f603ac3cf6304d3ca357fce96c6e26bd7cd Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 28 Aug 2025 13:12:13 +0200 Subject: [PATCH 34/46] Fix gradle task name in deploy-docs.yml --- .github/workflows/deploy-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 2a4f05ca0..d9de4a385 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -23,7 +23,7 @@ jobs: run: echo PROJECT_VERSION=${{ github.event.inputs.releaseVersion }} >> $GITHUB_ENV - name: Build with Gradle - run: ./gradlew -Pversion=$PROJECT_VERSION -x test build + run: ./gradlew -Pversion=$PROJECT_VERSION distZip - name: Setup SSH key if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} From 4e14363e97392f748acc693f409fac4445fc8e16 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 28 Aug 2025 22:07:07 +0200 Subject: [PATCH 35/46] Fix documentation upload Resolves #1191 --- .github/workflows/ci.yml | 61 ++++++++++++++----------------- .github/workflows/deploy-docs.yml | 35 ++++-------------- 2 files changed, 35 insertions(+), 61 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75909cd06..8dae12c1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,10 +10,8 @@ on: jobs: - # build and test, then upload logs if failure build: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4.2.2 - name: Spring Gradle Build Action @@ -22,39 +20,10 @@ jobs: java-version: '17' distribution: 'temurin' - name: Build with Gradle - run: ./gradlew build - - name: Tar Build Logs - if: ${{ failure() }} - run: | - mkdir -p build - tar \ - -zc \ - --ignore-failed-read \ - --file build/buildlogs.tar.gz \ - */build/reports \ - */*/build/reports - - name: Upload Build Logs - if: ${{ failure() }} - uses: actions/upload-artifact@v4.6.2 - with: - name: buildlogs - path: build/buildlogs.tar.gz + run: ./gradlew -Dmaven.repo.local=$(pwd)/deployment-repository clean build distZip publishToMavenLocal - # if build job succeed, build without tests and publish - publish: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4.2.2 - - name: Spring Gradle Build Action - uses: spring-io/spring-gradle-build-action@v2 - with: - java-version: '17' - distribution: 'temurin' - - name: Build with Gradle - run: ./gradlew -Dmaven.repo.local=$(pwd)/deployment-repository -x test clean build publishToMavenLocal - - - name: Deploy + - name: Deploy artefacts + if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} uses: spring-io/artifactory-deploy-action@v0.0.2 with: uri: 'https://repo.spring.io' @@ -63,3 +32,27 @@ jobs: build-name: 'spring-statemachine-main' repository: 'libs-snapshot-local' folder: 'deployment-repository' + + - name: Deploy documentation + if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} + env: + DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} + DOCS_SSH_HOST_KEY: ${{ secrets.DOCS_SSH_HOST_KEY }} + DOCS_HOST: ${{ secrets.DOCS_HOST }} + DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} + DOCS_PATH: ${{ secrets.DOCS_PATH }} + run: | + mkdir "$HOME/.ssh" + echo "$DOCS_SSH_KEY" > "$HOME/.ssh/key" + chmod 600 "$HOME/.ssh/key" + echo "$DOCS_SSH_HOST_KEY" > "$HOME/.ssh/known_hosts" + + PROJECT_VERSION=$(cat gradle.properties | grep "version=" | cut -d '=' -f 2) + ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST "cd $DOCS_PATH && mkdir -p $PROJECT_VERSION" + + pushd build + scp -i $HOME/.ssh/key -r api $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$PROJECT_VERSION + popd + pushd docs/build/docs + scp -i $HOME/.ssh/key -r asciidoc $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$PROJECT_VERSION/reference + popd diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index d9de4a385..2e660b310 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -25,42 +25,23 @@ jobs: - name: Build with Gradle run: ./gradlew -Pversion=$PROJECT_VERSION distZip - - name: Setup SSH key + - name: Deploy documentation if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} env: DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} DOCS_SSH_HOST_KEY: ${{ secrets.DOCS_SSH_HOST_KEY }} + DOCS_HOST: ${{ secrets.DOCS_HOST }} + DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} + DOCS_PATH: ${{ secrets.DOCS_PATH }} run: | mkdir "$HOME/.ssh" echo "$DOCS_SSH_KEY" > "$HOME/.ssh/key" chmod 600 "$HOME/.ssh/key" echo "$DOCS_SSH_HOST_KEY" > "$HOME/.ssh/known_hosts" - - - name: Prepare release directory - if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} - env: - DOCS_HOST: ${{ secrets.DOCS_HOST }} - DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} - run: | - DOCS_PATH="/opt/www/domains/spring.io/docs/htdocs/spring-statemachine/docs" ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST "cd $DOCS_PATH && mkdir -p $PROJECT_VERSION" - - - name: Deploy Java docs - if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} - env: - DOCS_HOST: ${{ secrets.DOCS_HOST }} - DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} - working-directory: build - run: | - DOCS_PATH="/opt/www/domains/spring.io/docs/htdocs/spring-statemachine/docs" + pushd build scp -i $HOME/.ssh/key -r api $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$PROJECT_VERSION - - - name: Deploy reference docs - if: ${{ github.repository == 'spring-projects/spring-statemachine' && github.ref_name == 'main' }} - env: - DOCS_HOST: ${{ secrets.DOCS_HOST }} - DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} - working-directory: docs/build/docs - run: | - DOCS_PATH="/opt/www/domains/spring.io/docs/htdocs/spring-statemachine/docs" + popd + pushd docs/build/docs scp -i $HOME/.ssh/key -r asciidoc $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$PROJECT_VERSION/reference + popd From 5c9c0f6de7996ea9940e3a206c98ab076988482d Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 28 Aug 2025 22:35:12 +0200 Subject: [PATCH 36/46] Update dependencies --- gradle.properties | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/gradle.properties b/gradle.properties index 26958760d..4cf77ca92 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ version=4.0.2-SNAPSHOT -springBootVersion=3.5.4-SNAPSHOT +springBootVersion=3.5.6-SNAPSHOT jakartaPersistenceVersion=3.1.0 -mongoDriverSyncVersion=5.5.0 +mongoDriverSyncVersion=5.5.1 kryoVersion=4.0.3 eclipseEmfXmiVersion=2.11.1 eclipseUml2CommonVersion=2.0.0-v20140602-0749 @@ -10,11 +10,10 @@ eclipseEmfCommonVersion=2.11.0 eclipseUml2TypesVersion=2.0.0-v20140602-0749 eclipseEmfEcoreVersion=2.11.2 eclipseUml2UmlVersion=5.0.0-v20140602-0749 -xcuratorVersion=2.11.1 -curatorVersion=5.5.0 +curatorVersion=5.9.0 springAsciidoctorBackends=0.0.5 awaitilityVersion=3.1.6 -reactorBlockHoundVersion=1.0.10.RELEASE +reactorBlockHoundVersion=1.0.13.RELEASE findbugsVersion=3.0.2 gradleEnterpriseVersion=3.16.2 springGeConventionsVersion=0.0.15 From ced20982d2d9a4ef370b16c7230e3dd9ffc5e38f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Jun 2026 23:36:09 +0200 Subject: [PATCH 37/46] Update whatsnew.adoc for v4 Resolves #1201 --- docs/src/reference/asciidoc/whatsnew.adoc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/src/reference/asciidoc/whatsnew.adoc b/docs/src/reference/asciidoc/whatsnew.adoc index beb169856..b03125b96 100644 --- a/docs/src/reference/asciidoc/whatsnew.adoc +++ b/docs/src/reference/asciidoc/whatsnew.adoc @@ -78,3 +78,11 @@ through a process of migrating into `3.x` for cases we're not handling internall At this point most of a documentation has been changed to showcase reactive interfaces while we still keep some notes around to users still using old blocking methods. + +== In 4.0 + +Spring Statemachine 4.0.x requires Java 17+ and focuses on the support of Spring Boot 3.5.x. + +This release includes several dependencies and documentation updates, as well as some bug fixes. + +The samples have been updated, and the dependency to Spring Shell has been removed from the project. \ No newline at end of file From fc394cb3d95010c07433d931689fadb099bc16bc Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Jun 2026 23:38:26 +0200 Subject: [PATCH 38/46] Update dependencies --- gradle.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 4cf77ca92..303721488 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ version=4.0.2-SNAPSHOT -springBootVersion=3.5.6-SNAPSHOT +springBootVersion=3.5.15 jakartaPersistenceVersion=3.1.0 -mongoDriverSyncVersion=5.5.1 +mongoDriverSyncVersion=5.5.2 kryoVersion=4.0.3 eclipseEmfXmiVersion=2.11.1 eclipseUml2CommonVersion=2.0.0-v20140602-0749 @@ -13,7 +13,7 @@ eclipseUml2UmlVersion=5.0.0-v20140602-0749 curatorVersion=5.9.0 springAsciidoctorBackends=0.0.5 awaitilityVersion=3.1.6 -reactorBlockHoundVersion=1.0.13.RELEASE +reactorBlockHoundVersion=1.0.17.RELEASE findbugsVersion=3.0.2 gradleEnterpriseVersion=3.16.2 springGeConventionsVersion=0.0.15 From 912009944a7692afcd38c39e3eec4f2e03e9af82 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Jun 2026 23:38:45 +0200 Subject: [PATCH 39/46] Update gradle version to 8.14.5 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa02b02fc..7a04a2dcf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 776ee25f0e9a49ee5d0afb464292e17741eed49b Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 11 Jun 2026 00:04:11 +0200 Subject: [PATCH 40/46] Update README.adoc --- README.adoc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.adoc b/README.adoc index edb284ae8..0f17284e2 100644 --- a/README.adoc +++ b/README.adoc @@ -47,12 +47,11 @@ invoked from the root of the source tree and serves as a cross-platform, self-contained bootstrap mechanism for the build. === Prerequisites -https://help.github.com/set-up-git-redirect[Git] and the -https://www.oracle.com/technetwork/java/javase/downloads[JDK8 build]. +https://help.github.com/set-up-git-redirect[Git] and https://www.oracle.com/technetwork/java/javase/downloads[JDK17+]. Be sure that your `JAVA_HOME` environment variable points to the -`jdk` folder extracted from the JDK download. While build requires -`JDK8`, created artifacts are `JDK7` compatible. +`jdk` folder extracted from the JDK download. The build requires +`JDK17+`. === Check out sources [indent=0] From b37e7f41ea5c6ec6079bb1acdcc02918a2ba940b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4fer?= <2326303+b0n541@users.noreply.github.com> Date: Thu, 11 Sep 2025 06:39:21 +0200 Subject: [PATCH 41/46] Fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jan Schäfer <2326303+b0n541@users.noreply.github.com> --- docs/src/reference/asciidoc/appendix-reactormigration.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/reference/asciidoc/appendix-reactormigration.adoc b/docs/src/reference/asciidoc/appendix-reactormigration.adoc index 5ecb8cfc5..8f2d18714 100644 --- a/docs/src/reference/asciidoc/appendix-reactormigration.adoc +++ b/docs/src/reference/asciidoc/appendix-reactormigration.adoc @@ -3,7 +3,7 @@ == Reactor Migration Guide Main task for a work for `3.x` has been to both internally and externally move and change as much as we can from imperative code into a reactive world. This means that some -of the main interfaces have added new reative methods and most of a internal execution logic +of the main interfaces have added new reactive methods and most of a internal execution logic (where applicable) has been moved over to be handled by a reactor. Essentially what this means is that thread handling model is considerably different compared to `2.x`. Following chapters go throught all these changes. From 6b77ea6b729e5346d83e31f36c43818665d67384 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 11 Jun 2026 10:32:31 +0200 Subject: [PATCH 42/46] Fix kryo default configuration --- .../buildtests/RedisPersistTests.java | 9 +- .../JpaPersistingStateMachineInterceptor.java | 20 ++- .../jpa/JpaRepositoryStateMachinePersist.java | 20 ++- .../data/jpa/JpaRepositoryTests.java | 7 +- ...goDbPersistingStateMachineInterceptor.java | 20 ++- .../MongoDbRepositoryStateMachinePersist.java | 20 ++- .../data/mongodb/MongoDbRepositoryTests.java | 7 +- ...edisPersistingStateMachineInterceptor.java | 20 ++- .../RedisRepositoryStateMachinePersist.java | 20 ++- .../RedisStateMachineContextRepository.java | 94 ++++++++++--- .../data/redis/RedisRepositoryTests.java | 7 +- .../data/RepositoryStateMachinePersist.java | 30 +++- ...tKryoStateMachineSerialisationService.java | 3 +- ...KryoStateMachineSerialisationDefaults.java | 102 ++++++++++++++ .../KryoStateMachineSerialisationService.java | 52 ++++++- ...tateMachineSerialisationDefaultsTests.java | 132 ++++++++++++++++++ .../demo/datapersist/StateMachineConfig.java | 17 ++- .../ZookeeperStateMachineEnsemble.java | 25 +++- .../ZookeeperStateMachinePersist.java | 59 +++++--- 19 files changed, 594 insertions(+), 70 deletions(-) create mode 100644 spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationDefaults.java create mode 100644 spring-statemachine-kryo/src/test/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationDefaultsTests.java diff --git a/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/RedisPersistTests.java b/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/RedisPersistTests.java index fca2b38c9..3fb973378 100644 --- a/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/RedisPersistTests.java +++ b/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/RedisPersistTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2026 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. @@ -110,7 +110,12 @@ public RedisConnectionFactory redisConnectionFactory() { @Bean public StateMachinePersist stateMachinePersist(RedisConnectionFactory connectionFactory) { RedisStateMachineContextRepository repository = - new RedisStateMachineContextRepository(connectionFactory); + new RedisStateMachineContextRepository(connectionFactory, + RedisStateMachineContextRepository.DEFAULT_KEY_NAMESPACE, + kryo -> { + kryo.register(TestStates.class); + kryo.register(TestEvents.class); + }); return new RepositoryStateMachinePersist(repository); } diff --git a/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaPersistingStateMachineInterceptor.java b/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaPersistingStateMachineInterceptor.java index eba5cb12d..e63c4b328 100644 --- a/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaPersistingStateMachineInterceptor.java +++ b/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaPersistingStateMachineInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2026 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. @@ -15,6 +15,9 @@ */ package org.springframework.statemachine.data.jpa; +import java.util.function.Consumer; + +import com.esotericsoftware.kryo.Kryo; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.persist.AbstractPersistingStateMachineInterceptor; import org.springframework.statemachine.persist.StateMachineRuntimePersister; @@ -45,6 +48,21 @@ public JpaPersistingStateMachineInterceptor(JpaStateMachineRepository jpaStateMa this.persist = new JpaRepositoryStateMachinePersist(jpaStateMachineRepository); } + /** + * Instantiates a new jpa persisting state machine interceptor with a Kryo + * customizer for registering application-specific state and event types. + * + * @param jpaStateMachineRepository the jpa state machine repository + * @param kryoCustomizer callback applied to each new Kryo instance after + * the framework defaults; use it to register state/event enums + * @since 4.0.2 + */ + public JpaPersistingStateMachineInterceptor(JpaStateMachineRepository jpaStateMachineRepository, + Consumer kryoCustomizer) { + Assert.notNull(jpaStateMachineRepository, "'jpaStateMachineRepository' must be set"); + this.persist = new JpaRepositoryStateMachinePersist(jpaStateMachineRepository, kryoCustomizer); + } + /** * Instantiates a new jpa persisting state machine interceptor. * diff --git a/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryStateMachinePersist.java b/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryStateMachinePersist.java index 72b6f35c6..345905dd1 100644 --- a/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryStateMachinePersist.java +++ b/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryStateMachinePersist.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 the original author or authors. + * Copyright 2017-2026 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. @@ -15,6 +15,9 @@ */ package org.springframework.statemachine.data.jpa; +import java.util.function.Consumer; + +import com.esotericsoftware.kryo.Kryo; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.data.RepositoryStateMachinePersist; import org.springframework.statemachine.data.StateMachineRepository; @@ -42,6 +45,21 @@ public JpaRepositoryStateMachinePersist(JpaStateMachineRepository jpaStateMachin this.jpaStateMachineRepository = jpaStateMachineRepository; } + /** + * Instantiates a new jpa repository state machine persist with a Kryo + * customizer for registering application-specific types. + * + * @param jpaStateMachineRepository the jpa state machine repository + * @param kryoCustomizer callback applied to each new Kryo instance after + * the framework defaults; use it to register state/event enums + * @since 4.0.2 + */ + public JpaRepositoryStateMachinePersist(JpaStateMachineRepository jpaStateMachineRepository, + Consumer kryoCustomizer) { + super(kryoCustomizer); + this.jpaStateMachineRepository = jpaStateMachineRepository; + } + /** * Instantiates a new jpa repository state machine persist. * diff --git a/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/JpaRepositoryTests.java b/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/JpaRepositoryTests.java index cfb5fcdf0..b9d08a68d 100644 --- a/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/JpaRepositoryTests.java +++ b/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/JpaRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2026 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. @@ -457,7 +457,10 @@ public void configure(StateMachineTransitionConfigurer stateMachineRuntimePersister() { - return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository); + return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository, kryo -> { + kryo.register(PersistTestStates.class); + kryo.register(PersistTestEvents.class); + }); } } diff --git a/spring-statemachine-data/mongodb/src/main/java/org/springframework/statemachine/data/mongodb/MongoDbPersistingStateMachineInterceptor.java b/spring-statemachine-data/mongodb/src/main/java/org/springframework/statemachine/data/mongodb/MongoDbPersistingStateMachineInterceptor.java index 8c78a519d..8252b927b 100644 --- a/spring-statemachine-data/mongodb/src/main/java/org/springframework/statemachine/data/mongodb/MongoDbPersistingStateMachineInterceptor.java +++ b/spring-statemachine-data/mongodb/src/main/java/org/springframework/statemachine/data/mongodb/MongoDbPersistingStateMachineInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2026 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. @@ -15,6 +15,9 @@ */ package org.springframework.statemachine.data.mongodb; +import java.util.function.Consumer; + +import com.esotericsoftware.kryo.Kryo; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.persist.AbstractPersistingStateMachineInterceptor; import org.springframework.statemachine.persist.StateMachineRuntimePersister; @@ -45,6 +48,21 @@ public MongoDbPersistingStateMachineInterceptor(MongoDbStateMachineRepository mo this.persist = new MongoDbRepositoryStateMachinePersist(mongodbStateMachineRepository); } + /** + * Instantiates a new mongodb persisting state machine interceptor with a + * Kryo customizer for registering application-specific state and event types. + * + * @param mongodbStateMachineRepository the mongodb state machine repository + * @param kryoCustomizer callback applied to each new Kryo instance after + * the framework defaults; use it to register state/event enums + * @since 4.0.2 + */ + public MongoDbPersistingStateMachineInterceptor(MongoDbStateMachineRepository mongodbStateMachineRepository, + Consumer kryoCustomizer) { + Assert.notNull(mongodbStateMachineRepository, "'mongodbStateMachineRepository' must be set"); + this.persist = new MongoDbRepositoryStateMachinePersist(mongodbStateMachineRepository, kryoCustomizer); + } + /** * Instantiates a new mongodb persisting state machine interceptor. * diff --git a/spring-statemachine-data/mongodb/src/main/java/org/springframework/statemachine/data/mongodb/MongoDbRepositoryStateMachinePersist.java b/spring-statemachine-data/mongodb/src/main/java/org/springframework/statemachine/data/mongodb/MongoDbRepositoryStateMachinePersist.java index 625085d55..534ba70e6 100644 --- a/spring-statemachine-data/mongodb/src/main/java/org/springframework/statemachine/data/mongodb/MongoDbRepositoryStateMachinePersist.java +++ b/spring-statemachine-data/mongodb/src/main/java/org/springframework/statemachine/data/mongodb/MongoDbRepositoryStateMachinePersist.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 the original author or authors. + * Copyright 2017-2026 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. @@ -15,6 +15,9 @@ */ package org.springframework.statemachine.data.mongodb; +import java.util.function.Consumer; + +import com.esotericsoftware.kryo.Kryo; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.data.RepositoryStateMachinePersist; import org.springframework.statemachine.data.StateMachineRepository; @@ -42,6 +45,21 @@ public MongoDbRepositoryStateMachinePersist(MongoDbStateMachineRepository mongod this.mongodbStateMachineRepository = mongodbStateMachineRepository; } + /** + * Instantiates a new mongodb repository state machine persist with a Kryo + * customizer for registering application-specific types. + * + * @param mongodbStateMachineRepository the mongodb state machine repository + * @param kryoCustomizer callback applied to each new Kryo instance after + * the framework defaults; use it to register state/event enums + * @since 4.0.2 + */ + public MongoDbRepositoryStateMachinePersist(MongoDbStateMachineRepository mongodbStateMachineRepository, + Consumer kryoCustomizer) { + super(kryoCustomizer); + this.mongodbStateMachineRepository = mongodbStateMachineRepository; + } + /** * Instantiates a new mongodb repository state machine persist. * diff --git a/spring-statemachine-data/mongodb/src/test/java/org/springframework/statemachine/data/mongodb/MongoDbRepositoryTests.java b/spring-statemachine-data/mongodb/src/test/java/org/springframework/statemachine/data/mongodb/MongoDbRepositoryTests.java index f5a32142e..a07e6941b 100644 --- a/spring-statemachine-data/mongodb/src/test/java/org/springframework/statemachine/data/mongodb/MongoDbRepositoryTests.java +++ b/spring-statemachine-data/mongodb/src/test/java/org/springframework/statemachine/data/mongodb/MongoDbRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2026 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. @@ -246,7 +246,10 @@ public void configure(StateMachineTransitionConfigurer stateMachineRuntimePersister() { - return new MongoDbPersistingStateMachineInterceptor<>(mongodbStateMachineRepository); + return new MongoDbPersistingStateMachineInterceptor<>(mongodbStateMachineRepository, kryo -> { + kryo.register(PersistTestStates.class); + kryo.register(PersistTestEvents.class); + }); } } diff --git a/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisPersistingStateMachineInterceptor.java b/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisPersistingStateMachineInterceptor.java index 9ec3761a0..92650f173 100644 --- a/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisPersistingStateMachineInterceptor.java +++ b/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisPersistingStateMachineInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2026 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. @@ -15,6 +15,9 @@ */ package org.springframework.statemachine.data.redis; +import java.util.function.Consumer; + +import com.esotericsoftware.kryo.Kryo; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.persist.AbstractPersistingStateMachineInterceptor; import org.springframework.statemachine.persist.StateMachineRuntimePersister; @@ -45,6 +48,21 @@ public RedisPersistingStateMachineInterceptor(RedisStateMachineRepository redisS this.persist = new RedisRepositoryStateMachinePersist(redisStateMachineRepository); } + /** + * Instantiates a new redis persisting state machine interceptor with a + * Kryo customizer for registering application-specific state and event types. + * + * @param redisStateMachineRepository the redis state machine repository + * @param kryoCustomizer callback applied to each new Kryo instance after + * the framework defaults; use it to register state/event enums + * @since 4.0.2 + */ + public RedisPersistingStateMachineInterceptor(RedisStateMachineRepository redisStateMachineRepository, + Consumer kryoCustomizer) { + Assert.notNull(redisStateMachineRepository, "'redisStateMachineRepository' must be set"); + this.persist = new RedisRepositoryStateMachinePersist(redisStateMachineRepository, kryoCustomizer); + } + /** * Instantiates a new redis persisting state machine interceptor. * diff --git a/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisRepositoryStateMachinePersist.java b/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisRepositoryStateMachinePersist.java index aa5473505..5d586222e 100644 --- a/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisRepositoryStateMachinePersist.java +++ b/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisRepositoryStateMachinePersist.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 the original author or authors. + * Copyright 2017-2026 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. @@ -15,6 +15,9 @@ */ package org.springframework.statemachine.data.redis; +import java.util.function.Consumer; + +import com.esotericsoftware.kryo.Kryo; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.data.RepositoryStateMachinePersist; import org.springframework.statemachine.data.StateMachineRepository; @@ -42,6 +45,21 @@ public RedisRepositoryStateMachinePersist(RedisStateMachineRepository redisState this.redisStateMachineRepository = redisStateMachineRepository; } + /** + * Instantiates a new redis repository state machine persist with a Kryo + * customizer for registering application-specific types. + * + * @param redisStateMachineRepository the redis state machine repository + * @param kryoCustomizer callback applied to each new Kryo instance after + * the framework defaults; use it to register state/event enums + * @since 4.0.2 + */ + public RedisRepositoryStateMachinePersist(RedisStateMachineRepository redisStateMachineRepository, + Consumer kryoCustomizer) { + super(kryoCustomizer); + this.redisStateMachineRepository = redisStateMachineRepository; + } + /** * Instantiates a new redis repository state machine persist. * diff --git a/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisStateMachineContextRepository.java b/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisStateMachineContextRepository.java index 95e8aeaac..bfc81daf7 100644 --- a/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisStateMachineContextRepository.java +++ b/spring-statemachine-data/redis/src/main/java/org/springframework/statemachine/data/redis/RedisStateMachineContextRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2026 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. @@ -17,18 +17,16 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.util.UUID; +import java.util.function.Consumer; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.messaging.MessageHeaders; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.StateMachineContextRepository; -import org.springframework.statemachine.kryo.MessageHeadersSerializer; -import org.springframework.statemachine.kryo.StateMachineContextSerializer; -import org.springframework.statemachine.kryo.UUIDSerializer; +import org.springframework.statemachine.kryo.KryoStateMachineSerialisationDefaults; +import org.springframework.util.Assert; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; @@ -36,6 +34,18 @@ /** * A {@link StateMachineContextRepository} backed by a redis and kryo serialization. + *

+ * Persisted bytes are deserialised through Kryo with the + * {@linkplain KryoStateMachineSerialisationDefaults safe-by-default class + * allowlist} applied. Applications that use custom state or event types + * (typically enums) must register those types via the + * {@link #RedisStateMachineContextRepository(RedisConnectionFactory, String, Consumer)} + * constructor's {@code kryoCustomizer} parameter; otherwise Kryo will reject + * them with {@code IllegalArgumentException} ("Class is not registered"). + *

+ * Keys are written with a fixed namespace prefix (default + * {@value #DEFAULT_KEY_NAMESPACE}) so that user-supplied {@code machineId} + * values cannot collide with unrelated keys in the same Redis logical database. * * @author Janne Valkealahti * @@ -44,38 +54,80 @@ */ public class RedisStateMachineContextRepository implements StateMachineContextRepository> { - private static final ThreadLocal kryoThreadLocal = new ThreadLocal() { + /** + * Default key namespace prefix used when none is supplied. + */ + public static final String DEFAULT_KEY_NAMESPACE = "ssm:context:"; - @SuppressWarnings("rawtypes") - @Override - protected Kryo initialValue() { - Kryo kryo = new Kryo(); - kryo.addDefaultSerializer(StateMachineContext.class, new StateMachineContextSerializer()); - kryo.addDefaultSerializer(MessageHeaders.class, new MessageHeadersSerializer()); - kryo.addDefaultSerializer(UUID.class, new UUIDSerializer()); - return kryo; - } - }; + private final ThreadLocal kryoThreadLocal; private final RedisOperations redisOperations; + private final String keyNamespace; + /** - * Instantiates a new redis state machine context repository. + * Instantiates a new redis state machine context repository using the + * default key namespace and no extra Kryo registrations. * * @param redisConnectionFactory the redis connection factory */ public RedisStateMachineContextRepository(RedisConnectionFactory redisConnectionFactory) { - redisOperations = createDefaultTemplate(redisConnectionFactory); + this(redisConnectionFactory, DEFAULT_KEY_NAMESPACE, null); + } + + /** + * Instantiates a new redis state machine context repository with a custom + * key namespace prefix and no extra Kryo registrations. + * + * @param redisConnectionFactory the redis connection factory + * @param keyNamespace the prefix prepended to every Redis key + * @since 4.0.2 + */ + public RedisStateMachineContextRepository(RedisConnectionFactory redisConnectionFactory, String keyNamespace) { + this(redisConnectionFactory, keyNamespace, null); + } + + /** + * Instantiates a new redis state machine context repository. + * + * @param redisConnectionFactory the redis connection factory + * @param keyNamespace the prefix prepended to every Redis key. Must not be {@code null}; + * pass an empty string to disable prefixing (not recommended). + * @param kryoCustomizer optional callback invoked once per Kryo instance after the + * framework's default registrations are applied. Use this to register + * application-specific state and event types (typically enums) so that they + * are accepted by the allowlist. May be {@code null}. + * @since 4.0.2 + */ + public RedisStateMachineContextRepository(RedisConnectionFactory redisConnectionFactory, + String keyNamespace, Consumer kryoCustomizer) { + Assert.notNull(redisConnectionFactory, "Redis connection factory must not be null"); + Assert.notNull(keyNamespace, "Key namespace must not be null"); + this.redisOperations = createDefaultTemplate(redisConnectionFactory); + this.keyNamespace = keyNamespace; + this.kryoThreadLocal = ThreadLocal.withInitial(() -> { + Kryo kryo = new Kryo(); + KryoStateMachineSerialisationDefaults.registerDefaults(kryo); + if (kryoCustomizer != null) { + kryoCustomizer.accept(kryo); + } + return kryo; + }); } @Override public void save(StateMachineContext context, String id) { - redisOperations.opsForValue().set(id, serialize(context)); + redisOperations.opsForValue().set(prefixedKey(id), serialize(context)); } @Override public StateMachineContext getContext(String id) { - return deserialize(redisOperations.opsForValue().get(id)); + return deserialize(redisOperations.opsForValue().get(prefixedKey(id))); + } + + private String prefixedKey(String id) { + Assert.notNull(id, "Machine id must not be null"); + return keyNamespace + id; } private static RedisTemplate createDefaultTemplate(RedisConnectionFactory connectionFactory) { diff --git a/spring-statemachine-data/redis/src/test/java/org/springframework/statemachine/data/redis/RedisRepositoryTests.java b/spring-statemachine-data/redis/src/test/java/org/springframework/statemachine/data/redis/RedisRepositoryTests.java index 4a9e9271b..c5098af3c 100644 --- a/spring-statemachine-data/redis/src/test/java/org/springframework/statemachine/data/redis/RedisRepositoryTests.java +++ b/spring-statemachine-data/redis/src/test/java/org/springframework/statemachine/data/redis/RedisRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2026 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. @@ -246,7 +246,10 @@ public void configure(StateMachineTransitionConfigurer stateMachineRuntimePersister() { - return new RedisPersistingStateMachineInterceptor<>(redisStateMachineRepository); + return new RedisPersistingStateMachineInterceptor<>(redisStateMachineRepository, kryo -> { + kryo.register(PersistTestStates.class); + kryo.register(PersistTestEvents.class); + }); } } diff --git a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryStateMachinePersist.java b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryStateMachinePersist.java index fc734a116..b0cb821ec 100644 --- a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryStateMachinePersist.java +++ b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryStateMachinePersist.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 the original author or authors. + * Copyright 2017-2026 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. @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -27,6 +28,8 @@ import org.springframework.statemachine.support.DefaultStateMachineContext; import org.springframework.util.Assert; +import com.esotericsoftware.kryo.Kryo; + /** * Base implementation of a {@link StateMachinePersist} using Spring Data Repositories. * @@ -42,12 +45,35 @@ public abstract class RepositoryStateMachinePersist serialisationService; /** - * Instantiates a new repository state machine persist. + * Instantiates a new repository state machine persist using the default + * {@link KryoStateMachineSerialisationService} with no extra registrations. */ protected RepositoryStateMachinePersist() { this.serialisationService = new KryoStateMachineSerialisationService(); } + /** + * Instantiates a new repository state machine persist using a + * {@link KryoStateMachineSerialisationService} pre-configured with the + * supplied {@code kryoCustomizer}. + *

+ * Use this constructor to register application-specific state and event + * types (typically enums) with Kryo's registration-required allowlist: + *

{@code
+	 * new JpaRepositoryStateMachinePersist<>(repo, kryo -> {
+	 *     kryo.register(MyStates.class);
+	 *     kryo.register(MyEvents.class);
+	 * })
+	 * }
+ * + * @param kryoCustomizer callback applied to each new Kryo instance; + * may be {@code null} + * @since 4.0.2 + */ + protected RepositoryStateMachinePersist(Consumer kryoCustomizer) { + this.serialisationService = new KryoStateMachineSerialisationService(kryoCustomizer); + } + /** * Instantiates a new repository state machine persist. * diff --git a/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/AbstractKryoStateMachineSerialisationService.java b/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/AbstractKryoStateMachineSerialisationService.java index 4255b8600..36bdd6ebd 100644 --- a/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/AbstractKryoStateMachineSerialisationService.java +++ b/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/AbstractKryoStateMachineSerialisationService.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 the original author or authors. + * Copyright 2017-2026 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. @@ -54,6 +54,7 @@ public Kryo create() { // doesn't match. for now just use below trick before we try // to go fully on beans and get a bean class loader. kryo.setClassLoader(ClassUtils.getDefaultClassLoader()); + KryoStateMachineSerialisationDefaults.registerDefaults(kryo); configureKryoInstance(kryo); return kryo; } diff --git a/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationDefaults.java b/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationDefaults.java new file mode 100644 index 000000000..ffffe9946 --- /dev/null +++ b/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationDefaults.java @@ -0,0 +1,102 @@ +/* + * Copyright 2026 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 org.springframework.statemachine.kryo; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.messaging.MessageHeaders; +import org.springframework.statemachine.StateMachineContext; +import org.springframework.statemachine.support.DefaultStateMachineContext; +import org.springframework.statemachine.support.ObservableMap; + +import com.esotericsoftware.kryo.Kryo; + +/** + * Static helper that applies the safe-by-default configuration used by Spring + * Statemachine's Kryo-based persistence: enables + * {@link Kryo#setRegistrationRequired(boolean) registrationRequired=true} and + * registers the concrete framework and JDK types known to be written through + * {@link StateMachineContextSerializer}. + * Applications that use custom state or event types (typically enums) need to + * register those types as well. For Spring-Boot or annotation-config usage this + * is done by subclassing {@link KryoStateMachineSerialisationService} and + * overriding + * {@link AbstractKryoStateMachineSerialisationService#configureKryoInstance(Kryo)}. + * For the {@code spring-statemachine-data-redis} and + * {@code spring-statemachine-zookeeper} backends, a {@code Consumer} + * constructor parameter is provided. + * @since 4.0.2 + * @author Spring Statemachine team + */ +public final class KryoStateMachineSerialisationDefaults { + + private KryoStateMachineSerialisationDefaults() { + } + + /** + * Apply the safe-by-default configuration to the supplied Kryo instance. + * + * @param kryo the Kryo instance to configure + */ + public static void registerDefaults(Kryo kryo) { + // Lock down class resolution: any class that has not been explicitly + // registered below (or by application code afterwards) cannot be + // resolved on read, regardless of what bytes the input stream contains. + kryo.setRegistrationRequired(true); + + // Custom serializers for framework types must be installed before the + // matching register() calls so that the registration picks them up + // rather than falling back to FieldSerializer. + kryo.addDefaultSerializer(StateMachineContext.class, new StateMachineContextSerializer<>()); + kryo.addDefaultSerializer(MessageHeaders.class, new MessageHeadersSerializer()); + kryo.addDefaultSerializer(UUID.class, new UUIDSerializer()); + + // Concrete framework types that travel through the wire. + kryo.register(StateMachineContext.class); + kryo.register(DefaultStateMachineContext.class); + kryo.register(MessageHeaders.class); + kryo.register(UUID.class); + + // Common JDK collection / map types that may legitimately appear in + // event headers, extended-state variables, child collections and + // history-state maps. Application code that uses other concrete map or + // collection types must register them explicitly via the appropriate + // extension hook. + kryo.register(HashMap.class); + kryo.register(LinkedHashMap.class); + kryo.register(TreeMap.class); + kryo.register(ConcurrentHashMap.class); + kryo.register(ArrayList.class); + kryo.register(LinkedList.class); + kryo.register(HashSet.class); + kryo.register(LinkedHashSet.class); + kryo.register(TreeSet.class); + + // DefaultExtendedState wraps its variables in an ObservableMap; that + // concrete wrapper class is therefore the type the serializer + // observes for the variables slot. + kryo.register(ObservableMap.class); + } +} diff --git a/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationService.java b/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationService.java index 45d71fb15..0e330f747 100644 --- a/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationService.java +++ b/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationService.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2026 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. @@ -15,10 +15,8 @@ */ package org.springframework.statemachine.kryo; -import java.util.UUID; +import java.util.function.Consumer; -import org.springframework.messaging.MessageHeaders; -import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.service.StateMachineSerialisationService; import com.esotericsoftware.kryo.Kryo; @@ -35,6 +33,41 @@ */ public class KryoStateMachineSerialisationService extends AbstractKryoStateMachineSerialisationService { + private final Consumer kryoCustomizer; + + /** + * Instantiates a new kryo state machine serialisation service with no + * extra class registrations beyond the framework defaults. + */ + public KryoStateMachineSerialisationService() { + this.kryoCustomizer = null; + } + + /** + * Instantiates a new kryo state machine serialisation service with an + * application-supplied Kryo customizer. + *

+ * The customizer is invoked once per Kryo instance after the + * framework's safe-by-default registrations have been applied (via + * {@link KryoStateMachineSerialisationDefaults#registerDefaults(Kryo)}). + * Use it to register application-specific types — typically the {@code S} + * and {@code E} enums used by the state machine — so they are accepted by + * the registration-required allowlist: + *

{@code
+	 * new KryoStateMachineSerialisationService<>(kryo -> {
+	 *     kryo.register(MyStates.class);
+	 *     kryo.register(MyEvents.class);
+	 * })
+	 * }
+ * + * @param kryoCustomizer callback applied to each new Kryo instance; + * may be {@code null} + * @since 4.0.2 + */ + public KryoStateMachineSerialisationService(Consumer kryoCustomizer) { + this.kryoCustomizer = kryoCustomizer; + } + @Override protected void doEncode(Kryo kryo, Object object, Output output) { kryo.writeObject(output, object); @@ -45,10 +78,15 @@ protected T doDecode(Kryo kryo, Input input, Class type) { return kryo.readObject(input, type); } + /** + * Applies the optional {@link #kryoCustomizer} supplied at construction + * time. Subclasses may override this to register additional types on top + * of the framework defaults and the customizer. + */ @Override protected void configureKryoInstance(Kryo kryo) { - kryo.addDefaultSerializer(StateMachineContext.class, new StateMachineContextSerializer()); - kryo.addDefaultSerializer(MessageHeaders.class, new MessageHeadersSerializer()); - kryo.addDefaultSerializer(UUID.class, new UUIDSerializer()); + if (kryoCustomizer != null) { + kryoCustomizer.accept(kryo); + } } } diff --git a/spring-statemachine-kryo/src/test/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationDefaultsTests.java b/spring-statemachine-kryo/src/test/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationDefaultsTests.java new file mode 100644 index 000000000..7f4f06092 --- /dev/null +++ b/spring-statemachine-kryo/src/test/java/org/springframework/statemachine/kryo/KryoStateMachineSerialisationDefaultsTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2026 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 org.springframework.statemachine.kryo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.HashMap; + +import org.junit.jupiter.api.Test; +import org.springframework.statemachine.StateMachineContext; +import org.springframework.statemachine.support.DefaultExtendedState; +import org.springframework.statemachine.support.DefaultStateMachineContext; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * Tests for @{link KryoStateMachineSerialisationDefaults}. + */ +public class KryoStateMachineSerialisationDefaultsTests { + + public static class SomeClass { + public String payload; + } + + public enum MyStates { + LOCKED, UNLOCKED + } + + public enum MyEvents { + COIN, PUSH + } + + @Test + public void testKryoDefaults() { + Kryo myKryo = new Kryo(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Output output = new Output(out); + SomeClass someClass = new SomeClass(); + someClass.payload = "foo"; + myKryo.writeClassAndObject(output, someClass); + output.close(); + + Kryo kryo = new Kryo(); + KryoStateMachineSerialisationDefaults.registerDefaults(kryo); + Input input = new Input(new ByteArrayInputStream(out.toByteArray())); + + assertThatThrownBy(() -> kryo.readClassAndObject(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Class is not registered"); + } + + @Test + public void testKryoDefaultsRoundTrip() { + HashMap headers = new HashMap<>(); + headers.put("foo", "bar"); + StateMachineContext original = + new DefaultStateMachineContext<>("S1", "E1", headers, new DefaultExtendedState()); + + KryoStateMachineSerialisationService service = + new KryoStateMachineSerialisationService<>(); + byte[] bytes; + StateMachineContext roundTrip; + try { + bytes = service.serialiseStateMachineContext(original); + roundTrip = service.deserialiseStateMachineContext(bytes); + } catch (Exception ex) { + throw new AssertionError("Round-trip of framework-default payload must not fail", ex); + } + + assertThat(roundTrip.getState()).isEqualTo("S1"); + assertThat(roundTrip.getEvent()).isEqualTo("E1"); + assertThat(roundTrip.getEventHeaders()).containsEntry("foo", "bar"); + } + + @Test + public void applicationEnumsCanBeAddedViaSubclass() { + KryoStateMachineSerialisationService service = + new KryoStateMachineSerialisationService() { + @Override + protected void configureKryoInstance(Kryo kryo) { + kryo.register(MyStates.class); + kryo.register(MyEvents.class); + } + }; + + StateMachineContext original = new DefaultStateMachineContext<>( + MyStates.LOCKED, MyEvents.COIN, new HashMap<>(), new DefaultExtendedState()); + + byte[] bytes; + StateMachineContext roundTrip; + try { + bytes = service.serialiseStateMachineContext(original); + roundTrip = service.deserialiseStateMachineContext(bytes); + } catch (Exception ex) { + throw new AssertionError("Round-trip with user enums must not fail", ex); + } + + assertThat(roundTrip.getState()).isEqualTo(MyStates.LOCKED); + assertThat(roundTrip.getEvent()).isEqualTo(MyEvents.COIN); + } + + @Test + public void applicationEnumsShouldBeRegistered() { + KryoStateMachineSerialisationService service = + new KryoStateMachineSerialisationService<>(); + + StateMachineContext original = new DefaultStateMachineContext<>( + MyStates.LOCKED, MyEvents.COIN, new HashMap<>(), new DefaultExtendedState()); + + assertThatThrownBy(() -> service.serialiseStateMachineContext(original)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Class is not registered"); + } +} diff --git a/spring-statemachine-samples/datapersist/src/main/java/demo/datapersist/StateMachineConfig.java b/spring-statemachine-samples/datapersist/src/main/java/demo/datapersist/StateMachineConfig.java index ff6c25c82..0a6a48ec9 100644 --- a/spring-statemachine-samples/datapersist/src/main/java/demo/datapersist/StateMachineConfig.java +++ b/spring-statemachine-samples/datapersist/src/main/java/demo/datapersist/StateMachineConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 the original author or authors. + * Copyright 2017-2026 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. @@ -48,7 +48,10 @@ public static class JpaPersisterConfig { @Bean public StateMachineRuntimePersister stateMachineRuntimePersister( JpaStateMachineRepository jpaStateMachineRepository) { - return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository); + return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository, kryo -> { + kryo.register(States.class); + kryo.register(Events.class); + }); } } //end::snippetA[] @@ -61,7 +64,10 @@ public static class MongoPersisterConfig { @Bean public StateMachineRuntimePersister stateMachineRuntimePersister( MongoDbStateMachineRepository mongoDbStateMachineRepository) { - return new MongoDbPersistingStateMachineInterceptor<>(mongoDbStateMachineRepository); + return new MongoDbPersistingStateMachineInterceptor<>(mongoDbStateMachineRepository, kryo -> { + kryo.register(States.class); + kryo.register(Events.class); + }); } } //end::snippetB[] @@ -74,7 +80,10 @@ public static class RedisPersisterConfig { @Bean public StateMachineRuntimePersister stateMachineRuntimePersister( RedisStateMachineRepository redisStateMachineRepository) { - return new RedisPersistingStateMachineInterceptor<>(redisStateMachineRepository); + return new RedisPersistingStateMachineInterceptor<>(redisStateMachineRepository, kryo -> { + kryo.register(States.class); + kryo.register(Events.class); + }); } } //end::snippetC[] diff --git a/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineEnsemble.java b/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineEnsemble.java index c3a6d6ea7..44a5171f9 100644 --- a/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineEnsemble.java +++ b/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineEnsemble.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2019 the original author or authors. + * Copyright 2015-2026 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. @@ -23,7 +23,9 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import com.esotericsoftware.kryo.Kryo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.curator.framework.CuratorFramework; @@ -102,6 +104,25 @@ public ZookeeperStateMachineEnsemble(CuratorFramework curatorClient, String base * @param logSize the log size */ public ZookeeperStateMachineEnsemble(CuratorFramework curatorClient, String basePath, boolean cleanState, int logSize) { + this(curatorClient, basePath, cleanState, logSize, null); + } + + /** + * Instantiates a new zookeeper state machine ensemble with a custom Kryo + * configurer for application-specific allowlist registrations. + * + * @param curatorClient the curator client + * @param basePath the base zookeeper path + * @param cleanState if true clean existing state + * @param logSize the log size + * @param kryoCustomizer optional callback invoked once per Kryo instance after the + * framework's default registrations are applied. Use this to register + * application-specific state and event types (typically enums) so that they + * are accepted by the allowlist. May be {@code null}. + * @since 4.0.2 + */ + public ZookeeperStateMachineEnsemble(CuratorFramework curatorClient, String basePath, boolean cleanState, int logSize, + Consumer kryoCustomizer) { this.curatorClient = curatorClient; this.cleanState = cleanState; this.logSize = logSize; @@ -110,7 +131,7 @@ public ZookeeperStateMachineEnsemble(CuratorFramework curatorClient, String base this.logPath = baseDataPath + "/" + PATH_LOG; this.memberPath = basePath + "/" + PATH_MEMBERS; this.mutexPath = basePath + "/" + PATH_MUTEX; - this.persist = new ZookeeperStateMachinePersist(curatorClient, statePath, logPath, logSize); + this.persist = new ZookeeperStateMachinePersist(curatorClient, statePath, logPath, logSize, kryoCustomizer); setAutoStartup(true); } diff --git a/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachinePersist.java b/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachinePersist.java index b3d334474..3e6ed7c13 100644 --- a/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachinePersist.java +++ b/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachinePersist.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2026 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. @@ -18,20 +18,17 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Collection; -import java.util.UUID; +import java.util.function.Consumer; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.api.transaction.CuratorTransaction; import org.apache.curator.framework.api.transaction.CuratorTransactionFinal; import org.apache.curator.framework.api.transaction.CuratorTransactionResult; import org.apache.zookeeper.data.Stat; -import org.springframework.messaging.MessageHeaders; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.StateMachineException; import org.springframework.statemachine.StateMachinePersist; -import org.springframework.statemachine.kryo.MessageHeadersSerializer; -import org.springframework.statemachine.kryo.StateMachineContextSerializer; -import org.springframework.statemachine.kryo.UUIDSerializer; +import org.springframework.statemachine.kryo.KryoStateMachineSerialisationDefaults; import org.springframework.util.Assert; import com.esotericsoftware.kryo.Kryo; @@ -41,6 +38,14 @@ /** * {@link StateMachinePersist} using zookeeper as a storage and * kroy libraries as a backing serialization technique. + *

+ * Persisted bytes are deserialised through Kryo with the + * {@linkplain KryoStateMachineSerialisationDefaults safe-by-default class + * allowlist} applied. Applications that use custom state or event types + * (typically enums) must register those types via the + * {@link #ZookeeperStateMachinePersist(CuratorFramework, String, String, int, Consumer)} + * constructor's {@code kryoCustomizer} parameter; otherwise Kryo will reject + * them with {@code IllegalArgumentException} ("Class is not registered"). * * @author Janne Valkealahti * @@ -51,18 +56,7 @@ public class ZookeeperStateMachinePersist implements StateMachinePersist kryoThreadLocal = new ThreadLocal() { - - @SuppressWarnings("rawtypes") - @Override - protected Kryo initialValue() { - Kryo kryo = new Kryo(); - kryo.addDefaultSerializer(StateMachineContext.class, new StateMachineContextSerializer()); - kryo.addDefaultSerializer(MessageHeaders.class, new MessageHeadersSerializer()); - kryo.addDefaultSerializer(UUID.class, new UUIDSerializer()); - return kryo; - } - }; + private final ThreadLocal kryoThreadLocal; private final CuratorFramework curatorClient; private final String path; @@ -76,7 +70,7 @@ protected Kryo initialValue() { * @param path the path for persistent state */ public ZookeeperStateMachinePersist(CuratorFramework curatorClient, String path) { - this(curatorClient, path, null, 0); + this(curatorClient, path, null, 0, null); } /** @@ -88,6 +82,25 @@ public ZookeeperStateMachinePersist(CuratorFramework curatorClient, String path) * @param logSize the log size */ public ZookeeperStateMachinePersist(CuratorFramework curatorClient, String path, String logPath, int logSize) { + this(curatorClient, path, logPath, logSize, null); + } + + /** + * Instantiates a new zookeeper state machine persist with a custom Kryo + * configurer for application-specific allowlist registrations. + * + * @param curatorClient the curator client + * @param path the path + * @param logPath the log path (may be {@code null}) + * @param logSize the log size (must be a positive power of two when {@code logPath} is set) + * @param kryoCustomizer optional callback invoked once per Kryo instance after the + * framework's default registrations are applied. Use this to register + * application-specific state and event types (typically enums) so that they + * are accepted by the allowlist. May be {@code null}. + * @since 4.0.2 + */ + public ZookeeperStateMachinePersist(CuratorFramework curatorClient, String path, String logPath, int logSize, + Consumer kryoCustomizer) { if (logPath != null) { Assert.state(logSize > 0 && ((logSize & -logSize) == logSize), "Log size must be positive and power of two"); } @@ -95,6 +108,14 @@ public ZookeeperStateMachinePersist(CuratorFramework curatorClient, String path, this.path = path; this.logPath = logPath; this.logSize = logSize; + this.kryoThreadLocal = ThreadLocal.withInitial(() -> { + Kryo kryo = new Kryo(); + KryoStateMachineSerialisationDefaults.registerDefaults(kryo); + if (kryoCustomizer != null) { + kryoCustomizer.accept(kryo); + } + return kryo; + }); } @Override From 44ce34b85a41281969813de8a809f532a33fb72e Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 11 Jun 2026 10:55:50 +0200 Subject: [PATCH 43/46] Fix javadoc warning --- .../statemachine/config/configurers/TransitionConfigurer.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java index dec074587..75bd43096 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * Copyright 2015-2026 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. @@ -143,8 +143,6 @@ public interface TransitionConfigurer extends * * @param name the name * @return configurer for chaining - * @param name - * @return */ T name(String name); } From 18a37b07f76b0eef329c3e9d16b7b83c6f5713d4 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 11 Jun 2026 12:04:36 +0200 Subject: [PATCH 44/46] Release version 4.0.2 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 303721488..d837e459b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=4.0.2-SNAPSHOT +version=4.0.2 springBootVersion=3.5.15 jakartaPersistenceVersion=3.1.0 From 718d0705c4fb50d5530492df3d6ade0b8ca9f10d Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 3 Jul 2026 15:59:11 +0200 Subject: [PATCH 45/46] Update README for EOL --- README.adoc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.adoc b/README.adoc index 0f17284e2..a998643f7 100644 --- a/README.adoc +++ b/README.adoc @@ -1,8 +1,6 @@ -= Project status: maintenance mode += Spring Statemachine is no longer maintained -This project is not actively maintained anymore. Please refer to the https://spring.io/blog/2025/04/21/spring-cloud-data-flow-commercial[announcement]. - -Spring Statemachine 4.0.x will be maintained with patch releases until it is out of OSS support, please check the https://spring.io/projects/spring-statemachine#support[support page] for more details about supported versions. +Please refer to the https://spring.io/blog/2025/04/21/spring-cloud-data-flow-commercial[announcement] for more information. = Spring Statemachine From b78817dd691c70760fa428db5ae1bb133ea73380 Mon Sep 17 00:00:00 2001 From: Trevor Marshall Date: Sun, 5 Jul 2026 13:45:56 -0700 Subject: [PATCH 46/46] Create LICENSE prior to archive Signed-off-by: Trevor Marshall --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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.