From cec0d2403570aa60d0605ca06f237e3acd13a37a Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Fri, 5 May 2017 14:40:00 +0100 Subject: [PATCH] Support actions with transition via choice/junction pseudostate - Adding support for javaconfig/uml to define actions for transitions leading out from a choice or junction states. - Support for action for incoming transition is already implemented as it is defined as normal transition. - Fixes #108 --- docs/src/reference/asciidoc/sm.adoc | 17 ++ .../config/AbstractStateMachineFactory.java | 6 +- .../ChoiceTransitionConfigurer.java | 79 +++++ .../DefaultChoiceTransitionConfigurer.java | 54 +++- .../DefaultJunctionTransitionConfigurer.java | 64 +++- .../JunctionTransitionConfigurer.java | 82 +++++- .../statemachine/config/model/ChoiceData.java | 28 +- .../config/model/JunctionData.java | 30 +- .../statemachine/state/ChoicePseudoState.java | 38 ++- .../state/JunctionPseudoState.java | 38 ++- .../docs/DocsConfigurationSampleTests.java | 42 +++ .../statemachine/state/ChoiceStateTests.java | 118 +++++++- .../state/JunctionStateTests.java | 117 ++++++++ .../uml/support/UmlModelParser.java | 12 +- .../uml/UmlStateMachineModelFactoryTests.java | 277 +++++++++++++++++- .../uml/action-with-transition-choice.di | 2 + .../action-with-transition-choice.notation | 265 +++++++++++++++++ .../uml/action-with-transition-choice.uml | 82 ++++++ .../uml/action-with-transition-junction.di | 2 + .../action-with-transition-junction.notation | 265 +++++++++++++++++ .../uml/action-with-transition-junction.uml | 82 ++++++ 21 files changed, 1665 insertions(+), 35 deletions(-) create mode 100644 spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.di create mode 100644 spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.notation create mode 100644 spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.uml create mode 100644 spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.di create mode 100644 spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.notation create mode 100644 spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.uml diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 069ed3c4..2b9353ae 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -329,6 +329,23 @@ Otherwise configuration is ill-formed. include::samples/DocsConfigurationSampleTests.java[tags=snippetS] ---- +Actions can be executed with both incoming and outgoing transitions of +a choice pseudostate. As seeing from below example, one dummy lambda +action is defined leading into a choice state and one similar dummy +lambda action defined for one outgoing transition where it also +define an error action. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetSSS] +---- + +[NOTE] +==== +Junction have same api format meaning actions can be defined +similarly. +==== + [[statemachine-config-states-junction]] ==== Junction State Junction needs to be defined in both states and transitions to work diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java index 7358fa72..d245bcf7 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -653,7 +653,7 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS if (holder.getState() == null) { holderMap.put(c.getTarget(), holder); } - choices.add(new ChoiceStateData(holder, c.getGuard())); + choices.add(new ChoiceStateData(holder, c.getGuard(), c.getActions())); } PseudoState pseudoState = new ChoicePseudoState(choices); state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(), @@ -669,7 +669,7 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS if (holder.getState() == null) { holderMap.put(c.getTarget(), holder); } - junctions.add(new JunctionStateData(holder, c.getGuard())); + junctions.add(new JunctionStateData(holder, c.getGuard(), c.getActions())); } PseudoState pseudoState = new JunctionPseudoState(junctions); state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(), diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ChoiceTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ChoiceTransitionConfigurer.java index 9b5ea47a..59cd751b 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ChoiceTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ChoiceTransitionConfigurer.java @@ -15,6 +15,7 @@ */ package org.springframework.statemachine.config.configurers; +import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; import org.springframework.statemachine.guard.Guard; @@ -51,6 +52,35 @@ public interface ChoiceTransitionConfigurer */ ChoiceTransitionConfigurer first(S target, Guard guard); + /** + * Specify a target state {@code S} as a first choice associating an + * {@link Action} to outgoing vertex. This must be set. + *

+ * In normal if/else if/else this would represent if. + *

+ * + * @param target the target state + * @param guard the guard for this choice + * @param action the action + * @return configurer for chaining + */ + ChoiceTransitionConfigurer first(S target, Guard guard, Action action); + + /** + * Specify a target state {@code S} as a first choice associating an + * {@link Action} to outgoing vertex. This must be set. + *

+ * In normal if/else if/else this would represent if. + *

+ * + * @param target the target state + * @param guard the guard for this choice + * @param action the action + * @param error action that will be called if any unexpected exception is thrown by the action. + * @return configurer for chaining + */ + ChoiceTransitionConfigurer first(S target, Guard guard, Action action, Action error); + /** * Specify a target state {@code S} as a then choice. * This is optional. Multiple thens will preserve order. @@ -62,6 +92,33 @@ public interface ChoiceTransitionConfigurer */ ChoiceTransitionConfigurer then(S target, Guard guard); + /** + * Specify a target state {@code S} as a then choice associating an + * {@link Action} to outgoing vertex. This is optional. Multiple thens + * will preserve order. + *

In normal if/else if/else this would represent else if.

+ * + * @param target the target state + * @param guard the guard for this choice + * @param action the action + * @return configurer for chaining + */ + ChoiceTransitionConfigurer then(S target, Guard guard, Action action); + + /** + * Specify a target state {@code S} as a then choice associating an + * {@link Action} to outgoing vertex. This is optional. Multiple thens + * will preserve order. + *

In normal if/else if/else this would represent else if.

+ * + * @param target the target state + * @param guard the guard for this choice + * @param action the action + * @param error action that will be called if any unexpected exception is thrown by the action. + * @return configurer for chaining + */ + ChoiceTransitionConfigurer then(S target, Guard guard, Action action, Action error); + /** * Specify a target state {@code S} as a last choice. * This must be set. @@ -72,4 +129,26 @@ public interface ChoiceTransitionConfigurer */ ChoiceTransitionConfigurer last(S target); + /** + * Specify a target state {@code S} as a last choice associating an + * {@link Action} to outgoing vertex. This must be set. + *

In normal if/else if/else this would represent else.

+ * + * @param target the target state + * @param action the action + * @return configurer for chaining + */ + ChoiceTransitionConfigurer last(S target, Action action); + + /** + * Specify a target state {@code S} as a last choice associating an + * {@link Action} to outgoing vertex. This must be set. + *

In normal if/else if/else this would represent else.

+ * + * @param target the target state + * @param action the action + * @param error action that will be called if any unexpected exception is thrown by the action. + * @return configurer for chaining + */ + ChoiceTransitionConfigurer last(S target, Action action, Action error); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultChoiceTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultChoiceTransitionConfigurer.java index 0efe60a4..04446ad5 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultChoiceTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultChoiceTransitionConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -16,8 +16,11 @@ package org.springframework.statemachine.config.configurers; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.action.Actions; import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; @@ -63,20 +66,61 @@ public class DefaultChoiceTransitionConfigurer @Override public ChoiceTransitionConfigurer first(S target, Guard guard) { - this.first = new ChoiceData(source, target, guard); + return first(target, guard, null); + } + + @Override + public ChoiceTransitionConfigurer first(S target, Guard guard, Action action) { + return first(target, guard, action, null); + } + + @Override + public ChoiceTransitionConfigurer first(S target, Guard guard, Action action, Action error) { + Collection> actions = new ArrayList<>(); + if (action != null) { + actions.add(error != null ? Actions.errorCallingAction(action, error) : action); + } + this.first = new ChoiceData(source, target, guard, actions); return this; } @Override public ChoiceTransitionConfigurer then(S target, Guard guard) { - thens.add(new ChoiceData(source, target, guard)); + return then(target, guard, null); + } + + @Override + public ChoiceTransitionConfigurer then(S target, Guard guard, Action action) { + return then(target, guard, action, null); + } + + @Override + public ChoiceTransitionConfigurer then(S target, Guard guard, Action action, Action error) { + Collection> actions = new ArrayList<>(); + if (action != null) { + actions.add(error != null ? Actions.errorCallingAction(action, error) : action); + } + thens.add(new ChoiceData(source, target, guard, actions)); return this; } @Override public ChoiceTransitionConfigurer last(S target) { - this.last = new ChoiceData(source, target, null); - return this; + return last(target, null); } + @Override + public ChoiceTransitionConfigurer last(S target, Action action) { + return last(target, action, null); + } + + @Override + public ChoiceTransitionConfigurer last(S target, Action action, Action error) { + Collection> actions = new ArrayList<>(); + if (action != null) { + actions.add(error != null ? Actions.errorCallingAction(action, error) : action); + } + this.last = new ChoiceData(source, target, null, actions); + return this; + } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultJunctionTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultJunctionTransitionConfigurer.java index ada1a505..861bda86 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultJunctionTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultJunctionTransitionConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -16,8 +16,11 @@ package org.springframework.statemachine.config.configurers; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.action.Actions; import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; @@ -44,15 +47,15 @@ public class DefaultJunctionTransitionConfigurer @Override public void configure(StateMachineTransitionBuilder builder) throws Exception { - List> Junctions = new ArrayList>(); + List> junctions = new ArrayList>(); if (first != null) { - Junctions.add(first); + junctions.add(first); } - Junctions.addAll(thens); + junctions.addAll(thens); if (last != null) { - Junctions.add(last); + junctions.add(last); } - builder.addJunction(source, Junctions); + builder.addJunction(source, junctions); } @Override @@ -63,20 +66,61 @@ public class DefaultJunctionTransitionConfigurer @Override public JunctionTransitionConfigurer first(S target, Guard guard) { - this.first = new JunctionData(source, target, guard); + return first(target, guard, null); + } + + @Override + public JunctionTransitionConfigurer first(S target, Guard guard, Action action) { + return first(target, guard, action, null); + } + + @Override + public JunctionTransitionConfigurer first(S target, Guard guard, Action action, Action error) { + Collection> actions = new ArrayList<>(); + if (action != null) { + actions.add(error != null ? Actions.errorCallingAction(action, error) : action); + } + this.first = new JunctionData(source, target, guard, actions); return this; } @Override public JunctionTransitionConfigurer then(S target, Guard guard) { - thens.add(new JunctionData(source, target, guard)); + return then(target, guard, null); + } + + @Override + public JunctionTransitionConfigurer then(S target, Guard guard, Action action) { + return then(target, guard, action, null); + } + + @Override + public JunctionTransitionConfigurer then(S target, Guard guard, Action action, Action error) { + Collection> actions = new ArrayList<>(); + if (action != null) { + actions.add(error != null ? Actions.errorCallingAction(action, error) : action); + } + thens.add(new JunctionData(source, target, guard, actions)); return this; } @Override public JunctionTransitionConfigurer last(S target) { - this.last = new JunctionData(source, target, null); - return this; + return last(target, null); } + @Override + public JunctionTransitionConfigurer last(S target, Action action) { + return last(target, action, null); + } + + @Override + public JunctionTransitionConfigurer last(S target, Action action, Action error) { + Collection> actions = new ArrayList<>(); + if (action != null) { + actions.add(error != null ? Actions.errorCallingAction(action, error) : action); + } + this.last = new JunctionData(source, target, null, actions); + return this; + } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/JunctionTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/JunctionTransitionConfigurer.java index 4abe869d..dfd34465 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/JunctionTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/JunctionTransitionConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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,7 @@ */ package org.springframework.statemachine.config.configurers; +import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; import org.springframework.statemachine.guard.Guard; @@ -51,6 +52,35 @@ public interface JunctionTransitionConfigurer */ JunctionTransitionConfigurer first(S target, Guard guard); + /** + * Specify a target state {@code S} as a first choice associating an + * {@link Action} to outgoing vertex. This must be set. + *

+ * In normal if/else if/else this would represent if. + *

+ * + * @param target the target state + * @param guard the guard for this choice + * @param action the action + * @return configurer for chaining + */ + JunctionTransitionConfigurer first(S target, Guard guard, Action action); + + /** + * Specify a target state {@code S} as a first choice associating an + * {@link Action} to outgoing vertex. This must be set. + *

+ * In normal if/else if/else this would represent if. + *

+ * + * @param target the target state + * @param guard the guard for this choice + * @param action the action + * @param error action that will be called if any unexpected exception is thrown by the action. + * @return configurer for chaining + */ + JunctionTransitionConfigurer first(S target, Guard guard, Action action, Action error); + /** * Specify a target state {@code S} as a then choice. * This is optional. Multiple thens will preserve order. @@ -62,6 +92,33 @@ public interface JunctionTransitionConfigurer */ JunctionTransitionConfigurer then(S target, Guard guard); + /** + * Specify a target state {@code S} as a then choice associating an + * {@link Action} to outgoing vertex. This is optional. Multiple thens + * will preserve order. + *

In normal if/else if/else this would represent else if.

+ * + * @param target the target state + * @param guard the guard for this choice + * @param action the action + * @return configurer for chaining + */ + JunctionTransitionConfigurer then(S target, Guard guard, Action action); + + /** + * Specify a target state {@code S} as a then choice associating an + * {@link Action} to outgoing vertex. This is optional. Multiple thens + * will preserve order. + *

In normal if/else if/else this would represent else if.

+ * + * @param target the target state + * @param guard the guard for this choice + * @param action the action + * @param error action that will be called if any unexpected exception is thrown by the action. + * @return configurer for chaining + */ + JunctionTransitionConfigurer then(S target, Guard guard, Action action, Action error); + /** * Specify a target state {@code S} as a last choice. * This must be set. @@ -72,4 +129,27 @@ public interface JunctionTransitionConfigurer */ JunctionTransitionConfigurer last(S target); + /** + * Specify a target state {@code S} as a last choice associating an + * {@link Action} to outgoing vertex. This must be set. + *

In normal if/else if/else this would represent else.

+ * + * @param target the target state + * @param action the action + * @return configurer for chaining + */ + JunctionTransitionConfigurer last(S target, Action action); + + /** + * Specify a target state {@code S} as a last choice associating an + * {@link Action} to outgoing vertex. This must be set. + *

In normal if/else if/else this would represent else.

+ * + * @param target the target state + * @param action the action + * @param error action that will be called if any unexpected exception is thrown by the action. + * @return configurer for chaining + */ + JunctionTransitionConfigurer last(S target, Action action, Action error); + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ChoiceData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ChoiceData.java index 3a5813a2..b642cec7 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ChoiceData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ChoiceData.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2017 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.config.model; +import java.util.Collection; + +import org.springframework.statemachine.action.Action; import org.springframework.statemachine.guard.Guard; /** @@ -27,6 +30,7 @@ public class ChoiceData { private final S source; private final S target; private final Guard guard; + private final Collection> actions; /** * Instantiates a new choice data. @@ -36,9 +40,22 @@ public class ChoiceData { * @param guard the guard */ public ChoiceData(S source, S target, Guard guard) { + this(source, target, guard, null); + } + + /** + * Instantiates a new choice data. + * + * @param source the source + * @param target the target + * @param guard the guard + * @param actions the actions + */ + public ChoiceData(S source, S target, Guard guard, Collection> actions) { this.source = source; this.target = target; this.guard = guard; + this.actions = actions; } /** @@ -67,4 +84,13 @@ public class ChoiceData { public Guard getGuard() { return guard; } + + /** + * Gets the actions. + * + * @return the actions + */ + public Collection> getActions() { + return actions; + } } \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/JunctionData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/JunctionData.java index 4bfdcda1..507aaa89 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/JunctionData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/JunctionData.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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.config.model; +import java.util.Collection; + +import org.springframework.statemachine.action.Action; import org.springframework.statemachine.guard.Guard; /** @@ -27,6 +30,7 @@ public class JunctionData { private final S source; private final S target; private final Guard guard; + private final Collection> actions; /** * Instantiates a new junction data. @@ -36,9 +40,22 @@ public class JunctionData { * @param guard the guard */ public JunctionData(S source, S target, Guard guard) { + this(source, target, guard, null); + } + + /** + * Instantiates a new junction data. + * + * @param source the source + * @param target the target + * @param guard the guard + * @param actions the actions + */ + public JunctionData(S source, S target, Guard guard, Collection> actions) { this.source = source; this.target = target; this.guard = guard; + this.actions = actions; } /** @@ -67,4 +84,13 @@ public class JunctionData { public Guard getGuard() { return guard; } -} \ No newline at end of file + + /** + * Gets the actions. + * + * @return the actions + */ + public Collection> getActions() { + return actions; + } +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ChoicePseudoState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ChoicePseudoState.java index 706b53d7..0d248e60 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ChoicePseudoState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ChoicePseudoState.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2017 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,11 +15,13 @@ */ package org.springframework.statemachine.state; +import java.util.Collection; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; import org.springframework.statemachine.guard.Guard; import org.springframework.util.Assert; @@ -53,12 +55,17 @@ public class ChoicePseudoState implements PseudoState { @Override public State entry(StateContext context) { State s = null; + ChoiceStateData csd = null; for (ChoiceStateData c : choices) { - s = c.getState(); + csd = c; if (c.guard != null && evaluateInternal(c.guard, context)) { break; } } + if (csd != null) { + s = csd.getState(); + executeActions(csd.getActions(), context); + } return s; } @@ -83,6 +90,19 @@ public class ChoicePseudoState implements PseudoState { } } + private void executeActions(Collection> actions, StateContext context) { + if (actions == null) { + return; + } + for (Action action : actions) { + try { + action.execute(context); + } catch (Throwable t) { + log.warn("Action execution resulted error", t); + } + } + } + /** * Data class wrapping choice {@link State} and {@link Guard} * together. @@ -93,17 +113,20 @@ public class ChoicePseudoState implements PseudoState { public static class ChoiceStateData { private final StateHolder state; private final Guard guard; + private final Collection> actions; /** * Instantiates a new choice state data. * * @param state the state holder * @param guard the guard + * @param actions the actions */ - public ChoiceStateData(StateHolder state, Guard guard) { + public ChoiceStateData(StateHolder state, Guard guard, Collection> actions) { Assert.notNull(state, "Holder must be set"); this.state = state; this.guard = guard; + this.actions = actions; } /** @@ -132,5 +155,14 @@ public class ChoicePseudoState implements PseudoState { public Guard getGuard() { return guard; } + + /** + * Gets the actions. + * + * @return the actions + */ + public Collection> getActions() { + return actions; + } } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/JunctionPseudoState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/JunctionPseudoState.java index 9698d0c9..535ead1e 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/JunctionPseudoState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/JunctionPseudoState.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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,11 +15,13 @@ */ package org.springframework.statemachine.state; +import java.util.Collection; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; import org.springframework.statemachine.guard.Guard; import org.springframework.util.Assert; @@ -53,12 +55,17 @@ public class JunctionPseudoState implements PseudoState { @Override public State entry(StateContext context) { State s = null; + JunctionStateData jsd = null; for (JunctionStateData j : junctions) { - s = j.getState(); + jsd = j; if (j.guard != null && evaluateInternal(j.guard, context)) { break; } } + if (jsd != null) { + s = jsd.getState(); + executeActions(jsd.getActions(), context); + } return s; } @@ -83,6 +90,19 @@ public class JunctionPseudoState implements PseudoState { } } + private void executeActions(Collection> actions, StateContext context) { + if (actions == null) { + return; + } + for (Action action : actions) { + try { + action.execute(context); + } catch (Throwable t) { + log.warn("Action execution resulted error", t); + } + } + } + /** * Data class wrapping choice {@link State} and {@link Guard} * together. @@ -93,17 +113,20 @@ public class JunctionPseudoState implements PseudoState { public static class JunctionStateData { private final StateHolder state; private final Guard guard; + private final Collection> actions; /** * Instantiates a new junction state data. * * @param state the state holder * @param guard the guard + * @param actions the actions */ - public JunctionStateData(StateHolder state, Guard guard) { + public JunctionStateData(StateHolder state, Guard guard, Collection> actions) { Assert.notNull(state, "Holder must be set"); this.state = state; this.guard = guard; + this.actions = actions; } /** @@ -132,5 +155,14 @@ public class JunctionPseudoState implements PseudoState { public Guard getGuard() { return guard; } + + /** + * Gets the actions. + * + * @return the actions + */ + public Collection> getActions() { + return actions; + } } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java index e9dc2091..96515d3c 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java @@ -747,6 +747,48 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { } // end::snippetS[] +// tag::snippetSSS[] + @Configuration + @EnableStateMachine + public class Config23 + extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) + throws Exception { + states + .withStates() + .initial(States.SI) + .choice(States.S1) + .end(States.SF) + .states(EnumSet.allOf(States.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) + throws Exception { + transitions + .withExternal() + .source(States.SI) + .action(c -> { + // action with SI-S1 + }) + .target(States.S1) + .and() + .withChoice() + .source(States.S1) + .first(States.S2, c -> { + return true; + }) + .last(States.S3, c -> { + // action with S1-S3 + }, c -> { + // error callback for action S1-S3 + }); + } + } +// end::snippetSSS[] + // tag::snippetSS[] @Configuration @EnableStateMachine diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ChoiceStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ChoiceStateTests.java index 0d4ded6a..7fa6f158 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ChoiceStateTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ChoiceStateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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,6 +23,8 @@ import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -33,6 +35,7 @@ import org.springframework.statemachine.AbstractStateMachineTests; import org.springframework.statemachine.ObjectStateMachine; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; @@ -156,6 +159,44 @@ public class ChoiceStateTests extends AbstractStateMachineTests { assertThat(listener.entered.size(), is(1)); } + @Test + @SuppressWarnings("unchecked") + public void testTransitionToChoiceActionCalled1() throws InterruptedException { + context.register(Config5.class); + context.refresh(); + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + LatchAction sIToChoice = context.getBean("sIToChoice", LatchAction.class); + LatchAction choiceToS30 = context.getBean("choiceToS30", LatchAction.class); + LatchAction choiceToS33 = context.getBean("choiceToS33", LatchAction.class); + assertThat(machine, notNullValue()); + machine.start(); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("choice", "s30").build()); + assertThat(sIToChoice.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS30.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS33.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(machine.getState().getIds(), contains(TestStates.S30)); + } + + @Test + @SuppressWarnings("unchecked") + public void testTransitionToChoiceActionCalled2() throws InterruptedException { + context.register(Config5.class); + context.refresh(); + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + LatchAction sIToChoice = context.getBean("sIToChoice", LatchAction.class); + LatchAction choiceToS30 = context.getBean("choiceToS30", LatchAction.class); + LatchAction choiceToS33 = context.getBean("choiceToS33", LatchAction.class); + assertThat(machine, notNullValue()); + machine.start(); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(sIToChoice.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS30.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(choiceToS33.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(machine.getState().getIds(), contains(TestStates.S33)); + } + @Configuration @EnableStateMachine static class Config1 extends EnumStateMachineConfigurerAdapter { @@ -324,6 +365,73 @@ public class ChoiceStateTests extends AbstractStateMachineTests { } } + @Configuration + @EnableStateMachine + static class Config5 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.SI) + .states(EnumSet.allOf(TestStates.class)) + .choice(TestStates.S3) + .end(TestStates.SF); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.S3) + .action(sIToChoice()) + .event(TestEvents.E1) + .and() + .withChoice() + .source(TestStates.S3) + .first(TestStates.S30, s30Guard(), choiceToS30()) + .then(TestStates.S31, s31Guard()) + .then(TestStates.S32, s32Guard()) + .last(TestStates.S33, choiceToS33(), choiceToS33Error()); + } + + @Bean + public Guard s30Guard() { + return new ChoiceGuard("s30"); + } + + @Bean + public Guard s31Guard() { + return new ChoiceGuard("s31"); + } + + @Bean + public Guard s32Guard() { + return new ChoiceGuard("s32"); + } + + @Bean + public Action sIToChoice() { + return new LatchAction(); + } + + @Bean + public Action choiceToS30() { + return new LatchAction(); + } + + @Bean + public Action choiceToS33() { + return new LatchAction(); + } + + @Bean + public Action choiceToS33Error() { + return new LatchAction(); + } + } + private static class TestStateEntryExitListener extends StateMachineListenerAdapter { List> entered = new ArrayList<>(); @@ -359,4 +467,12 @@ public class ChoiceStateTests extends AbstractStateMachineTests { } } + private static class LatchAction implements Action { + CountDownLatch latch = new CountDownLatch(1); + + @Override + public void execute(StateContext context) { + latch.countDown(); + } + } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/JunctionStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/JunctionStateTests.java index daaac1db..898b4211 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/JunctionStateTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/JunctionStateTests.java @@ -16,10 +16,13 @@ package org.springframework.statemachine.state; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import java.util.EnumSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -30,6 +33,7 @@ import org.springframework.statemachine.AbstractStateMachineTests; import org.springframework.statemachine.ObjectStateMachine; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; @@ -128,6 +132,44 @@ public class JunctionStateTests extends AbstractStateMachineTests { assertThat(machine.getState().getIds(), contains(TestStates.S21)); } + @Test + @SuppressWarnings("unchecked") + public void testTransitionToJunctionActionCalled1() throws InterruptedException { + context.register(Config4.class); + context.refresh(); + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + LatchAction sIToJunction = context.getBean("sIToJunction", LatchAction.class); + LatchAction junctionToS30 = context.getBean("junctionToS30", LatchAction.class); + LatchAction junctionToS33 = context.getBean("junctionToS33", LatchAction.class); + assertThat(machine, notNullValue()); + machine.start(); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("junction", "s30").build()); + assertThat(sIToJunction.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(junctionToS30.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(junctionToS33.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(machine.getState().getIds(), contains(TestStates.S30)); + } + + @Test + @SuppressWarnings("unchecked") + public void testTransitionToJunctionActionCalled2() throws InterruptedException { + context.register(Config4.class); + context.refresh(); + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + LatchAction sIToJunction = context.getBean("sIToJunction", LatchAction.class); + LatchAction junctionToS30 = context.getBean("junctionToS30", LatchAction.class); + LatchAction junctionToS33 = context.getBean("junctionToS33", LatchAction.class); + assertThat(machine, notNullValue()); + machine.start(); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(sIToJunction.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(junctionToS30.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(junctionToS33.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(machine.getState().getIds(), contains(TestStates.S33)); + } + @Configuration @EnableStateMachine static class Config1 extends EnumStateMachineConfigurerAdapter { @@ -264,6 +306,73 @@ public class JunctionStateTests extends AbstractStateMachineTests { } } + @Configuration + @EnableStateMachine + static class Config4 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.SI) + .states(EnumSet.allOf(TestStates.class)) + .junction(TestStates.S3) + .end(TestStates.SF); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.S3) + .action(sIToJunction()) + .event(TestEvents.E1) + .and() + .withJunction() + .source(TestStates.S3) + .first(TestStates.S30, s30Guard(), junctionToS30()) + .then(TestStates.S31, s31Guard()) + .then(TestStates.S32, s32Guard()) + .last(TestStates.S33, junctionToS33(), junctionToS33Error()); + } + + @Bean + public Guard s30Guard() { + return new JunctionGuard("s30"); + } + + @Bean + public Guard s31Guard() { + return new JunctionGuard("s31"); + } + + @Bean + public Guard s32Guard() { + return new JunctionGuard("s32"); + } + + @Bean + public Action sIToJunction() { + return new LatchAction(); + } + + @Bean + public Action junctionToS30() { + return new LatchAction(); + } + + @Bean + public Action junctionToS33() { + return new LatchAction(); + } + + @Bean + public Action junctionToS33Error() { + return new LatchAction(); + } + } + private static class JunctionGuard implements Guard { private final String match; @@ -278,4 +387,12 @@ public class JunctionStateTests extends AbstractStateMachineTests { } } + private static class LatchAction implements Action { + CountDownLatch latch = new CountDownLatch(1); + + @Override + public void execute(StateContext context) { + latch.countDown(); + } + } } diff --git a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java index 2f604f9a..408dd6fd 100644 --- a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java +++ b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -288,11 +288,12 @@ public class UmlModelParser { choices.put(transition.getSource().getName(), list); } Guard guard = resolveGuard(transition); + Collection> actions = UmlUtils.resolveTransitionActions(transition, resolver); // we want null guards to be at the end if (guard == null) { - list.addLast(new ChoiceData(transition.getSource().getName(), transition.getTarget().getName(), guard)); + list.addLast(new ChoiceData(transition.getSource().getName(), transition.getTarget().getName(), guard, actions)); } else { - list.addFirst(new ChoiceData(transition.getSource().getName(), transition.getTarget().getName(), guard)); + list.addFirst(new ChoiceData(transition.getSource().getName(), transition.getTarget().getName(), guard, actions)); } } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.JUNCTION_LITERAL) { LinkedList> list = junctions.get(transition.getSource().getName()); @@ -301,11 +302,12 @@ public class UmlModelParser { junctions.put(transition.getSource().getName(), list); } Guard guard = resolveGuard(transition); + Collection> actions = UmlUtils.resolveTransitionActions(transition, resolver); // we want null guards to be at the end if (guard == null) { - list.addLast(new JunctionData(transition.getSource().getName(), transition.getTarget().getName(), guard)); + list.addLast(new JunctionData(transition.getSource().getName(), transition.getTarget().getName(), guard, actions)); } else { - list.addFirst(new JunctionData(transition.getSource().getName(), transition.getTarget().getName(), guard)); + list.addFirst(new JunctionData(transition.getSource().getName(), transition.getTarget().getName(), guard, actions)); } } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.FORK_LITERAL) { List list = forks.get(transition.getSource().getName()); diff --git a/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java b/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java index 25f79079..68bb153b 100644 --- a/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java +++ b/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -926,6 +926,144 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { assertThat(stateMachineModel.getTransitionsData().getExits().size(), is(1)); } + @Test + @SuppressWarnings("unchecked") + public void testActionWithTransitionChoice1() throws InterruptedException { + context.register(Config25.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + LatchAction s1ToChoice = context.getBean("s1ToChoice", LatchAction.class); + LatchAction choiceToS2 = context.getBean("choiceToS2", LatchAction.class); + LatchAction choiceToS4 = context.getBean("choiceToS4", LatchAction.class); + + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent(MessageBuilder.withPayload("E1").setHeader("choice", "s2").build()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2")); + + assertThat(s1ToChoice.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS2.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS4.latch.await(1, TimeUnit.SECONDS), is(false)); + } + + @Test + @SuppressWarnings("unchecked") + public void testActionWithTransitionChoice2() throws InterruptedException { + context.register(Config25.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + LatchAction s1ToChoice = context.getBean("s1ToChoice", LatchAction.class); + LatchAction choiceToS2 = context.getBean("choiceToS2", LatchAction.class); + LatchAction choiceToS4 = context.getBean("choiceToS4", LatchAction.class); + + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent(MessageBuilder.withPayload("E1").build()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S4")); + + assertThat(s1ToChoice.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS2.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(choiceToS4.latch.await(1, TimeUnit.SECONDS), is(true)); + } + + @Test + @SuppressWarnings("unchecked") + public void testActionWithTransitionChoice3() throws InterruptedException { + context.register(Config25.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + LatchAction s1ToChoice = context.getBean("s1ToChoice", LatchAction.class); + LatchAction choiceToS2 = context.getBean("choiceToS2", LatchAction.class); + LatchAction choiceToS4 = context.getBean("choiceToS4", LatchAction.class); + LatchAction choice1ToChoice2 = context.getBean("choice1ToChoice2", LatchAction.class); + LatchAction choiceToS5 = context.getBean("choiceToS5", LatchAction.class); + LatchAction choiceToS6 = context.getBean("choiceToS6", LatchAction.class); + + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent(MessageBuilder.withPayload("E1").setHeader("choice", "choice2").build()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S6")); + + assertThat(s1ToChoice.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS2.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(choiceToS4.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(choice1ToChoice2.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS5.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(choiceToS6.latch.await(1, TimeUnit.SECONDS), is(true)); + } + + @Test + @SuppressWarnings("unchecked") + public void testActionWithTransitionJunction1() throws InterruptedException { + context.register(Config26.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + LatchAction s1ToChoice = context.getBean("s1ToChoice", LatchAction.class); + LatchAction choiceToS2 = context.getBean("choiceToS2", LatchAction.class); + LatchAction choiceToS4 = context.getBean("choiceToS4", LatchAction.class); + + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent(MessageBuilder.withPayload("E1").setHeader("choice", "s2").build()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2")); + + assertThat(s1ToChoice.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS2.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS4.latch.await(1, TimeUnit.SECONDS), is(false)); + } + + @Test + @SuppressWarnings("unchecked") + public void testActionWithTransitionJunction2() throws InterruptedException { + context.register(Config26.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + LatchAction s1ToChoice = context.getBean("s1ToChoice", LatchAction.class); + LatchAction choiceToS2 = context.getBean("choiceToS2", LatchAction.class); + LatchAction choiceToS4 = context.getBean("choiceToS4", LatchAction.class); + + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent(MessageBuilder.withPayload("E1").build()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S4")); + + assertThat(s1ToChoice.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS2.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(choiceToS4.latch.await(1, TimeUnit.SECONDS), is(true)); + } + + @Test + @SuppressWarnings("unchecked") + public void testActionWithTransitionJunction3() throws InterruptedException { + context.register(Config26.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + LatchAction s1ToChoice = context.getBean("s1ToChoice", LatchAction.class); + LatchAction choiceToS2 = context.getBean("choiceToS2", LatchAction.class); + LatchAction choiceToS4 = context.getBean("choiceToS4", LatchAction.class); + LatchAction choice1ToChoice2 = context.getBean("choice1ToChoice2", LatchAction.class); + LatchAction choiceToS5 = context.getBean("choiceToS5", LatchAction.class); + LatchAction choiceToS6 = context.getBean("choiceToS6", LatchAction.class); + + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent(MessageBuilder.withPayload("E1").setHeader("choice", "choice2").build()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S6")); + + assertThat(s1ToChoice.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS2.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(choiceToS4.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(choice1ToChoice2.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(choiceToS5.latch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(choiceToS6.latch.await(1, TimeUnit.SECONDS), is(true)); + } + @Configuration @EnableStateMachine public static class Config2 extends StateMachineConfigurerAdapter { @@ -1398,6 +1536,143 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { } } + @Configuration + @EnableStateMachine + public static class Config25 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + model + .withModel() + .factory(modelFactory()); + } + + @Bean + public StateMachineModelFactory modelFactory() { + Resource model = new ClassPathResource("org/springframework/statemachine/uml/action-with-transition-choice.uml"); + return new UmlStateMachineModelFactory(model); + } + + @Bean + public ChoiceGuard s2Guard() { + return new ChoiceGuard("s2"); + } + + @Bean + public ChoiceGuard s3Guard() { + return new ChoiceGuard("s3"); + } + + @Bean + public ChoiceGuard s5Guard() { + return new ChoiceGuard("s5"); + } + + @Bean + public ChoiceGuard choice2Guard() { + return new ChoiceGuard("choice2"); + } + + @Bean + public LatchAction s1ToChoice() { + return new LatchAction(); + } + + @Bean + public LatchAction choiceToS2() { + return new LatchAction(); + } + + @Bean + public LatchAction choiceToS4() { + return new LatchAction(); + } + + @Bean + public LatchAction choice1ToChoice2() { + return new LatchAction(); + } + + @Bean + public LatchAction choiceToS5() { + return new LatchAction(); + } + + @Bean + public LatchAction choiceToS6() { + return new LatchAction(); + } + } + + @Configuration + @EnableStateMachine + public static class Config26 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + model + .withModel() + .factory(modelFactory()); + } + + @Bean + public StateMachineModelFactory modelFactory() { + Resource model = new ClassPathResource("org/springframework/statemachine/uml/action-with-transition-junction.uml"); + return new UmlStateMachineModelFactory(model); + } + + @Bean + public ChoiceGuard s2Guard() { + return new ChoiceGuard("s2"); + } + + @Bean + public ChoiceGuard s3Guard() { + return new ChoiceGuard("s3"); + } + + @Bean + public ChoiceGuard s5Guard() { + return new ChoiceGuard("s5"); + } + + @Bean + public ChoiceGuard choice2Guard() { + return new ChoiceGuard("choice2"); + } + + @Bean + public LatchAction s1ToChoice() { + return new LatchAction(); + } + + @Bean + public LatchAction choiceToS2() { + return new LatchAction(); + } + + @Bean + public LatchAction choiceToS4() { + return new LatchAction(); + } + + @Bean + public LatchAction choice1ToChoice2() { + return new LatchAction(); + } + + @Bean + public LatchAction choiceToS5() { + return new LatchAction(); + } + + @Bean + public LatchAction choiceToS6() { + return new LatchAction(); + } + + } + public static class LatchAction implements Action { CountDownLatch latch = new CountDownLatch(1); @Override diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.notation new file mode 100644 index 00000000..882e029e --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.notation @@ -0,0 +1,265 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.uml new file mode 100644 index 00000000..c2b19eef --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-choice.uml @@ -0,0 +1,82 @@ + + + + + + + + bean + s1ToChoice + + + + + + + bean + s2Guard + + + + bean + choiceToS2 + + + + + + bean + s3Guard + + + + + + bean + choiceToS4 + + + + + + bean + choice2Guard + + + + bean + choice1ToChoice2 + + + + + + bean + s5Guard + + + + bean + choiceToS5 + + + + + bean + choiceToS6 + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.notation new file mode 100644 index 00000000..62350cf8 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.notation @@ -0,0 +1,265 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.uml new file mode 100644 index 00000000..b6fa5445 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/action-with-transition-junction.uml @@ -0,0 +1,82 @@ + + + + + + + + bean + s1ToChoice + + + + + + + bean + s2Guard + + + + bean + choiceToS2 + + + + + + bean + s3Guard + + + + + + bean + choiceToS4 + + + + + + bean + choice2Guard + + + + bean + choice1ToChoice2 + + + + + + bean + s5Guard + + + + bean + choiceToS5 + + + + + bean + choiceToS6 + + + + + + + + + + + + + + + +