diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java index ece2ca84..37bde75d 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java @@ -162,7 +162,11 @@ public class DefaultStateConfigurer @Override public StateConfigurer state(S state, Collection> stateActions) { - addIncomplete(null, state, null, null, null, stateActions); + Collection, Mono>> rStateActions = new ArrayList<>(); + if (stateActions != null) { + rStateActions.addAll(stateActions.stream().map(a -> Actions.from(a)).collect(Collectors.toList())); + } + addIncomplete(null, state, null, null, null, rStateActions); return this; } @@ -181,6 +185,30 @@ public class DefaultStateConfigurer return stateDo(state, action, null); } + @Override + public StateConfigurer stateDoFunction(S state, Function, Mono> stateAction) { + Collection, Mono>> stateActions = new ArrayList<>(); + stateActions.add(stateAction); + addIncomplete(null, state, null, null, null, stateActions); + return this; + } + + @Override + public StateConfigurer stateEntryFunction(S state, Function, Mono> action) { + Collection, Mono>> entryActions = new ArrayList<>(); + entryActions.add(action); + addIncomplete(null, state, null, entryActions, null, null); + return this; + } + + @Override + public StateConfigurer stateExitFunction(S state, Function, Mono> action) { + Collection, Mono>> exitActions = new ArrayList<>(); + exitActions.add(action); + addIncomplete(null, state, null, null, exitActions, null); + return this; + } + @Override public StateConfigurer stateDo(S state, Action action, Action error) { Collection> stateActions = null; @@ -194,7 +222,17 @@ public class DefaultStateConfigurer @Override public StateConfigurer state(S state, Collection> entryActions, Collection> exitActions) { - addIncomplete(null, state, null, entryActions, exitActions, null); + Collection, Mono>> rEntryActions = null; + Collection, Mono>> rExitActions = null; + if (entryActions != null) { + rEntryActions = new ArrayList<>(); + rEntryActions.addAll(entryActions.stream().map(a -> Actions.from(a)).collect(Collectors.toList())); + } + if (exitActions != null) { + rExitActions = new ArrayList<>(); + rExitActions.addAll(exitActions.stream().map(a -> Actions.from(a)).collect(Collectors.toList())); + } + addIncomplete(null, state, null, rEntryActions, rExitActions, null); return this; } @@ -313,26 +351,12 @@ public class DefaultStateConfigurer } private void addIncomplete(Object parent, S state, Collection deferred, - Collection> entryActions, Collection> exitActions, - Collection> stateActions) { - Collection, Mono>> rEntryActions = null; - Collection, Mono>> rExitActions = null; - Collection, Mono>> rStateActions = null; - if (entryActions != null) { - rEntryActions = new ArrayList<>(); - rEntryActions.addAll(entryActions.stream().map(a -> Actions.from(a)).collect(Collectors.toList())); - } - if (exitActions != null) { - rExitActions = new ArrayList<>(); - rExitActions.addAll(exitActions.stream().map(a -> Actions.from(a)).collect(Collectors.toList())); - } - if (stateActions != null) { - rStateActions = new ArrayList<>(); - rStateActions.addAll(stateActions.stream().map(a -> Actions.from(a)).collect(Collectors.toList())); - } + Collection, Mono>> entryActions, + Collection, Mono>> exitActions, + Collection, Mono>> stateActions) { StateData stateData = incomplete.get(state); if (stateData == null) { - stateData = new StateData(parent, region, state, deferred, rEntryActions, rExitActions); + stateData = new StateData(parent, region, state, deferred, entryActions, exitActions); incomplete.put(state, stateData); } if (stateData.getParent() == null) { @@ -345,13 +369,13 @@ public class DefaultStateConfigurer stateData.setDeferred(deferred); } if (stateData.getEntryActions() == null) { - stateData.setEntryActions(rEntryActions); + stateData.setEntryActions(entryActions); } if (stateData.getExitActions() == null) { - stateData.setExitActions(rExitActions); + stateData.setExitActions(exitActions); } if (stateData.getStateActions() == null) { - stateData.setStateActions(rStateActions); + stateData.setStateActions(stateActions); } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java index 2b871d7c..10d701ac 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java @@ -17,7 +17,9 @@ package org.springframework.statemachine.config.configurers; import java.util.Collection; import java.util.Set; +import java.util.function.Function; +import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.StateMachineFactory; @@ -25,6 +27,8 @@ import org.springframework.statemachine.config.builders.StateMachineStateConfigu import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; import org.springframework.statemachine.state.State; +import reactor.core.publisher.Mono; + /** * Base {@code StateConfigurer} interface for configuring {@link State}s. * @@ -139,6 +143,33 @@ public interface StateConfigurer extends */ StateConfigurer stateDo(S state, Action action, Action error); + /** + * Specify a state {@code S} with state behaviour {@link Function}. + * + * @param state the state + * @param action the state action + * @return configurer for chaining + */ + StateConfigurer stateDoFunction(S state, Function, Mono> action); + + /** + * Specify a state {@code S} with state entry {@link Function}. + * + * @param state the state + * @param action the state action + * @return configurer for chaining + */ + StateConfigurer stateEntryFunction(S state, Function, Mono> action); + + /** + * Specify a state {@code S} with state exit {@link Function}. + * + * @param state the state + * @param action the state action + * @return configurer for chaining + */ + StateConfigurer stateExitFunction(S state, Function, Mono> action); + /** * Specify a state {@code S} with entry and exit {@link Action}s. * diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java index 8181f288..a8c3fa00 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java @@ -15,8 +15,8 @@ */ package org.springframework.statemachine.action; -import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ReactiveActionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ReactiveActionTests.java index dc802786..6f51a832 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ReactiveActionTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ReactiveActionTests.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -45,10 +46,9 @@ import reactor.core.publisher.Mono; */ public class ReactiveActionTests extends AbstractStateMachineTests { - @SuppressWarnings({ "unchecked" }) @Test - public void testSimpleReactiveAction() { + public void testSimpleReactiveActions() throws Exception { context.register(Config1.class); context.refresh(); assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); @@ -57,8 +57,19 @@ public class ReactiveActionTests extends AbstractStateMachineTests { machine.start(); TestCountAction testAction1 = context.getBean("testAction1", TestCountAction.class); + TestCountAction testAction2 = context.getBean("testAction2", TestCountAction.class); + TestCountAction testAction3 = context.getBean("testAction3", TestCountAction.class); + TestCountAction testAction4 = context.getBean("testAction4", TestCountAction.class); machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build()); + assertThat(testAction1.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(testAction2.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(testAction3.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(testAction4.latch.await(1, TimeUnit.SECONDS), is(true)); assertThat(testAction1.count, is(1)); + assertThat(testAction2.count, is(1)); + assertThat(testAction3.count, is(1)); + assertThat(testAction4.count, is(1)); } @Configuration @@ -70,7 +81,9 @@ public class ReactiveActionTests extends AbstractStateMachineTests { states .withStates() .initial(TestStates.S1) - .state(TestStates.S2); + .stateExitFunction(TestStates.S2, testAction2()) + .stateDoFunction(TestStates.S3, testAction3()) + .stateEntryFunction(TestStates.S3, testAction4()); } @Override @@ -80,13 +93,33 @@ public class ReactiveActionTests extends AbstractStateMachineTests { .source(TestStates.S1) .target(TestStates.S2) .event(TestEvents.E1) - .actionFunction(testAction1()); + .actionFunction(testAction1()) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2); } @Bean public TestCountAction testAction1() { return new TestCountAction(); } + + @Bean + public TestCountAction testAction2() { + return new TestCountAction(); + } + + @Bean + public TestCountAction testAction3() { + return new TestCountAction(); + } + + @Bean + public TestCountAction testAction4() { + return new TestCountAction(); + } } @Override diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurerTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurerTests.java index 529e3d99..f3cb3fe9 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurerTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurerTests.java @@ -34,6 +34,8 @@ import org.springframework.statemachine.config.builders.StateMachineStateBuilder import org.springframework.statemachine.config.model.StateData; import org.springframework.statemachine.state.PseudoStateKind; +import reactor.core.publisher.Mono; + public class DefaultStateConfigurerTests { @Test @@ -136,6 +138,26 @@ public class DefaultStateConfigurerTests { assertThat(builder.data.iterator().next().getEntryActions(), nullValue()); } + @Test + public void testStateActionFunctions() throws Exception { + DefaultStateConfigurer configurer = new DefaultStateConfigurer(); + TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder(); + configurer.stateDoFunction(TestStates.S2, context -> Mono.empty()); + configurer.stateEntryFunction(TestStates.S2, context -> Mono.empty()); + configurer.stateExitFunction(TestStates.S2, context -> Mono.empty()); + configurer.configure(builder); + assertThat(builder.data, notNullValue()); + assertThat(builder.data.size(), is(1)); + + assertThat(builder.data.iterator().next().getState(), is(TestStates.S2)); + assertThat(builder.data.iterator().next().getExitActions(), notNullValue()); + assertThat(builder.data.iterator().next().getExitActions().size(), is(1)); + assertThat(builder.data.iterator().next().getStateActions(), notNullValue()); + assertThat(builder.data.iterator().next().getStateActions().size(), is(1)); + assertThat(builder.data.iterator().next().getEntryActions(), notNullValue()); + assertThat(builder.data.iterator().next().getEntryActions().size(), is(1)); + } + @Test public void testEndStateNoState() throws Exception { DefaultStateConfigurer configurer = new DefaultStateConfigurer();