Transition error action not used per defined action

- Revert some previous changes order to define transition
  error action logic in a different place. Now basically
  wrapping try/catch in a dedicated action order to have
  separate error action per action as is expected by a config
  SPI.
- Added some tests.
- Added docs for transition error actions.
- Relates to #238
- Fixes #259
This commit is contained in:
Janne Valkealahti
2016-09-27 09:21:30 +01:00
parent 8fc7592404
commit 0f3ffa1876
24 changed files with 452 additions and 211 deletions

View File

@@ -160,6 +160,7 @@ expression based guard is a _SpelExpressionGuard_. This was attached to
transition between states `S2` and `S3`. Both guard in above sample
always evaluate to true.
[[statemachine-config-actions]]
=== Configuring Actions
Actions can be defined to be executed with transitions and states
itself. Action is always executed as a result of a transition which
@@ -220,6 +221,27 @@ wrapped within a `Runnable` which may get cancelled via
action, you need to be able to catch `InterruptedException` which is
raised if task is cancelled.
[[statemachine-config-actions-errorhandling]]
==== Transition Action Error Handling
User can always catch exceptions manually but with actions defined for
transitions it is possible to define error action which is called if
exception is reased. Exception is then available from a `StateContext`
passed to that action.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetEC]
----
Similar logic can be done manually for every action if needed.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetED]
----
=== Configuring Pseudo States
_Pseudo state_ configuration is usually done by configuring states and
@@ -1583,6 +1605,12 @@ recognize only `StateMachineEvent` instances.
include::samples/DocsConfigurationSampleTests.java[tags=snippet4]
----
[TIP]
====
Actions defined for transitions also have their own error handling
logic <<statemachine-config-actions-errorhandling>>.
====
[[sm-persist]]
== Persisting State Machine
Traditionally an instance of a state machine is used as is within a

View File

@@ -30,5 +30,5 @@ _UML_ support and integrations with external config repositories.
* New _Repository_ abstraction keeping machine configuration in an
external repository <<sm-repository>>
* New support for state actions. <<state-actions>>
* New error action concepts.
* New transition error action concepts. <<statemachine-config-actions-errorhandling>>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,9 +17,13 @@
package org.springframework.statemachine.action;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.support.DefaultStateContext;
/**
* Action Utilities.
*
* @author Janne Valkealahti
*
*/
public final class Actions {
@@ -28,9 +32,10 @@ public final class Actions {
}
/**
* Builds a noop {@link Action}.
*
* @param <S> represents states Class
* @param <E> represents event Class
* @param <S> the type of state
* @param <E> the type of event
* @return an empty (Noop) Action.
*/
public static <S, E> Action<S, E> emptyAction() {
@@ -41,4 +46,35 @@ public final class Actions {
}
};
}
/**
* Builds a erro calling action {@link Action}.
*
* @param <S> the type of state
* @param <E> the type of event
* @param action the action
* @param errorAction the error action
* @return the error calling action
*/
public static <S, E> Action<S, E> errorCallingAction(final Action<S, E> action, final Action<S, E> errorAction) {
return new Action<S, E>() {
@Override
public void execute(final StateContext<S, E> context) {
try {
action.execute(context);
}
catch (Exception exception) {
// notify something wrong is happening in actions execution.
try {
errorAction.execute(new DefaultStateContext<>(context.getStage(), context.getMessage(), context.getMessageHeaders(),
context.getExtendedState(), context.getTransition(), context.getStateMachine(), context.getSource(),
context.getTarget(), context.getSources(), context.getTargets(), exception));
} catch (Exception e) {
// not interested
}
throw exception;
}
}
};
}
}

View File

@@ -735,7 +735,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
}
DefaultExternalTransition<S, E> transition = new DefaultExternalTransition<S, E>(stateMap.get(source),
stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard(), trigger,
transitionData.getSecurityRule(), transitionData.getErrorAction());
transitionData.getSecurityRule());
transitions.add(transition);
} else if (transitionData.getKind() == TransitionKind.LOCAL) {
@@ -745,12 +745,12 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
}
DefaultLocalTransition<S, E> transition = new DefaultLocalTransition<S, E>(stateMap.get(source),
stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard(), trigger,
transitionData.getSecurityRule(), transitionData.getErrorAction());
transitionData.getSecurityRule());
transitions.add(transition);
} else if (transitionData.getKind() == TransitionKind.INTERNAL) {
DefaultInternalTransition<S, E> transition = new DefaultInternalTransition<S, E>(stateMap.get(source),
transitionData.getActions(), event, transitionData.getGuard(), trigger,
transitionData.getSecurityRule(), transitionData.getErrorAction());
transitionData.getSecurityRule());
transitions.add(transition);
}
}
@@ -764,7 +764,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
if (source != null && !source.isOrthogonal()) {
State<S, E> target = stateMap.get(entry.getKey());
DefaultExternalTransition<S, E> transition = new DefaultExternalTransition<S, E>(
source, target, null, null, null, null, null, null);
source, target, null, null, null, null, null);
transitions.add(transition);
}
}

View File

@@ -174,17 +174,16 @@ public class StateMachineTransitionBuilder<S, E>
* @param guard the guard
* @param kind the kind
* @param securityRule the security rule
* @param errorAction the {@link Action} that will be called each time an action is gonna throw an exception.
*/
public void addTransition(S source, S target, S state, E event, Long period, Integer count, Collection<Action<S, E>> actions,
Guard<S, E> guard, TransitionKind kind, SecurityRule securityRule, Action<S, E> errorAction) {
Guard<S, E> guard, TransitionKind kind, SecurityRule securityRule) {
// if rule not given, get it from global
if (securityRule == null) {
@SuppressWarnings("unchecked")
ConfigurationData<S, E> config = getSharedObject(ConfigurationData.class);
securityRule = config.getTransitionSecurityRule();
}
transitionData.add(new TransitionData<>(source, target, state, event, period, count, actions, guard, kind, securityRule, errorAction));
transitionData.add(new TransitionData<>(source, target, state, event, period, count, actions, guard, kind, securityRule));
}
/**

View File

@@ -16,6 +16,7 @@
package org.springframework.statemachine.config.configurers;
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;
@@ -47,7 +48,6 @@ public abstract class AbstractTransitionConfigurer<S, E> extends
private final Collection<Action<S, E>> actions = new ArrayList<>();
private Guard<S, E> guard;
private SecurityRule securityRule;
private Action<S, E> errorAction;
protected S getSource() {
return source;
@@ -118,29 +118,17 @@ public abstract class AbstractTransitionConfigurer<S, E> extends
}
protected void addAction(Action<S, E> action) {
this.actions.add(action);
addAction(action, null);
}
protected void addAction(Action<S, E> action, Action<S, E> error) {
this.actions.add(error != null ? Actions.errorCallingAction(action, error) : action);
}
protected void setGuard(Guard<S, E> guard) {
this.guard = guard;
}
/**
*
* @return the Error {@link Action}
*/
public Action<S, E> getErrorAction() {
return errorAction;
}
/**
*
* @param errorAction the {@link Action} that will be called each time an action is gonna throw an exception.
*/
public void setErrorAction(Action<S, E> errorAction) {
this.errorAction = errorAction;
}
protected void setSecurityRule(String attributes, ComparisonType match) {
if (securityRule == null) {
securityRule = new SecurityRule();

View File

@@ -39,7 +39,7 @@ public class DefaultExternalTransitionConfigurer<S, E> extends AbstractTransitio
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
builder.addTransition(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.EXTERNAL,
getSecurityRule(), getErrorAction());
getSecurityRule());
}
@Override
@@ -86,8 +86,7 @@ public class DefaultExternalTransitionConfigurer<S, E> extends AbstractTransitio
@Override
public ExternalTransitionConfigurer<S, E> action(Action<S, E> action, Action<S, E> error) {
addAction(action);
setErrorAction(error);
addAction(action, error);
return this;
}

View File

@@ -39,7 +39,7 @@ public class DefaultInternalTransitionConfigurer<S, E> extends AbstractTransitio
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
builder.addTransition(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.INTERNAL,
getSecurityRule(), getErrorAction());
getSecurityRule());
}
@Override
@@ -80,8 +80,7 @@ public class DefaultInternalTransitionConfigurer<S, E> extends AbstractTransitio
@Override
public InternalTransitionConfigurer<S, E> action(Action<S, E> action, Action<S, E> error) {
addAction(action);
setErrorAction(error);
addAction(action, error);
return this;
}

View File

@@ -38,7 +38,7 @@ public class DefaultLocalTransitionConfigurer<S, E> extends AbstractTransitionCo
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
builder.addTransition(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.LOCAL,
getSecurityRule(), getErrorAction());
getSecurityRule());
}
@Override
@@ -85,8 +85,7 @@ public class DefaultLocalTransitionConfigurer<S, E> extends AbstractTransitionCo
@Override
public LocalTransitionConfigurer<S, E> action(Action<S, E> action, Action<S, E> error) {
addAction(action);
setErrorAction(error);
addAction(action, error);
return this;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -87,7 +87,7 @@ public interface TransitionConfigurer<T, S, E> extends
* Specify {@link Action} for this {@link Transition}.
*
* @param action the action
* @param error that will be called if any unexpected exception is thrown by the action.
* @param error action that will be called if any unexpected exception is thrown by the action.
* @return configurer for chaining
*/
T action(Action<S, E> action, Action<S, E> error);

View File

@@ -39,7 +39,6 @@ public class TransitionData<S, E> {
private final Guard<S, E> guard;
private final TransitionKind kind;
private final SecurityRule securityRule;
private final Action<S, E> errorAction;
/**
* Instantiates a new transition data.
@@ -49,7 +48,7 @@ public class TransitionData<S, E> {
* @param event the event
*/
public TransitionData(S source, S target, E event) {
this(source, target, null, event, null, null, null, null, TransitionKind.EXTERNAL, null, null);
this(source, target, null, event, null, null, null, null, TransitionKind.EXTERNAL, null);
}
/**
@@ -64,7 +63,7 @@ public class TransitionData<S, E> {
*/
public TransitionData(S source, S target, E event, Collection<Action<S, E>> actions,
Guard<S, E> guard, TransitionKind kind) {
this(source, target, null, event, null, null, actions, guard, kind, null, null);
this(source, target, null, event, null, null, actions, guard, kind, null);
}
/**
@@ -80,7 +79,7 @@ public class TransitionData<S, E> {
*/
public TransitionData(S source, S target, Long period, Integer count, Collection<Action<S, E>> actions,
Guard<S, E> guard, TransitionKind kind) {
this(source, target, null, null, period, count, actions, guard, kind, null, null);
this(source, target, null, null, period, count, actions, guard, kind, null);
}
/**
@@ -96,10 +95,9 @@ public class TransitionData<S, E> {
* @param guard the guard
* @param kind the kind
* @param securityRule the security rule
* @param errorAction the {@link Action} that will be called each time an action is gonna throw an exception.
*/
public TransitionData(S source, S target, S state, E event, Long period, Integer count, Collection<Action<S, E>> actions,
Guard<S, E> guard, TransitionKind kind, SecurityRule securityRule, Action<S, E> errorAction) {
Guard<S, E> guard, TransitionKind kind, SecurityRule securityRule) {
this.source = source;
this.target = target;
this.state = state;
@@ -110,7 +108,6 @@ public class TransitionData<S, E> {
this.guard = guard;
this.kind = kind;
this.securityRule = securityRule;
this.errorAction = errorAction;
}
/**
@@ -202,12 +199,4 @@ public class TransitionData<S, E> {
public SecurityRule getSecurityRule() {
return securityRule;
}
/**
*
* @return the error {@link Action}
*/
public Action<S, E> getErrorAction() {
return errorAction;
}
}
}

View File

@@ -25,14 +25,34 @@ import java.util.Collection;
public abstract class AbstractExternalTransition<S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
/**
* Instantiates a new abstract external transition.
*
* @param source the source
* @param target the target
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
* @param securityRule the security rule
*/
public AbstractExternalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions,
E event, Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule, Action<S, E> errorAction) {
super(source, target, actions, event, TransitionKind.EXTERNAL, guard, trigger, securityRule, errorAction);
E event, Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule) {
super(source, target, actions, event, TransitionKind.EXTERNAL, guard, trigger, securityRule);
}
/**
* Instantiates a new abstract external transition.
*
* @param source the source
* @param target the target
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
*/
public AbstractExternalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions,
E event, Guard<S, E> guard, Trigger<S, E> trigger) {
super(source, target, actions, event, TransitionKind.EXTERNAL, guard, trigger);
}
}

View File

@@ -25,14 +25,32 @@ import org.springframework.statemachine.trigger.Trigger;
public class AbstractInternalTransition<S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
/**
* Instantiates a new abstract internal transition.
*
* @param source the source
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
*/
public AbstractInternalTransition(State<S, E> source, Collection<Action<S, E>> actions, E event, Guard<S, E> guard,
Trigger<S, E> trigger) {
super(source, source, actions, event, TransitionKind.INTERNAL, guard, trigger);
}
/**
* Instantiates a new abstract internal transition.
*
* @param source the source
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
* @param securityRule the security rule
*/
public AbstractInternalTransition(State<S, E> source, Collection<Action<S, E>> actions, E event, Guard<S, E> guard,
Trigger<S, E> trigger, SecurityRule securityRule, Action<S, E> errorAction) {
super(source, source, actions, event, TransitionKind.INTERNAL, guard, trigger, securityRule, errorAction);
Trigger<S, E> trigger, SecurityRule securityRule) {
super(source, source, actions, event, TransitionKind.INTERNAL, guard, trigger, securityRule);
}
}

View File

@@ -25,14 +25,34 @@ import java.util.Collection;
public class AbstractLocalTransition<S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
/**
* Instantiates a new abstract local transition.
*
* @param source the source
* @param target the target
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
*/
public AbstractLocalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
Guard<S, E> guard, Trigger<S, E> trigger) {
super(source, target, actions, event, TransitionKind.LOCAL, guard, trigger);
}
/**
* Instantiates a new abstract local transition.
*
* @param source the source
* @param target the target
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
* @param securityRule the security rule
*/
public AbstractLocalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule, Action<S, E> errorAction) {
super(source, target, actions, event, TransitionKind.LOCAL, guard, trigger, securityRule, errorAction);
Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule) {
super(source, target, actions, event, TransitionKind.LOCAL, guard, trigger, securityRule);
}
}

View File

@@ -15,19 +15,18 @@
*/
package org.springframework.statemachine.transition;
import java.util.Collection;
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.action.Actions;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.security.SecurityRule;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.trigger.Trigger;
import org.springframework.util.Assert;
import java.util.Collection;
/**
* Base implementation of a {@link Transition}.
*
@@ -41,64 +40,52 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
private final static Log log = LogFactory.getLog(AbstractTransition.class);
protected final State<S, E> target;
protected final Collection<Action<S, E>> actions;
protected final Action<S, E> errorAction;
private final State<S, E> source;
private final TransitionKind kind;
private final Guard<S, E> guard;
private final Trigger<S, E> trigger;
private final SecurityRule securityRule;
public AbstractTransition(State<S, E> source,
State<S, E> target,
Collection<Action<S, E>> actions,
E event,
TransitionKind kind,
Guard<S, E> guard,
Trigger<S, E> trigger) {
this(source, target, actions, event, kind, guard, trigger, null, Actions.<S, E>emptyAction());
/**
* Instantiates a new abstract transition.
*
* @param source the source
* @param target the target
* @param actions the actions
* @param event the event
* @param kind the kind
* @param guard the guard
* @param trigger the trigger
*/
public AbstractTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event, TransitionKind kind,
Guard<S, E> guard, Trigger<S, E> trigger) {
this(source, target, actions, event, kind, guard, trigger, null);
}
public AbstractTransition(State<S, E> source,
State<S, E> target,
Collection<Action<S, E>> actions,
E event,
TransitionKind kind,
Guard<S, E> guard,
Trigger<S, E> trigger,
SecurityRule securityRule,
Action<S, E> errorAction) {
Assert.notNull(source, "Source must be set");
/**
* Instantiates a new abstract transition.
*
* @param source the source
* @param target the target
* @param actions the actions
* @param event the event
* @param kind the kind
* @param guard the guard
* @param trigger the trigger
* @param securityRule the security rule
*/
public AbstractTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event, TransitionKind kind,
Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule) {
Assert.notNull(kind, "Transition type must be set");
this.source = source;
this.target = target;
this.actions = actions;
this.errorAction = errorAction == null
? Actions.<S, E> emptyAction()
: errorAction; this.kind = kind;
this.kind = kind;
this.guard = guard;
this.trigger = trigger;
this.securityRule = securityRule;
}
protected AbstractTransition(State<S, E> target, Collection<Action<S, E>> actions, TransitionKind kind, Action<S, E> errorAction) {
Assert.notNull(kind, "Transition type must be set");
this.source = null;
this.target = target;
this.actions = actions;
this.errorAction = errorAction == null
? Actions.<S, E> emptyAction()
: errorAction;
this.kind = kind;
this.guard = null;
this.trigger = null;
this.securityRule = null;
}
@Override
public State<S, E> getSource() {
return source;
@@ -123,7 +110,6 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
}
}
executeAllActions(context);
return true;
}
@@ -137,44 +123,23 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
return securityRule;
}
/**
*
* @return the target {@link State}
*/
@Override
public State<S, E> getTarget() {
return target;
}
/**
*
* @return all {@link Action}
*/
@Override
public Collection<Action<S, E>> getActions() {
return actions;
}
/**
*
* @return the {@link Action} called of any error occurred while actions are executed.
*/
public Action<S, E> getErrorAction() {
return errorAction;
}
protected final void executeAllActions(StateContext<S, E> context) {
if (actions == null) {
return;
}
for (Action<S, E> action : actions) {
try {
action.execute(context);
}
catch (Exception exception) {
errorAction.execute(context); // notify something wrong is happening in
// Actions execution.
throw exception;
}
action.execute(context);
}
}
}

View File

@@ -25,14 +25,34 @@ import java.util.Collection;
public class DefaultExternalTransition<S, E> extends AbstractExternalTransition<S, E> {
/**
* Instantiates a new default external transition.
*
* @param source the source
* @param target the target
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
*/
public DefaultExternalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
Guard<S, E> guard, Trigger<S, E> trigger) {
super(source, target, actions, event, guard, trigger);
}
/**
* Instantiates a new default external transition.
*
* @param source the source
* @param target the target
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
* @param securityRule the security rule
*/
public DefaultExternalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule, Action<S, E> errorAction) {
super(source, target, actions, event, guard, trigger, securityRule, errorAction);
Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule) {
super(source, target, actions, event, guard, trigger, securityRule);
}
}

View File

@@ -25,14 +25,32 @@ import java.util.Collection;
public class DefaultInternalTransition<S, E> extends AbstractInternalTransition<S, E> {
/**
* Instantiates a new default internal transition.
*
* @param source the source
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
*/
public DefaultInternalTransition(State<S, E> source, Collection<Action<S, E>> actions, E event, Guard<S, E> guard,
Trigger<S, E> trigger) {
super(source, actions, event, guard, trigger);
}
/**
* Instantiates a new default internal transition.
*
* @param source the source
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
* @param securityRule the security rule
*/
public DefaultInternalTransition(State<S, E> source, Collection<Action<S, E>> actions, E event, Guard<S, E> guard,
Trigger<S, E> trigger, SecurityRule securityRule, Action<S, E> errorAction) {
super(source, actions, event, guard, trigger, securityRule, errorAction);
Trigger<S, E> trigger, SecurityRule securityRule) {
super(source, actions, event, guard, trigger, securityRule);
}
}

View File

@@ -25,14 +25,35 @@ import org.springframework.statemachine.trigger.Trigger;
public class DefaultLocalTransition<S, E> extends AbstractLocalTransition<S, E> {
/**
* Instantiates a new default local transition.
*
* @param source the source
* @param target the target
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
*/
public DefaultLocalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event, Guard<S, E> guard,
Trigger<S, E> trigger) {
super(source, target, actions, event, guard, trigger);
}
/**
* Instantiates a new default local transition.
*
* @param source the source
* @param target the target
* @param actions the actions
* @param event the event
* @param guard the guard
* @param trigger the trigger
* @param securityRule the security rule
*/
public DefaultLocalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule, Action<S, E> errorAction) {
super(source, target, actions, event, guard, trigger, securityRule, errorAction);
Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule) {
super(source, target, actions, event, guard, trigger, securityRule);
}
@Override

View File

@@ -34,30 +34,32 @@ public class InitialTransition<S, E> extends AbstractTransition<S, E>
implements Transition<S, E> {
/**
* Instantiates a new initial transition.
*
* @param target state.
* @param target the target
*/
public InitialTransition(State<S, E> target) {
this(target, null);
super(null, target, null, null, TransitionKind.INITIAL, null, null, null);
}
/**
* Instantiates a new initial transition.
*
* @param target state
* @param action to be executed to succeed for this transition
* @param target the target
* @param action the action
*/
public InitialTransition(State<S, E> target, Action<S, E> action) {
this(target, action == null ? Collections.<Action<S,E>>emptyList(): Collections.singleton(action), null);
super(null, target, action != null ? Collections.singleton(action) : null, null, TransitionKind.INITIAL, null, null, null);
}
/**
* Instantiates a new initial transition.
*
* @param target state
* @param actions to be executed to succeed for this transition
* @param errorAction to be executed if one of {@link #getActions()} throw an exception.
* @param target the target
* @param actions the actions
*/
public InitialTransition(State<S, E> target, Collection<Action<S, E>> actions, Action<S, E> errorAction) {
super(target, actions, TransitionKind.INITIAL, errorAction);
public InitialTransition(State<S, E> target, Collection<Action<S, E>> actions) {
super(null, target, actions, null, TransitionKind.INITIAL, null, null, null);
}
@Override

View File

@@ -63,14 +63,6 @@ public interface Transition<S, E> {
*/
Collection<Action<S, E>> getActions();
/**
* Get The action that will be execute if one of {@link #getActions()} throw an exception.
* This action may be usefull if you want to notify another sub system from unexpected technical error.
*
* @return the error {@link Action}
*/
Action<S, E> getErrorAction();
/**
* Gets the transition trigger.
*
@@ -91,5 +83,4 @@ public interface Transition<S, E> {
* @return the security rule
*/
SecurityRule getSecurityRule();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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,12 @@
*/
package org.springframework.statemachine.action;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -31,10 +37,6 @@ import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Tests for state machine actions.
*
@@ -54,18 +56,34 @@ public class ActionTests extends AbstractStateMachineTests {
TestCountAction testAction1 = ctx.getBean("testAction1", TestCountAction.class);
TestCountAction testAction2 = ctx.getBean("testAction2", TestCountAction.class);
TestCountAction testAction3 = ctx.getBean("testAction3", TestCountAction.class);
TestCountAction testAction4 = ctx.getBean("testAction4", TestCountAction.class);
TestCountAction testErrorAction = ctx.getBean("testErrorAction", TestCountAction.class);
TestCountAction testAction3 = ctx.getBean("testAction3", TestCountAction.class);
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build());
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build());
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).build());
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build());
assertThat(testAction1.count, is(1));
assertThat(testAction2.count, is(1));
assertThat(testAction3.count, is(1));
assertThat(testAction4.count, is(0));
assertThat(testErrorAction.count, is(1));
assertThat(testAction3.count, is(1));
ctx.close();
}
@SuppressWarnings({ "unchecked" })
@Test
public void testTransitionActionErrors() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config2.class);
assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
StateMachine<TestStates,TestEvents> machine =
ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
machine.start();
TestCountAction testAction1 = ctx.getBean("testAction1", TestCountAction.class);
TestCountAction testErrorAction = ctx.getBean("testErrorAction", TestCountAction.class);
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
assertThat(testAction1.count, is(1));
assertThat(testErrorAction.count, is(1));
assertThat(testErrorAction.context, notNullValue());
assertThat(testErrorAction.context.getException(), notNullValue());
assertThat(testErrorAction.context.getException(), instanceOf(RuntimeException.class));
assertThat(testErrorAction.context.getException().getMessage(), is("Fake Error"));
ctx.close();
}
@@ -77,6 +95,7 @@ public class ActionTests extends AbstractStateMachineTests {
private static class TestCountAction implements Action<TestStates, TestEvents> {
int count = 0;
StateContext<TestStates, TestEvents> context;
public TestCountAction() {
count = 0;
@@ -84,6 +103,7 @@ public class ActionTests extends AbstractStateMachineTests {
@Override
public void execute(StateContext<TestStates, TestEvents> context) {
this.context = context;
count++;
}
@@ -100,9 +120,8 @@ public class ActionTests extends AbstractStateMachineTests {
.initial(TestStates.S1)
.state(TestStates.S1)
.state(TestStates.S2)
.state(TestStates.S3)
.state(TestStates.S4)
.state(TestStates.S10);
.state(TestStates.S3)
.state(TestStates.S4);
}
@Override
@@ -124,13 +143,7 @@ public class ActionTests extends AbstractStateMachineTests {
.source(TestStates.S3)
.target(TestStates.S4)
.event(TestEvents.E3)
.action(testAction3())
.and()
.withExternal()
.source(TestStates.S4)
.target(TestStates.S10)
.event(TestEvents.E4)
.action(testAction4(), testErrorAction());
.action(testAction3());
}
@Bean
@@ -143,22 +156,52 @@ public class ActionTests extends AbstractStateMachineTests {
return new TestCountAction();
}
@Bean
public TestCountAction testAction3() {
return new TestCountAction();
}
@Bean
public TestCountAction testAction3() {
return new TestCountAction();
}
@Bean
public TestCountAction testAction4() {
return new TestCountAction() {
@Override
public void execute(StateContext<TestStates, TestEvents> context) {
throw new RuntimeException("Fake Error");
}
};
}
@Bean
public TaskExecutor taskExecutor() {
return new SyncTaskExecutor();
}
}
@Bean
@Configuration
@EnableStateMachine
static class Config2 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.state(TestStates.S1)
.state(TestStates.S2);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.S1)
.target(TestStates.S2)
.event(TestEvents.E1)
.action(testAction1(), testErrorAction());
}
@Bean
public TestCountAction testAction1() {
return new TestCountAction() {
@Override
public void execute(StateContext<TestStates, TestEvents> context) {
super.execute(context);
throw new RuntimeException("Fake Error");
}
};
}
@Bean
public TestCountAction testErrorAction() {
return new TestCountAction();
}
@@ -169,5 +212,4 @@ public class ActionTests extends AbstractStateMachineTests {
}
}
}

View File

@@ -68,7 +68,7 @@ public class StateMachineModelTests {
Collection<TransitionData<String, String>> transitions = new ArrayList<>();
TransitionData<String, String> transitionData1 = new TransitionData<String, String>("S1", "S2", null, "E1", null, null, null, null, TransitionKind.EXTERNAL, null, null);
TransitionData<String, String> transitionData1 = new TransitionData<String, String>("S1", "S2", null, "E1", null, null, null, null, TransitionKind.EXTERNAL, null);
transitions.add(transitionData1);
Map<String, List<ChoiceData<String, String>>> choices = new HashMap<>();
Map<String, List<JunctionData<String, String>>> junctions = new HashMap<>();

View File

@@ -37,6 +37,7 @@ import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.access.StateMachineAccess;
import org.springframework.statemachine.access.StateMachineFunction;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.action.Actions;
import org.springframework.statemachine.action.SpelExpressionAction;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnableStateMachineFactory;
@@ -265,6 +266,94 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
}
// end::snippetEB[]
// tag::snippetEC[]
@Configuration
@EnableStateMachine
public class Config53
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.S1)
.target(States.S2)
.event(Events.E1)
.action(action(), errorAction());
}
@Bean
public Action<States, Events> action() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
throw new RuntimeException("MyError");
}
};
}
@Bean
public Action<States, Events> errorAction() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
// RuntimeException("MyError") added to context
Exception exception = context.getException();
exception.getMessage();
}
};
}
}
// end::snippetEC[]
@Configuration
@EnableStateMachine
public class Config54
extends EnumStateMachineConfigurerAdapter<States, Events> {
// tag::snippetED[]
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.S1)
.target(States.S2)
.event(Events.E1)
.action(Actions.errorCallingAction(action(), errorAction()));
}
// end::snippetED[]
@Bean
public Action<States, Events> action() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
throw new RuntimeException("MyError");
}
};
}
@Bean
public Action<States, Events> errorAction() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
// RuntimeException("MyError") added to context
Exception exception = context.getException();
exception.getMessage();
}
};
}
}
// tag::snippetFA[]
@Configuration
@EnableStateMachineFactory

View File

@@ -15,6 +15,15 @@
*/
package org.springframework.statemachine.support;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.junit.Test;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -27,7 +36,6 @@ import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.access.StateMachineAccessor;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.action.Actions;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.security.SecurityRule;
import org.springframework.statemachine.state.EnumState;
@@ -36,11 +44,6 @@ import org.springframework.statemachine.transition.Transition;
import org.springframework.statemachine.transition.TransitionKind;
import org.springframework.statemachine.trigger.Trigger;
import java.util.*;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class StateContextExpressionMethodsTests {
@Test
@@ -129,11 +132,6 @@ public class StateContextExpressionMethodsTests {
public SecurityRule getSecurityRule() {
return null;
}
@Override
public Action<SpelStates, SpelEvents> getErrorAction() {
return Actions.emptyAction();
}
}
private static class MockStatemachine implements StateMachine<SpelStates, SpelEvents> {