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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+