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
This commit is contained in:
Janne Valkealahti
2017-05-05 14:40:00 +01:00
parent c0cb4dee44
commit cec0d24035
21 changed files with 1665 additions and 35 deletions

View File

@@ -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<S, E> extends LifecycleObjectS
if (holder.getState() == null) {
holderMap.put(c.getTarget(), holder);
}
choices.add(new ChoiceStateData<S, E>(holder, c.getGuard()));
choices.add(new ChoiceStateData<S, E>(holder, c.getGuard(), c.getActions()));
}
PseudoState<S, E> pseudoState = new ChoicePseudoState<S, E>(choices);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
@@ -669,7 +669,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
if (holder.getState() == null) {
holderMap.put(c.getTarget(), holder);
}
junctions.add(new JunctionStateData<S, E>(holder, c.getGuard()));
junctions.add(new JunctionStateData<S, E>(holder, c.getGuard(), c.getActions()));
}
PseudoState<S, E> pseudoState = new JunctionPseudoState<S, E>(junctions);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),

View File

@@ -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<S, E>
*/
ChoiceTransitionConfigurer<S, E> first(S target, Guard<S, E> guard);
/**
* Specify a target state {@code S} as a first choice associating an
* {@link Action} to outgoing vertex. This must be set.
* <p>
* In normal if/else if/else this would represent if.
* </p>
*
* @param target the target state
* @param guard the guard for this choice
* @param action the action
* @return configurer for chaining
*/
ChoiceTransitionConfigurer<S, E> first(S target, Guard<S, E> guard, Action<S, E> action);
/**
* Specify a target state {@code S} as a first choice associating an
* {@link Action} to outgoing vertex. This must be set.
* <p>
* In normal if/else if/else this would represent if.
* </p>
*
* @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<S, E> first(S target, Guard<S, E> guard, Action<S, E> action, Action<S, E> 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<S, E>
*/
ChoiceTransitionConfigurer<S, E> then(S target, Guard<S, E> 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.
* <p>In normal if/else if/else this would represent else if.</p>
*
* @param target the target state
* @param guard the guard for this choice
* @param action the action
* @return configurer for chaining
*/
ChoiceTransitionConfigurer<S, E> then(S target, Guard<S, E> guard, Action<S, E> 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.
* <p>In normal if/else if/else this would represent else if.</p>
*
* @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<S, E> then(S target, Guard<S, E> guard, Action<S, E> action, Action<S, E> error);
/**
* Specify a target state {@code S} as a last choice.
* This must be set.
@@ -72,4 +129,26 @@ public interface ChoiceTransitionConfigurer<S, E>
*/
ChoiceTransitionConfigurer<S, E> last(S target);
/**
* Specify a target state {@code S} as a last choice associating an
* {@link Action} to outgoing vertex. This must be set.
* <p>In normal if/else if/else this would represent else.</p>
*
* @param target the target state
* @param action the action
* @return configurer for chaining
*/
ChoiceTransitionConfigurer<S, E> last(S target, Action<S, E> action);
/**
* Specify a target state {@code S} as a last choice associating an
* {@link Action} to outgoing vertex. This must be set.
* <p>In normal if/else if/else this would represent else.</p>
*
* @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<S, E> last(S target, Action<S, E> action, Action<S, E> error);
}

View File

@@ -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<S, E>
@Override
public ChoiceTransitionConfigurer<S, E> first(S target, Guard<S, E> guard) {
this.first = new ChoiceData<S, E>(source, target, guard);
return first(target, guard, null);
}
@Override
public ChoiceTransitionConfigurer<S, E> first(S target, Guard<S, E> guard, Action<S, E> action) {
return first(target, guard, action, null);
}
@Override
public ChoiceTransitionConfigurer<S, E> first(S target, Guard<S, E> guard, Action<S, E> action, Action<S, E> error) {
Collection<Action<S, E>> actions = new ArrayList<>();
if (action != null) {
actions.add(error != null ? Actions.errorCallingAction(action, error) : action);
}
this.first = new ChoiceData<S, E>(source, target, guard, actions);
return this;
}
@Override
public ChoiceTransitionConfigurer<S, E> then(S target, Guard<S, E> guard) {
thens.add(new ChoiceData<S, E>(source, target, guard));
return then(target, guard, null);
}
@Override
public ChoiceTransitionConfigurer<S, E> then(S target, Guard<S, E> guard, Action<S, E> action) {
return then(target, guard, action, null);
}
@Override
public ChoiceTransitionConfigurer<S, E> then(S target, Guard<S, E> guard, Action<S, E> action, Action<S, E> error) {
Collection<Action<S, E>> actions = new ArrayList<>();
if (action != null) {
actions.add(error != null ? Actions.errorCallingAction(action, error) : action);
}
thens.add(new ChoiceData<S, E>(source, target, guard, actions));
return this;
}
@Override
public ChoiceTransitionConfigurer<S, E> last(S target) {
this.last = new ChoiceData<S, E>(source, target, null);
return this;
return last(target, null);
}
@Override
public ChoiceTransitionConfigurer<S, E> last(S target, Action<S, E> action) {
return last(target, action, null);
}
@Override
public ChoiceTransitionConfigurer<S, E> last(S target, Action<S, E> action, Action<S, E> error) {
Collection<Action<S, E>> actions = new ArrayList<>();
if (action != null) {
actions.add(error != null ? Actions.errorCallingAction(action, error) : action);
}
this.last = new ChoiceData<S, E>(source, target, null, actions);
return this;
}
}

View File

@@ -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<S, E>
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
List<JunctionData<S, E>> Junctions = new ArrayList<JunctionData<S, E>>();
List<JunctionData<S, E>> junctions = new ArrayList<JunctionData<S, E>>();
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<S, E>
@Override
public JunctionTransitionConfigurer<S, E> first(S target, Guard<S, E> guard) {
this.first = new JunctionData<S, E>(source, target, guard);
return first(target, guard, null);
}
@Override
public JunctionTransitionConfigurer<S, E> first(S target, Guard<S, E> guard, Action<S, E> action) {
return first(target, guard, action, null);
}
@Override
public JunctionTransitionConfigurer<S, E> first(S target, Guard<S, E> guard, Action<S, E> action, Action<S, E> error) {
Collection<Action<S, E>> actions = new ArrayList<>();
if (action != null) {
actions.add(error != null ? Actions.errorCallingAction(action, error) : action);
}
this.first = new JunctionData<S, E>(source, target, guard, actions);
return this;
}
@Override
public JunctionTransitionConfigurer<S, E> then(S target, Guard<S, E> guard) {
thens.add(new JunctionData<S, E>(source, target, guard));
return then(target, guard, null);
}
@Override
public JunctionTransitionConfigurer<S, E> then(S target, Guard<S, E> guard, Action<S, E> action) {
return then(target, guard, action, null);
}
@Override
public JunctionTransitionConfigurer<S, E> then(S target, Guard<S, E> guard, Action<S, E> action, Action<S, E> error) {
Collection<Action<S, E>> actions = new ArrayList<>();
if (action != null) {
actions.add(error != null ? Actions.errorCallingAction(action, error) : action);
}
thens.add(new JunctionData<S, E>(source, target, guard, actions));
return this;
}
@Override
public JunctionTransitionConfigurer<S, E> last(S target) {
this.last = new JunctionData<S, E>(source, target, null);
return this;
return last(target, null);
}
@Override
public JunctionTransitionConfigurer<S, E> last(S target, Action<S, E> action) {
return last(target, action, null);
}
@Override
public JunctionTransitionConfigurer<S, E> last(S target, Action<S, E> action, Action<S, E> error) {
Collection<Action<S, E>> actions = new ArrayList<>();
if (action != null) {
actions.add(error != null ? Actions.errorCallingAction(action, error) : action);
}
this.last = new JunctionData<S, E>(source, target, null, actions);
return this;
}
}

View File

@@ -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<S, E>
*/
JunctionTransitionConfigurer<S, E> first(S target, Guard<S, E> guard);
/**
* Specify a target state {@code S} as a first choice associating an
* {@link Action} to outgoing vertex. This must be set.
* <p>
* In normal if/else if/else this would represent if.
* </p>
*
* @param target the target state
* @param guard the guard for this choice
* @param action the action
* @return configurer for chaining
*/
JunctionTransitionConfigurer<S, E> first(S target, Guard<S, E> guard, Action<S, E> action);
/**
* Specify a target state {@code S} as a first choice associating an
* {@link Action} to outgoing vertex. This must be set.
* <p>
* In normal if/else if/else this would represent if.
* </p>
*
* @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<S, E> first(S target, Guard<S, E> guard, Action<S, E> action, Action<S, E> 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<S, E>
*/
JunctionTransitionConfigurer<S, E> then(S target, Guard<S, E> 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.
* <p>In normal if/else if/else this would represent else if.</p>
*
* @param target the target state
* @param guard the guard for this choice
* @param action the action
* @return configurer for chaining
*/
JunctionTransitionConfigurer<S, E> then(S target, Guard<S, E> guard, Action<S, E> 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.
* <p>In normal if/else if/else this would represent else if.</p>
*
* @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<S, E> then(S target, Guard<S, E> guard, Action<S, E> action, Action<S, E> error);
/**
* Specify a target state {@code S} as a last choice.
* This must be set.
@@ -72,4 +129,27 @@ public interface JunctionTransitionConfigurer<S, E>
*/
JunctionTransitionConfigurer<S, E> last(S target);
/**
* Specify a target state {@code S} as a last choice associating an
* {@link Action} to outgoing vertex. This must be set.
* <p>In normal if/else if/else this would represent else.</p>
*
* @param target the target state
* @param action the action
* @return configurer for chaining
*/
JunctionTransitionConfigurer<S, E> last(S target, Action<S, E> action);
/**
* Specify a target state {@code S} as a last choice associating an
* {@link Action} to outgoing vertex. This must be set.
* <p>In normal if/else if/else this would represent else.</p>
*
* @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<S, E> last(S target, Action<S, E> action, Action<S, E> error);
}

View File

@@ -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<S, E> {
private final S source;
private final S target;
private final Guard<S, E> guard;
private final Collection<Action<S, E>> actions;
/**
* Instantiates a new choice data.
@@ -36,9 +40,22 @@ public class ChoiceData<S, E> {
* @param guard the guard
*/
public ChoiceData(S source, S target, Guard<S, E> 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<S, E> guard, Collection<Action<S, E>> actions) {
this.source = source;
this.target = target;
this.guard = guard;
this.actions = actions;
}
/**
@@ -67,4 +84,13 @@ public class ChoiceData<S, E> {
public Guard<S, E> getGuard() {
return guard;
}
/**
* Gets the actions.
*
* @return the actions
*/
public Collection<Action<S, E>> getActions() {
return actions;
}
}

View File

@@ -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<S, E> {
private final S source;
private final S target;
private final Guard<S, E> guard;
private final Collection<Action<S, E>> actions;
/**
* Instantiates a new junction data.
@@ -36,9 +40,22 @@ public class JunctionData<S, E> {
* @param guard the guard
*/
public JunctionData(S source, S target, Guard<S, E> 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<S, E> guard, Collection<Action<S, E>> actions) {
this.source = source;
this.target = target;
this.guard = guard;
this.actions = actions;
}
/**
@@ -67,4 +84,13 @@ public class JunctionData<S, E> {
public Guard<S, E> getGuard() {
return guard;
}
}
/**
* Gets the actions.
*
* @return the actions
*/
public Collection<Action<S, E>> getActions() {
return actions;
}
}

View File

@@ -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<S, E> implements PseudoState<S, E> {
@Override
public State<S, E> entry(StateContext<S, E> context) {
State<S, E> s = null;
ChoiceStateData<S, E> csd = null;
for (ChoiceStateData<S, E> 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<S, E> implements PseudoState<S, E> {
}
}
private void executeActions(Collection<Action<S, E>> actions, StateContext<S, E> context) {
if (actions == null) {
return;
}
for (Action<S, E> 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<S, E> implements PseudoState<S, E> {
public static class ChoiceStateData<S, E> {
private final StateHolder<S, E> state;
private final Guard<S, E> guard;
private final Collection<Action<S, E>> actions;
/**
* Instantiates a new choice state data.
*
* @param state the state holder
* @param guard the guard
* @param actions the actions
*/
public ChoiceStateData(StateHolder<S, E> state, Guard<S, E> guard) {
public ChoiceStateData(StateHolder<S, E> state, Guard<S, E> guard, Collection<Action<S, E>> actions) {
Assert.notNull(state, "Holder must be set");
this.state = state;
this.guard = guard;
this.actions = actions;
}
/**
@@ -132,5 +155,14 @@ public class ChoicePseudoState<S, E> implements PseudoState<S, E> {
public Guard<S, E> getGuard() {
return guard;
}
/**
* Gets the actions.
*
* @return the actions
*/
public Collection<Action<S, E>> getActions() {
return actions;
}
}
}

View File

@@ -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<S, E> implements PseudoState<S, E> {
@Override
public State<S, E> entry(StateContext<S, E> context) {
State<S, E> s = null;
JunctionStateData<S, E> jsd = null;
for (JunctionStateData<S, E> 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<S, E> implements PseudoState<S, E> {
}
}
private void executeActions(Collection<Action<S, E>> actions, StateContext<S, E> context) {
if (actions == null) {
return;
}
for (Action<S, E> 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<S, E> implements PseudoState<S, E> {
public static class JunctionStateData<S, E> {
private final StateHolder<S, E> state;
private final Guard<S, E> guard;
private final Collection<Action<S, E>> actions;
/**
* Instantiates a new junction state data.
*
* @param state the state holder
* @param guard the guard
* @param actions the actions
*/
public JunctionStateData(StateHolder<S, E> state, Guard<S, E> guard) {
public JunctionStateData(StateHolder<S, E> state, Guard<S, E> guard, Collection<Action<S, E>> actions) {
Assert.notNull(state, "Holder must be set");
this.state = state;
this.guard = guard;
this.actions = actions;
}
/**
@@ -132,5 +155,14 @@ public class JunctionPseudoState<S, E> implements PseudoState<S, E> {
public Guard<S, E> getGuard() {
return guard;
}
/**
* Gets the actions.
*
* @return the actions
*/
public Collection<Action<S, E>> getActions() {
return actions;
}
}
}

View File

@@ -747,6 +747,48 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
}
// end::snippetS[]
// tag::snippetSSS[]
@Configuration
@EnableStateMachine
public class Config23
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.SI)
.choice(States.S1)
.end(States.SF)
.states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> 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

View File

@@ -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<TestStates,TestEvents> 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<TestStates,TestEvents> 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<TestStates, TestEvents> {
@@ -324,6 +365,73 @@ public class ChoiceStateTests extends AbstractStateMachineTests {
}
}
@Configuration
@EnableStateMachine
static class Config5 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.SI)
.states(EnumSet.allOf(TestStates.class))
.choice(TestStates.S3)
.end(TestStates.SF);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> 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<TestStates, TestEvents> s30Guard() {
return new ChoiceGuard("s30");
}
@Bean
public Guard<TestStates, TestEvents> s31Guard() {
return new ChoiceGuard("s31");
}
@Bean
public Guard<TestStates, TestEvents> s32Guard() {
return new ChoiceGuard("s32");
}
@Bean
public Action<TestStates, TestEvents> sIToChoice() {
return new LatchAction();
}
@Bean
public Action<TestStates, TestEvents> choiceToS30() {
return new LatchAction();
}
@Bean
public Action<TestStates, TestEvents> choiceToS33() {
return new LatchAction();
}
@Bean
public Action<TestStates, TestEvents> choiceToS33Error() {
return new LatchAction();
}
}
private static class TestStateEntryExitListener extends StateMachineListenerAdapter<TestStates, TestEvents> {
List<State<TestStates, TestEvents>> entered = new ArrayList<>();
@@ -359,4 +467,12 @@ public class ChoiceStateTests extends AbstractStateMachineTests {
}
}
private static class LatchAction implements Action<TestStates, TestEvents> {
CountDownLatch latch = new CountDownLatch(1);
@Override
public void execute(StateContext<TestStates, TestEvents> context) {
latch.countDown();
}
}
}

View File

@@ -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<TestStates,TestEvents> 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<TestStates,TestEvents> 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<TestStates, TestEvents> {
@@ -264,6 +306,73 @@ public class JunctionStateTests extends AbstractStateMachineTests {
}
}
@Configuration
@EnableStateMachine
static class Config4 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.SI)
.states(EnumSet.allOf(TestStates.class))
.junction(TestStates.S3)
.end(TestStates.SF);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> 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<TestStates, TestEvents> s30Guard() {
return new JunctionGuard("s30");
}
@Bean
public Guard<TestStates, TestEvents> s31Guard() {
return new JunctionGuard("s31");
}
@Bean
public Guard<TestStates, TestEvents> s32Guard() {
return new JunctionGuard("s32");
}
@Bean
public Action<TestStates, TestEvents> sIToJunction() {
return new LatchAction();
}
@Bean
public Action<TestStates, TestEvents> junctionToS30() {
return new LatchAction();
}
@Bean
public Action<TestStates, TestEvents> junctionToS33() {
return new LatchAction();
}
@Bean
public Action<TestStates, TestEvents> junctionToS33Error() {
return new LatchAction();
}
}
private static class JunctionGuard implements Guard<TestStates, TestEvents> {
private final String match;
@@ -278,4 +387,12 @@ public class JunctionStateTests extends AbstractStateMachineTests {
}
}
private static class LatchAction implements Action<TestStates, TestEvents> {
CountDownLatch latch = new CountDownLatch(1);
@Override
public void execute(StateContext<TestStates, TestEvents> context) {
latch.countDown();
}
}
}