diff --git a/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/BootStateMachineMonitor.java b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/BootStateMachineMonitor.java index e429d73b..1df4ff70 100644 --- a/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/BootStateMachineMonitor.java +++ b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/BootStateMachineMonitor.java @@ -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 extends AbstractStateMachineMonitor { - 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 extends AbstractStateMachineMonitor stateMachine, Transition 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 traceInfo = new HashMap<>(); traceInfo.put("transition", transitionToName(transition)); traceInfo.put("duration", duration); @@ -70,6 +73,18 @@ public class BootStateMachineMonitor extends AbstractStateMachineMonitor stateMachine, Action 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 traceInfo = new HashMap<>(); + traceInfo.put("action", actionName); + traceInfo.put("duration", duration); + traceInfo.put("machine", stateMachine.getId()); + traceRepository.add(traceInfo); + } + private static String transitionToName(Transition transition) { String sourceId = nullStateId(transition.getSource()); String targetId = nullStateId(transition.getTarget()); @@ -86,6 +101,10 @@ public class BootStateMachineMonitor extends AbstractStateMachineMonitor String actionToName(Action action) { + return ObjectUtils.getDisplayString(action); + } + private static String nullStateId(State state) { if (state == null) { return null; diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/ActionListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/ActionListener.java new file mode 100644 index 00000000..9d4020f6 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/ActionListener.java @@ -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 the type of state + * @param the type of event + */ +public interface ActionListener { + + /** + * Notified duration of a particular action. + * + * @param stateMachine the state machine + * @param action the action + * @param duration the transition duration + */ + void onExecute(StateMachine stateMachine, Action action, long duration); +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/CompositeActionListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/CompositeActionListener.java new file mode 100644 index 00000000..4703ab4a --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/CompositeActionListener.java @@ -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 the type of state + * @param the type of event + */ +public class CompositeActionListener extends AbstractCompositeItems> + implements ActionListener { + + @Override + public void onExecute(StateMachine stateMachine, Action action, long duration) { + for (Iterator> iterator = getItems().reverse(); iterator.hasNext();) { + ActionListener listener = iterator.next(); + listener.onExecute(stateMachine, action, duration); + } + } +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/monitor/CompositeStateMachineMonitor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/monitor/CompositeStateMachineMonitor.java index c72b2d06..e0b9d971 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/monitor/CompositeStateMachineMonitor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/monitor/CompositeStateMachineMonitor.java @@ -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 extends AbstractCompositeItems stateMachine, Action transition, long duration) { + for (Iterator> iterator = getItems().reverse(); iterator.hasNext();) { + StateMachineMonitor monitor = iterator.next(); + monitor.action(stateMachine, transition, duration); + } + } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/monitor/StateMachineMonitor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/monitor/StateMachineMonitor.java index ee7259c9..a3e99f1f 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/monitor/StateMachineMonitor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/monitor/StateMachineMonitor.java @@ -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 { /** - * 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 stateMachine, Transition 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 stateMachine, Action action, long duration); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java index 0d7ddb19..54d2d036 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java @@ -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 extends LifecycleObjectSupport impleme private List> triggers = new ArrayList>(); private final CompositeStateListener stateListener = new CompositeStateListener(); private final List> cancellableActions = new ArrayList<>(); + private CompositeActionListener actionListener; /** * Instantiates a new abstract state. @@ -269,6 +272,25 @@ public abstract class AbstractState extends LifecycleObjectSupport impleme stateListener.unregister(listener); } + @Override + public void addActionListener(ActionListener listener) { + synchronized (this) { + if (this.actionListener == null) { + this.actionListener = new CompositeActionListener<>(); + } + this.actionListener.register(listener); + } + } + + @Override + public void removeActionListener(ActionListener listener) { + synchronized (this) { + if (this.actionListener != null) { + this.actionListener.unregister(listener); + } + } + } + /** * Gets the submachine. * @@ -333,6 +355,24 @@ public abstract class AbstractState extends LifecycleObjectSupport impleme } } + /** + * Execute action and notify action listener if set. + * + * @param action the action + * @param context the context + */ + protected void executeAction(Action action, StateContext 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 extends LifecycleObjectSupport impleme @Override public void run() { - action.execute(context); + executeAction(action, context); } }, new Date()); return future; diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ObjectState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ObjectState.java index 6653f967..15f4b0bd 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ObjectState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ObjectState.java @@ -146,7 +146,7 @@ public class ObjectState extends AbstractSimpleState { if (actions != null) { for (Action 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 extends AbstractSimpleState { if (actions != null) { for (Action action : actions) { try { - action.execute(context); + executeAction(action, context); } catch (Exception e) { log.error("Action execution resulted error", e); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java index 1c07afec..76d3ac4c 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java @@ -137,7 +137,7 @@ public class RegionState extends AbstractState { Collection> actions = getExitActions(); if (actions != null) { for (Action action : actions) { - action.execute(context); + executeAction(action, context); } } } @@ -148,7 +148,7 @@ public class RegionState extends AbstractState { Collection> actions = getEntryActions(); if (actions != null) { for (Action action : actions) { - action.execute(context); + executeAction(action, context); } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java index 339941f7..03e3472d 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java @@ -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 { * @param listener the listener */ void removeStateListener(StateListener listener); + + /** + * Adds the action listener. + * + * @param listener the listener + */ + void addActionListener(ActionListener listener); + + /** + * Removes the action listener. + * + * @param listener the listener + */ + void removeActionListener(ActionListener listener); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java index 974391b7..09f128c3 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java @@ -150,7 +150,7 @@ public class StateMachineState extends AbstractState { Collection> actions = getExitActions(); if (actions != null && !isLocal(context)) { for (Action action : actions) { - action.execute(context); + executeAction(action, context); } } } @@ -161,7 +161,7 @@ public class StateMachineState extends AbstractState { Collection> actions = getEntryActions(); if (actions != null && !isLocal(context)) { for (Action action : actions) { - action.execute(context); + executeAction(action, context); } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java index 8c13afe2..21e071de 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -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 extends StateMachineObjectSuppo } }); stateMachineExecutor = executor; + + for (Transition t : getTransitions()) { + t.addActionListener(new ActionListener() { + + @Override + public void onExecute(StateMachine stateMachine, Action action, long duration) { + notifyActionMonitor(stateMachine, action, duration); + } + }); + } + for (State s : getStates()) { + s.addActionListener(new ActionListener() { + @Override + public void onExecute(StateMachine stateMachine, Action action, long duration) { + notifyActionMonitor(stateMachine, action, duration); + } + }); + } } @Override diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java index 1d83922c..9fda7578 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java @@ -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 extends LifecycleObjectSup } } + protected void notifyActionMonitor(StateMachine stateMachine, Action 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 diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java index 87c32838..6067e260 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java @@ -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 implements Transition { private final Guard guard; private final Trigger trigger; private final SecurityRule securityRule; + private CompositeActionListener actionListener; /** * Instantiates a new abstract transition. @@ -133,13 +136,36 @@ public abstract class AbstractTransition implements Transition { return actions; } + @Override + public void addActionListener(ActionListener listener) { + synchronized (this) { + if (this.actionListener == null) { + this.actionListener = new CompositeActionListener<>(); + } + this.actionListener.register(listener); + } + } + + @Override + public void removeActionListener(ActionListener listener) { + synchronized (this) { + if (this.actionListener != null) { + this.actionListener.unregister(listener); + } + } + } + protected final void executeAllActions(StateContext context) { if (actions == null) { return; } for (Action action : actions) { + long now = System.currentTimeMillis(); action.execute(context); + if (this.actionListener != null) { + this.actionListener.onExecute(context.getStateMachine(), action, System.currentTimeMillis() - now); + } } } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java index 5e48b68f..ff165d50 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java @@ -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 { * @return the security rule */ SecurityRule getSecurityRule(); + + /** + * Adds the action listener. + * + * @param listener the listener + */ + void addActionListener(ActionListener listener); + + /** + * Removes the action listener. + * + * @param listener the listener + */ + void removeActionListener(ActionListener listener); } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/monitor/StateMachineMonitorTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/monitor/StateMachineMonitorTests.java index 46df1b77..279b9a5c 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/monitor/StateMachineMonitorTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/monitor/StateMachineMonitorTests.java @@ -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 taction = context.getBean("taction", Action.class); + Action enaction = context.getBean("enaction", Action.class); + Action exaction = context.getBean("exaction", Action.class); + Action 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 taction() { + return new Action() { + @Override + public void execute(StateContext context) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + } + }; + } + + @Bean + public Action enaction() { + return new Action() { + @Override + public void execute(StateContext context) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + } + }; + } + + @Bean + public Action exaction() { + return new Action() { + @Override + public void execute(StateContext context) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + } + }; + } + + @Bean + public Action saction() { + return new Action() { + @Override + public void execute(StateContext context) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + } + }; + } + @Bean public StateMachineMonitor stateMachineMonitor() { return new TestStateMachineMonitor(); @@ -106,18 +171,43 @@ public class StateMachineMonitorTests extends AbstractStateMachineTests { private static class TestStateMachineMonitor extends AbstractStateMachineMonitor { - Transition transition; - Long duration; + Map, Transitions> transitions = new HashMap<>(); + Map, Actions> actions = new HashMap<>(); @Override public void transition(StateMachine stateMachine, Transition transition, long duration) { - this.transition = transition; - this.duration = duration; + transitions.put(transition, new Transitions(transition, duration)); + } + + @Override + public void action(StateMachine stateMachine, Action 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 transition; + Long duration; + public Transitions(Transition transition, Long duration) { + super(); + this.transition = transition; + this.duration = duration; + } + } + @SuppressWarnings("unused") + static class Actions { + Action action; + Long duration; + public Actions(Action action, Long duration) { + this.action = action; + this.duration = duration; + } } } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java index f155dc10..6ca13bc3 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java @@ -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 listener) { + } + + @Override + public void removeActionListener(ActionListener listener) { + } } private static class MockStatemachine implements StateMachine { diff --git a/spring-statemachine-samples/monitoring/src/main/java/demo/monitoring/StateMachineConfig.java b/spring-statemachine-samples/monitoring/src/main/java/demo/monitoring/StateMachineConfig.java index 836077a8..c4a23edf 100644 --- a/spring-statemachine-samples/monitoring/src/main/java/demo/monitoring/StateMachineConfig.java +++ b/spring-statemachine-samples/monitoring/src/main/java/demo/monitoring/StateMachineConfig.java @@ -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");