Implement action monitoring and tracing
- Modify transitions and actions so that action execution can be monitored. - Extend StateMachineMonitor - Add new ActionListener - Relates to #149
This commit is contained in:
@@ -22,10 +22,12 @@ import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.actuate.trace.TraceRepository;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.monitor.AbstractStateMachineMonitor;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Implementation of a {@link StateMachineMonitor} which converts monitoring
|
||||
@@ -39,7 +41,8 @@ import org.springframework.statemachine.transition.Transition;
|
||||
*/
|
||||
public class BootStateMachineMonitor<S, E> extends AbstractStateMachineMonitor<S, E> {
|
||||
|
||||
private final String METRIC_BASE = "ssm.transition";
|
||||
private final String METRIC_TRANSITION_BASE = "ssm.transition";
|
||||
private final String METRIC_ACTION_BASE = "ssm.action";
|
||||
private final CounterService counterService;
|
||||
private final GaugeService gaugeService;
|
||||
private final TraceRepository traceRepository;
|
||||
@@ -61,8 +64,8 @@ public class BootStateMachineMonitor<S, E> extends AbstractStateMachineMonitor<S
|
||||
@Override
|
||||
public void transition(StateMachine<S, E> stateMachine, Transition<S, E> transition, long duration) {
|
||||
String transitionName = transitionToName(transition);
|
||||
this.counterService.increment(METRIC_BASE + "." + transitionName + ".transit");
|
||||
this.gaugeService.submit(METRIC_BASE + "." + transitionName + ".duration", duration);
|
||||
this.counterService.increment(METRIC_TRANSITION_BASE + "." + transitionName + ".transit");
|
||||
this.gaugeService.submit(METRIC_TRANSITION_BASE + "." + transitionName + ".duration", duration);
|
||||
Map<String, Object> traceInfo = new HashMap<>();
|
||||
traceInfo.put("transition", transitionToName(transition));
|
||||
traceInfo.put("duration", duration);
|
||||
@@ -70,6 +73,18 @@ public class BootStateMachineMonitor<S, E> extends AbstractStateMachineMonitor<S
|
||||
traceRepository.add(traceInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(StateMachine<S, E> stateMachine, Action<S, E> action, long duration) {
|
||||
String actionName = actionToName(action);
|
||||
this.counterService.increment(METRIC_ACTION_BASE + "." + actionName + ".execute");
|
||||
this.gaugeService.submit(METRIC_ACTION_BASE + "." + actionName + ".duration", duration);
|
||||
Map<String, Object> traceInfo = new HashMap<>();
|
||||
traceInfo.put("action", actionName);
|
||||
traceInfo.put("duration", duration);
|
||||
traceInfo.put("machine", stateMachine.getId());
|
||||
traceRepository.add(traceInfo);
|
||||
}
|
||||
|
||||
private static <S, E> String transitionToName(Transition<S, E> transition) {
|
||||
String sourceId = nullStateId(transition.getSource());
|
||||
String targetId = nullStateId(transition.getTarget());
|
||||
@@ -86,6 +101,10 @@ public class BootStateMachineMonitor<S, E> extends AbstractStateMachineMonitor<S
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static <S, E> String actionToName(Action<S, E> action) {
|
||||
return ObjectUtils.getDisplayString(action);
|
||||
}
|
||||
|
||||
private static <S, E> String nullStateId(State<S, E> state) {
|
||||
if (state == null) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.action;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
|
||||
/**
|
||||
* {@code ActionListener} for various action events.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface ActionListener<S, E> {
|
||||
|
||||
/**
|
||||
* Notified duration of a particular action.
|
||||
*
|
||||
* @param stateMachine the state machine
|
||||
* @param action the action
|
||||
* @param duration the transition duration
|
||||
*/
|
||||
void onExecute(StateMachine<S, E> stateMachine, Action<S, E> action, long duration);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.action;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.support.AbstractCompositeItems;
|
||||
|
||||
/**
|
||||
* Implementation of a {@link ActionListener} backed by a multiple listeners.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class CompositeActionListener<S, E> extends AbstractCompositeItems<ActionListener<S, E>>
|
||||
implements ActionListener<S, E> {
|
||||
|
||||
@Override
|
||||
public void onExecute(StateMachine<S, E> stateMachine, Action<S, E> action, long duration) {
|
||||
for (Iterator<ActionListener<S, E>> iterator = getItems().reverse(); iterator.hasNext();) {
|
||||
ActionListener<S, E> listener = iterator.next();
|
||||
listener.onExecute(stateMachine, action, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.statemachine.monitor;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.support.AbstractCompositeItems;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
@@ -39,4 +40,12 @@ public class CompositeStateMachineMonitor<S, E> extends AbstractCompositeItems<S
|
||||
monitor.transition(stateMachine, transition, duration);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(StateMachine<S, E> stateMachine, Action<S, E> transition, long duration) {
|
||||
for (Iterator<StateMachineMonitor<S, E>> iterator = getItems().reverse(); iterator.hasNext();) {
|
||||
StateMachineMonitor<S, E> monitor = iterator.next();
|
||||
monitor.action(stateMachine, transition, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.statemachine.monitor;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
@@ -29,11 +30,20 @@ import org.springframework.statemachine.transition.Transition;
|
||||
public interface StateMachineMonitor<S, E> {
|
||||
|
||||
/**
|
||||
* Notified duration of a particular transition.
|
||||
* Notified duration of a particular transition.
|
||||
*
|
||||
* @param stateMachine the state machine
|
||||
* @param transition the transition
|
||||
* @param duration the transition duration
|
||||
*/
|
||||
void transition(StateMachine<S, E> stateMachine, Transition<S, E> transition, long duration);
|
||||
|
||||
/**
|
||||
* Notified duration of a particular action.
|
||||
*
|
||||
* @param stateMachine the state machine
|
||||
* @param action the action
|
||||
* @param duration the transition duration
|
||||
*/
|
||||
void action(StateMachine<S, E> stateMachine, Action<S, E> action, long duration);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.action.ActionListener;
|
||||
import org.springframework.statemachine.action.CompositeActionListener;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
import org.springframework.statemachine.support.LifecycleObjectSupport;
|
||||
import org.springframework.statemachine.trigger.Trigger;
|
||||
@@ -55,6 +57,7 @@ public abstract class AbstractState<S, E> extends LifecycleObjectSupport impleme
|
||||
private List<Trigger<S, E>> triggers = new ArrayList<Trigger<S, E>>();
|
||||
private final CompositeStateListener<S, E> stateListener = new CompositeStateListener<S, E>();
|
||||
private final List<ScheduledFuture<?>> cancellableActions = new ArrayList<>();
|
||||
private CompositeActionListener<S, E> actionListener;
|
||||
|
||||
/**
|
||||
* Instantiates a new abstract state.
|
||||
@@ -269,6 +272,25 @@ public abstract class AbstractState<S, E> extends LifecycleObjectSupport impleme
|
||||
stateListener.unregister(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addActionListener(ActionListener<S, E> listener) {
|
||||
synchronized (this) {
|
||||
if (this.actionListener == null) {
|
||||
this.actionListener = new CompositeActionListener<>();
|
||||
}
|
||||
this.actionListener.register(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeActionListener(ActionListener<S, E> listener) {
|
||||
synchronized (this) {
|
||||
if (this.actionListener != null) {
|
||||
this.actionListener.unregister(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the submachine.
|
||||
*
|
||||
@@ -333,6 +355,24 @@ public abstract class AbstractState<S, E> extends LifecycleObjectSupport impleme
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute action and notify action listener if set.
|
||||
*
|
||||
* @param action the action
|
||||
* @param context the context
|
||||
*/
|
||||
protected void executeAction(Action<S, E> action, StateContext<S, E> context) {
|
||||
long now = System.currentTimeMillis();
|
||||
action.execute(context);
|
||||
if (this.actionListener != null) {
|
||||
try {
|
||||
this.actionListener.onExecute(context.getStateMachine(), action, System.currentTimeMillis() - now);
|
||||
} catch (Exception e) {
|
||||
log.warn("Error with actionListener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule action and return future which can be used to cancel it.
|
||||
*
|
||||
@@ -350,7 +390,7 @@ public abstract class AbstractState<S, E> extends LifecycleObjectSupport impleme
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
action.execute(context);
|
||||
executeAction(action, context);
|
||||
}
|
||||
}, new Date());
|
||||
return future;
|
||||
|
||||
@@ -146,7 +146,7 @@ public class ObjectState<S, E> extends AbstractSimpleState<S, E> {
|
||||
if (actions != null) {
|
||||
for (Action<S, E> action : actions) {
|
||||
try {
|
||||
action.execute(context);
|
||||
executeAction(action, context);
|
||||
} catch (Exception e) {
|
||||
log.error("Action execution resulted error", e);
|
||||
}
|
||||
@@ -161,7 +161,7 @@ public class ObjectState<S, E> extends AbstractSimpleState<S, E> {
|
||||
if (actions != null) {
|
||||
for (Action<S, E> action : actions) {
|
||||
try {
|
||||
action.execute(context);
|
||||
executeAction(action, context);
|
||||
} catch (Exception e) {
|
||||
log.error("Action execution resulted error", e);
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
Collection<? extends Action<S, E>> actions = getExitActions();
|
||||
if (actions != null) {
|
||||
for (Action<S, E> action : actions) {
|
||||
action.execute(context);
|
||||
executeAction(action, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
Collection<? extends Action<S, E>> actions = getEntryActions();
|
||||
if (actions != null) {
|
||||
for (Action<S, E> action : actions) {
|
||||
action.execute(context);
|
||||
executeAction(action, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -20,6 +20,7 @@ import java.util.Collection;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.action.ActionListener;
|
||||
|
||||
/**
|
||||
* {@code State} is an interface representing possible state in a state machine.
|
||||
@@ -160,4 +161,18 @@ public interface State<S, E> {
|
||||
* @param listener the listener
|
||||
*/
|
||||
void removeStateListener(StateListener<S, E> listener);
|
||||
|
||||
/**
|
||||
* Adds the action listener.
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void addActionListener(ActionListener<S, E> listener);
|
||||
|
||||
/**
|
||||
* Removes the action listener.
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void removeActionListener(ActionListener<S, E> listener);
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
Collection<? extends Action<S, E>> actions = getExitActions();
|
||||
if (actions != null && !isLocal(context)) {
|
||||
for (Action<S, E> action : actions) {
|
||||
action.execute(context);
|
||||
executeAction(action, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
Collection<? extends Action<S, E>> actions = getEntryActions();
|
||||
if (actions != null && !isLocal(context)) {
|
||||
for (Action<S, E> action : actions) {
|
||||
action.execute(context);
|
||||
executeAction(action, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.access.StateMachineAccess;
|
||||
import org.springframework.statemachine.access.StateMachineAccessor;
|
||||
import org.springframework.statemachine.access.StateMachineFunction;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.action.ActionListener;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
@@ -312,6 +314,24 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
}
|
||||
});
|
||||
stateMachineExecutor = executor;
|
||||
|
||||
for (Transition<S, E> t : getTransitions()) {
|
||||
t.addActionListener(new ActionListener<S, E>() {
|
||||
|
||||
@Override
|
||||
public void onExecute(StateMachine<S, E> stateMachine, Action<S, E> action, long duration) {
|
||||
notifyActionMonitor(stateMachine, action, duration);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (State<S, E> s : getStates()) {
|
||||
s.addActionListener(new ActionListener<S, E>() {
|
||||
@Override
|
||||
public void onExecute(StateMachine<S, E> stateMachine, Action<S, E> action, long duration) {
|
||||
notifyActionMonitor(stateMachine, action, duration);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.core.OrderComparator;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.event.StateMachineEventPublisher;
|
||||
import org.springframework.statemachine.listener.CompositeStateMachineListener;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
@@ -318,6 +319,14 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyActionMonitor(StateMachine<S, E> stateMachine, Action<S, E> action, long duration) {
|
||||
try {
|
||||
stateMachineMonitor.action(stateMachine, action, duration);
|
||||
} catch (Exception e) {
|
||||
log.warn("Error during notifyTransitionMonitor", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void stateChangedInRelay() {
|
||||
// TODO: this is a temporary tweak to know when state is
|
||||
// changed in a submachine/regions order to give
|
||||
|
||||
@@ -21,6 +21,8 @@ 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.ActionListener;
|
||||
import org.springframework.statemachine.action.CompositeActionListener;
|
||||
import org.springframework.statemachine.guard.Guard;
|
||||
import org.springframework.statemachine.security.SecurityRule;
|
||||
import org.springframework.statemachine.state.State;
|
||||
@@ -45,6 +47,7 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
|
||||
private final Guard<S, E> guard;
|
||||
private final Trigger<S, E> trigger;
|
||||
private final SecurityRule securityRule;
|
||||
private CompositeActionListener<S, E> actionListener;
|
||||
|
||||
/**
|
||||
* Instantiates a new abstract transition.
|
||||
@@ -133,13 +136,36 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
|
||||
return actions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addActionListener(ActionListener<S, E> listener) {
|
||||
synchronized (this) {
|
||||
if (this.actionListener == null) {
|
||||
this.actionListener = new CompositeActionListener<>();
|
||||
}
|
||||
this.actionListener.register(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeActionListener(ActionListener<S, E> listener) {
|
||||
synchronized (this) {
|
||||
if (this.actionListener != null) {
|
||||
this.actionListener.unregister(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final void executeAllActions(StateContext<S, E> context) {
|
||||
if (actions == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Action<S, E> action : actions) {
|
||||
long now = System.currentTimeMillis();
|
||||
action.execute(context);
|
||||
if (this.actionListener != null) {
|
||||
this.actionListener.onExecute(context.getStateMachine(), action, System.currentTimeMillis() - now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -17,6 +17,7 @@ package org.springframework.statemachine.transition;
|
||||
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.action.ActionListener;
|
||||
import org.springframework.statemachine.security.SecurityRule;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.trigger.Trigger;
|
||||
@@ -83,4 +84,18 @@ public interface Transition<S, E> {
|
||||
* @return the security rule
|
||||
*/
|
||||
SecurityRule getSecurityRule();
|
||||
|
||||
/**
|
||||
* Adds the action listener.
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void addActionListener(ActionListener<S, E> listener);
|
||||
|
||||
/**
|
||||
* Removes the action listener.
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void removeActionListener(ActionListener<S, E> listener);
|
||||
}
|
||||
|
||||
@@ -16,16 +16,22 @@
|
||||
package org.springframework.statemachine.monitor;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineSystemConstants;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||
@@ -44,18 +50,22 @@ public class StateMachineMonitorTests extends AbstractStateMachineTests {
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
|
||||
TestStateMachineMonitor monitor = context.getBean(TestStateMachineMonitor.class);
|
||||
Action<String, String> taction = context.getBean("taction", Action.class);
|
||||
Action<String, String> enaction = context.getBean("enaction", Action.class);
|
||||
Action<String, String> exaction = context.getBean("exaction", Action.class);
|
||||
Action<String, String> saction = context.getBean("saction", Action.class);
|
||||
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), contains("S1"));
|
||||
machine.sendEvent("E1");
|
||||
assertThat(machine.getState().getIds(), contains("S2"));
|
||||
assertThat(monitor.transition, notNullValue());
|
||||
assertThat(monitor.duration, notNullValue());
|
||||
// there's also initial transition, thus 2 instead 1
|
||||
assertThat(monitor.transitions.size(), is(2));
|
||||
assertThat(monitor.actions.size(), is(4));
|
||||
assertThat(monitor.actions.keySet(), containsInAnyOrder(taction, enaction, exaction, saction));
|
||||
monitor.reset();
|
||||
machine.sendEvent("E2");
|
||||
assertThat(machine.getState().getIds(), contains("S1"));
|
||||
assertThat(monitor.transition, notNullValue());
|
||||
assertThat(monitor.duration, notNullValue());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -75,7 +85,9 @@ public class StateMachineMonitorTests extends AbstractStateMachineTests {
|
||||
states
|
||||
.withStates()
|
||||
.initial("S1")
|
||||
.state("S2");
|
||||
.state("S1", null, exaction())
|
||||
.state("S2", saction())
|
||||
.state("S2", enaction(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,6 +96,7 @@ public class StateMachineMonitorTests extends AbstractStateMachineTests {
|
||||
.withExternal()
|
||||
.source("S1")
|
||||
.target("S2")
|
||||
.action(taction())
|
||||
.event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
@@ -92,6 +105,58 @@ public class StateMachineMonitorTests extends AbstractStateMachineTests {
|
||||
.event("E2");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Action<String, String> taction() {
|
||||
return new Action<String, String>() {
|
||||
@Override
|
||||
public void execute(StateContext<String, String> context) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Action<String, String> enaction() {
|
||||
return new Action<String, String>() {
|
||||
@Override
|
||||
public void execute(StateContext<String, String> context) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Action<String, String> exaction() {
|
||||
return new Action<String, String>() {
|
||||
@Override
|
||||
public void execute(StateContext<String, String> context) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Action<String, String> saction() {
|
||||
return new Action<String, String>() {
|
||||
@Override
|
||||
public void execute(StateContext<String, String> context) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StateMachineMonitor<String, String> stateMachineMonitor() {
|
||||
return new TestStateMachineMonitor();
|
||||
@@ -106,18 +171,43 @@ public class StateMachineMonitorTests extends AbstractStateMachineTests {
|
||||
|
||||
private static class TestStateMachineMonitor extends AbstractStateMachineMonitor<String, String> {
|
||||
|
||||
Transition<String, String> transition;
|
||||
Long duration;
|
||||
Map<Transition<String, String>, Transitions> transitions = new HashMap<>();
|
||||
Map<Action<String, String>, Actions> actions = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void transition(StateMachine<String, String> stateMachine, Transition<String, String> transition, long duration) {
|
||||
this.transition = transition;
|
||||
this.duration = duration;
|
||||
transitions.put(transition, new Transitions(transition, duration));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(StateMachine<String, String> stateMachine, Action<String, String> action,
|
||||
long duration) {
|
||||
actions.put(action, new Actions(action, duration));
|
||||
}
|
||||
|
||||
void reset() {
|
||||
transition = null;
|
||||
duration = null;
|
||||
transitions.clear();
|
||||
actions.clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Transitions {
|
||||
Transition<String, String> transition;
|
||||
Long duration;
|
||||
public Transitions(Transition<String, String> transition, Long duration) {
|
||||
super();
|
||||
this.transition = transition;
|
||||
this.duration = duration;
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unused")
|
||||
static class Actions {
|
||||
Action<String, String> action;
|
||||
Long duration;
|
||||
public Actions(Action<String, String> action, Long duration) {
|
||||
this.action = action;
|
||||
this.duration = duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ 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.ActionListener;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.security.SecurityRule;
|
||||
import org.springframework.statemachine.state.EnumState;
|
||||
@@ -132,6 +133,14 @@ public class StateContextExpressionMethodsTests {
|
||||
public SecurityRule getSecurityRule() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addActionListener(ActionListener<SpelStates, SpelEvents> listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeActionListener(ActionListener<SpelStates, SpelEvents> listener) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class MockStatemachine implements StateMachine<SpelStates, SpelEvents> {
|
||||
|
||||
@@ -35,8 +35,8 @@ public class StateMachineConfig {
|
||||
states
|
||||
.withStates()
|
||||
.initial("S1")
|
||||
.state("S2")
|
||||
.state("S3");
|
||||
.state("S2", null, (c) -> {System.out.println("hello");})
|
||||
.state("S3", (c) -> {System.out.println("hello");}, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,6 +45,7 @@ public class StateMachineConfig {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("S1").target("S2").event("E1")
|
||||
.action((c) -> {System.out.println("hello");})
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S2").target("S3").event("E2");
|
||||
|
||||
Reference in New Issue
Block a user