Add monitoring subsystem
- This is a preliminary work. - New monitoring and tracing conceps around StateMachineMonitor. - Adds hooks internally to better calculate transition times. - Add new annotation configurer for monitors. - New boot module which autoconfigures monitoring for boot's metrics and trancing repos. - New monitoring sample. - Relates to #149
This commit is contained in:
15
build.gradle
15
build.gradle
@@ -129,6 +129,21 @@ project('spring-statemachine-core') {
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-boot') {
|
||||
description = "Spring State Machine Boot"
|
||||
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-core")
|
||||
compile "org.springframework.boot:spring-boot-autoconfigure:$springBootVersion"
|
||||
compile "org.springframework.boot:spring-boot-actuator:$springBootVersion"
|
||||
testCompile "org.springframework.boot:spring-boot-test:$springBootVersion"
|
||||
testCompile "org.springframework:spring-test:$springVersion"
|
||||
testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||
testCompile "junit:junit:$junitVersion"
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-test') {
|
||||
description = "Spring State Machine Test"
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ include 'spring-statemachine-cluster'
|
||||
include 'spring-statemachine-uml'
|
||||
include 'spring-statemachine-build-tests'
|
||||
include 'spring-statemachine-recipes'
|
||||
include 'spring-statemachine-boot'
|
||||
|
||||
include 'spring-statemachine-samples'
|
||||
include 'spring-statemachine-samples:turnstile'
|
||||
@@ -25,6 +26,7 @@ include 'spring-statemachine-samples:eventservice'
|
||||
include 'spring-statemachine-samples:deploy'
|
||||
include 'spring-statemachine-samples:ordershipping'
|
||||
include 'spring-statemachine-samples:datajpa'
|
||||
include 'spring-statemachine-samples:monitoring'
|
||||
|
||||
include 'spring-statemachine-data'
|
||||
include 'spring-statemachine-data:jpa'
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.boot;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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.monitor.AbstractStateMachineMonitor;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* Implementation of a {@link StateMachineMonitor} which converts monitoring
|
||||
* events and bridges those into supported format handled by Spring Boot's
|
||||
* tracing and metrics frameworks.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class BootStateMachineMonitor<S, E> extends AbstractStateMachineMonitor<S, E> {
|
||||
|
||||
private final String METRIC_BASE = "ssm.transition";
|
||||
private final CounterService counterService;
|
||||
private final GaugeService gaugeService;
|
||||
private final TraceRepository traceRepository;
|
||||
|
||||
/**
|
||||
* Instantiates a new boot state machine monitor.
|
||||
*
|
||||
* @param counterService the counter service
|
||||
* @param gaugeService the gauge service
|
||||
* @param traceRepository the trace repository
|
||||
*/
|
||||
public BootStateMachineMonitor(CounterService counterService, GaugeService gaugeService,
|
||||
TraceRepository traceRepository) {
|
||||
this.counterService = counterService;
|
||||
this.gaugeService = gaugeService;
|
||||
this.traceRepository = traceRepository;
|
||||
}
|
||||
|
||||
@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);
|
||||
Map<String, Object> traceInfo = new HashMap<>();
|
||||
traceInfo.put("transition", transitionToName(transition));
|
||||
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());
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(transition.getKind());
|
||||
if (sourceId != null) {
|
||||
buf.append("_");
|
||||
buf.append(sourceId);
|
||||
}
|
||||
if (targetId != null) {
|
||||
buf.append("_");
|
||||
buf.append(targetId);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static <S, E> String nullStateId(State<S, E> state) {
|
||||
if (state == null) {
|
||||
return null;
|
||||
}
|
||||
S id = state.getId();
|
||||
return id != null ? id.toString() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.boot;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.actuate.trace.TraceRepository;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for Spring Statemachine.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({ StateMachineProperties.class })
|
||||
public class StateMachineAutoConfiguration {
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(CounterService.class)
|
||||
@ConditionalOnProperty(prefix = "spring.statemachine.monitor", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public static class StateMachineMonitoringConfiguration {
|
||||
|
||||
private final CounterService counterService;
|
||||
private final GaugeService gaugeService;
|
||||
private final TraceRepository traceRepository;
|
||||
|
||||
public StateMachineMonitoringConfiguration(ObjectProvider<CounterService> counterServiceProvider,
|
||||
ObjectProvider<GaugeService> gaugeServiceProvider,
|
||||
ObjectProvider<TraceRepository> traceRepositoryProvider) {
|
||||
this.counterService = counterServiceProvider.getIfAvailable();
|
||||
this.gaugeService = gaugeServiceProvider.getIfAvailable();
|
||||
this.traceRepository = traceRepositoryProvider.getIfAvailable();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BootStateMachineMonitor<?, ?> bootStateMachineMonitor() {
|
||||
return new BootStateMachineMonitor<>(counterService, gaugeService, traceRepository);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.boot;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Spring Boot {@link ConfigurationProperties} for <em>spring.statemachine</em>.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
@ConfigurationProperties(value = "spring.statemachine")
|
||||
public class StateMachineProperties {
|
||||
|
||||
private StateMachineMonitoringProperties monitor;
|
||||
|
||||
public StateMachineMonitoringProperties getMonitor() {
|
||||
return monitor;
|
||||
}
|
||||
|
||||
public void setMonitor(StateMachineMonitoringProperties monitor) {
|
||||
this.monitor = monitor;
|
||||
}
|
||||
|
||||
public static class StateMachineMonitoringProperties {
|
||||
private boolean enabled = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.statemachine.boot.StateMachineAutoConfiguration
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.boot;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.test.util.EnvironmentTestUtils;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
|
||||
/**
|
||||
* Tests for {@link StateMachineAutoConfiguration}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class StateMachineAutoConfigurationTests {
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaults() throws Exception {
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
context.register(StateMachineAutoConfiguration.class);
|
||||
context.refresh();
|
||||
assertThat(context.containsBean("bootStateMachineMonitor"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMonitorDisabled() throws Exception {
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(context, "spring.statemachine.monitor.enabled:false");
|
||||
context.register(StateMachineAutoConfiguration.class);
|
||||
context.refresh();
|
||||
assertThat(context.containsBean("bootStateMachineMonitor"), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMonitoringAddedViaAutoconfig() throws Exception {
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
context.register(StateMachineAutoConfiguration.class, Config1.class);
|
||||
context.refresh();
|
||||
StateMachine stateMachine = context.getBean(StateMachine.class);
|
||||
Object compositeStateMachineMonitor = TestUtils.readField("stateMachineMonitor", stateMachine);
|
||||
Object orderedCompositeItem = TestUtils.readField("items", compositeStateMachineMonitor);
|
||||
List<Object> list = TestUtils.readField("list", orderedCompositeItem);
|
||||
assertThat(list, notNullValue());
|
||||
assertThat(list.size(), is(1));
|
||||
assertThat(list.get(0), instanceOf(BootStateMachineMonitor.class));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config1 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||
throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("S1")
|
||||
.state("S2")
|
||||
.state("S3");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||
throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("S1").target("S2").event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S2").target("S3").event("E2");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.boot;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Utils for tests.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public class TestUtils {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T readField(String name, Object target) throws Exception {
|
||||
Field field = null;
|
||||
Class<?> clazz = target.getClass();
|
||||
do {
|
||||
try {
|
||||
field = clazz.getDeclaredField(name);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
|
||||
clazz = clazz.getSuperclass();
|
||||
} while (field == null && !clazz.equals(Object.class));
|
||||
|
||||
if (field == null)
|
||||
throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of "
|
||||
+ target.getClass());
|
||||
field.setAccessible(true);
|
||||
return (T) field.get(target);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T callMethod(String name, Object target) throws Exception {
|
||||
Class<?> clazz = target.getClass();
|
||||
Method method = ReflectionUtils.findMethod(clazz, name);
|
||||
|
||||
if (method == null)
|
||||
throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of "
|
||||
+ target.getClass());
|
||||
method.setAccessible(true);
|
||||
return (T) ReflectionUtils.invokeMethod(method, target);
|
||||
}
|
||||
|
||||
public static void setField(String name, Object target, Object value) throws Exception {
|
||||
Field field = null;
|
||||
Class<?> clazz = target.getClass();
|
||||
do {
|
||||
try {
|
||||
field = clazz.getDeclaredField(name);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
|
||||
clazz = clazz.getSuperclass();
|
||||
} while (field == null && !clazz.equals(Object.class));
|
||||
|
||||
if (field == null)
|
||||
throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of "
|
||||
+ target.getClass());
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T callMethod(String name, Object target, Object[] args, Class<?>[] argsTypes) throws Exception {
|
||||
Class<?> clazz = target.getClass();
|
||||
Method method = ReflectionUtils.findMethod(clazz, name, argsTypes);
|
||||
|
||||
if (method == null)
|
||||
throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of "
|
||||
+ target.getClass());
|
||||
method.setAccessible(true);
|
||||
return (T) ReflectionUtils.invokeMethod(method, target, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.statemachine.access;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||
|
||||
/**
|
||||
@@ -51,6 +52,13 @@ public interface StateMachineAccess<S, E> {
|
||||
*/
|
||||
void addStateMachineInterceptor(StateMachineInterceptor<S, E> interceptor);
|
||||
|
||||
/**
|
||||
* Adds the state machine monitor.
|
||||
*
|
||||
* @param monitor the monitor
|
||||
*/
|
||||
void addStateMachineMonitor(StateMachineMonitor<S, E> monitor);
|
||||
|
||||
/**
|
||||
* Sets if initial state is enabled when a state machine is
|
||||
* using sub states.
|
||||
|
||||
@@ -53,6 +53,7 @@ import org.springframework.statemachine.config.model.verifier.CompositeStateMach
|
||||
import org.springframework.statemachine.config.model.verifier.StateMachineModelVerifier;
|
||||
import org.springframework.statemachine.ensemble.DistributedStateMachine;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
import org.springframework.statemachine.security.StateMachineSecurityInterceptor;
|
||||
import org.springframework.statemachine.state.AbstractState;
|
||||
@@ -112,6 +113,8 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
|
||||
private String beanName;
|
||||
|
||||
private StateMachineMonitor<S, E> defaultStateMachineMonitor;
|
||||
|
||||
/**
|
||||
* Instantiates a new abstract state machine factory.
|
||||
*
|
||||
@@ -265,6 +268,23 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
}
|
||||
});
|
||||
|
||||
// add monitoring hooks
|
||||
final StateMachineMonitor<S, E> stateMachineMonitor = stateMachineModel.getConfigurationData().getStateMachineMonitor();
|
||||
if (stateMachineMonitor != null || defaultStateMachineMonitor != null) {
|
||||
fmachine.getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S ,E>>() {
|
||||
|
||||
@Override
|
||||
public void apply(StateMachineAccess<S, E> function) {
|
||||
if (defaultStateMachineMonitor != null) {
|
||||
function.addStateMachineMonitor(defaultStateMachineMonitor);
|
||||
}
|
||||
if (stateMachineMonitor != null) {
|
||||
function.addStateMachineMonitor(stateMachineMonitor);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: should error out if sec is enabled but spring-security is not in cp
|
||||
if (stateMachineModel.getConfigurationData().isSecurityEnabled()) {
|
||||
final StateMachineSecurityInterceptor<S, E> securityInterceptor = new StateMachineSecurityInterceptor<S, E>(
|
||||
@@ -324,6 +344,15 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
this.contextEvents = contextEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sett state machine monitor.
|
||||
*
|
||||
* @param stateMachineMonitor the state machine monitor
|
||||
*/
|
||||
public void setStateMachineMonitor(StateMachineMonitor<S, E> stateMachineMonitor) {
|
||||
this.defaultStateMachineMonitor = stateMachineMonitor;
|
||||
}
|
||||
|
||||
private StateMachine<S, E> delegateAutoStartup(StateMachine<S, E> delegate) {
|
||||
if (handleAutostartup && delegate instanceof SmartLifecycle && ((SmartLifecycle) delegate).isAutoStartup()) {
|
||||
((SmartLifecycle)delegate).start();
|
||||
|
||||
@@ -28,9 +28,11 @@ import org.springframework.statemachine.config.common.annotation.ObjectPostProce
|
||||
import org.springframework.statemachine.config.configurers.ConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultDistributedStateMachineConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultMonitoringConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultSecurityConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultVerifierConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DistributedStateMachineConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.MonitoringConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.SecurityConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.VerifierConfigurer;
|
||||
import org.springframework.statemachine.config.model.ConfigurationData;
|
||||
@@ -38,6 +40,7 @@ import org.springframework.statemachine.config.model.StatesData;
|
||||
import org.springframework.statemachine.config.model.verifier.StateMachineModelVerifier;
|
||||
import org.springframework.statemachine.ensemble.StateMachineEnsemble;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.security.SecurityRule;
|
||||
|
||||
/**
|
||||
@@ -66,6 +69,7 @@ public class StateMachineConfigurationBuilder<S, E>
|
||||
private AccessDecisionManager eventSecurityAccessDecisionManager;
|
||||
private SecurityRule eventSecurityRule;
|
||||
private SecurityRule transitionSecurityRule;
|
||||
private StateMachineMonitor<S, E> stateMachineMonitor;
|
||||
|
||||
/**
|
||||
* Instantiates a new state machine configuration builder.
|
||||
@@ -114,11 +118,16 @@ public class StateMachineConfigurationBuilder<S, E>
|
||||
return apply(new DefaultVerifierConfigurer<S, E>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MonitoringConfigurer<S, E> withMonitoring() throws Exception {
|
||||
return apply(new DefaultMonitoringConfigurer<S, E>());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigurationData<S, E> performBuild() throws Exception {
|
||||
return new ConfigurationData<S, E>(beanFactory, taskExecutor, taskScheculer, autoStart, ensemble, listeners,
|
||||
securityEnabled, transitionSecurityAccessDecisionManager, eventSecurityAccessDecisionManager, eventSecurityRule,
|
||||
transitionSecurityRule, verifierEnabled, verifier, machineId);
|
||||
transitionSecurityRule, verifierEnabled, verifier, machineId, stateMachineMonitor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,6 +212,15 @@ public class StateMachineConfigurationBuilder<S, E>
|
||||
this.verifierEnabled = verifierEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state machine monitor.
|
||||
*
|
||||
* @param stateMachineMonitor the state machine monitor
|
||||
*/
|
||||
public void setStateMachineMonitor(StateMachineMonitor<S, E> stateMachineMonitor) {
|
||||
this.stateMachineMonitor = stateMachineMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the security transition access decision manager.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.statemachine.config.builders;
|
||||
|
||||
import org.springframework.statemachine.config.configurers.ConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DistributedStateMachineConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.MonitoringConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.SecurityConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.VerifierConfigurer;
|
||||
|
||||
@@ -61,4 +62,12 @@ public interface StateMachineConfigurationConfigurer<S, E> {
|
||||
* @throws Exception if configuration error happens
|
||||
*/
|
||||
VerifierConfigurer<S, E> withVerifier() throws Exception;
|
||||
|
||||
/**
|
||||
* Gets a configurer for state machine monitoring.
|
||||
*
|
||||
* @return {@link MonitoringConfigurer} for chaining
|
||||
* @throws Exception if configuration error happens
|
||||
*/
|
||||
MonitoringConfigurer<S, E> withMonitoring() throws Exception;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
@@ -42,6 +43,7 @@ import org.springframework.statemachine.config.model.DefaultStateMachineModel;
|
||||
import org.springframework.statemachine.config.model.ConfigurationData;
|
||||
import org.springframework.statemachine.config.model.StatesData;
|
||||
import org.springframework.statemachine.config.model.TransitionsData;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -114,6 +116,7 @@ public class StateMachineConfiguration<S, E> extends
|
||||
private SmartLifecycle lifecycle;
|
||||
private DisposableBean disposableBean;
|
||||
private String beanName;
|
||||
private StateMachineMonitor<S, E> stateMachineMonitor;
|
||||
|
||||
public StateMachineDelegatingFactoryBean(StateMachineConfigBuilder<S, E> builder, Class<StateMachine<S, E>> clazz,
|
||||
String clazzName, Boolean contextEvents) {
|
||||
@@ -160,6 +163,9 @@ public class StateMachineConfiguration<S, E> extends
|
||||
stateMachineFactory.setContextEventsEnabled(contextEvents);
|
||||
stateMachineFactory.setBeanName(beanName);
|
||||
stateMachineFactory.setHandleAutostartup(stateMachineConfigurationConfig.isAutoStart());
|
||||
if (stateMachineMonitor != null) {
|
||||
stateMachineFactory.setStateMachineMonitor(stateMachineMonitor);
|
||||
}
|
||||
StateMachine<S, E> stateMachine = stateMachineFactory.getStateMachine();
|
||||
this.lifecycle = (SmartLifecycle) stateMachine;
|
||||
this.disposableBean = (DisposableBean) stateMachine;
|
||||
@@ -201,6 +207,10 @@ public class StateMachineConfiguration<S, E> extends
|
||||
lifecycle.stop(callback);
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setStateMachineMonitor(StateMachineMonitor<S, E> stateMachineMonitor) {
|
||||
this.stateMachineMonitor = stateMachineMonitor;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.config.configurers;
|
||||
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurationBuilder;
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.model.ConfigurationData;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link MonitoringConfigurer}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class DefaultMonitoringConfigurer <S, E>
|
||||
extends AnnotationConfigurerAdapter<ConfigurationData<S, E>, StateMachineConfigurationConfigurer<S, E>, StateMachineConfigurationBuilder<S, E>>
|
||||
implements MonitoringConfigurer<S, E> {
|
||||
|
||||
private StateMachineMonitor<S, E> monitor;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationBuilder<S, E> builder) throws Exception {
|
||||
builder.setStateMachineMonitor(monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MonitoringConfigurer<S, E> monitor(StateMachineMonitor<S, E> monitor) {
|
||||
this.monitor = monitor;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.config.configurers;
|
||||
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
|
||||
/**
|
||||
* Base {@code MonitoringConfigurer} interface for configuring state machine monitoring.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface MonitoringConfigurer <S, E> extends
|
||||
AnnotationConfigurerBuilder<StateMachineConfigurationConfigurer<S, E>> {
|
||||
|
||||
/**
|
||||
* Specify a state machine monitor.
|
||||
*
|
||||
* @param monitor the state machine monitor
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
MonitoringConfigurer<S, E> monitor(StateMachineMonitor<S, E> monitor);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import org.springframework.statemachine.config.model.verifier.DefaultStateMachin
|
||||
import org.springframework.statemachine.config.model.verifier.StateMachineModelVerifier;
|
||||
import org.springframework.statemachine.ensemble.StateMachineEnsemble;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.security.SecurityRule;
|
||||
|
||||
/**
|
||||
@@ -55,13 +56,14 @@ public class ConfigurationData<S, E> {
|
||||
private final AccessDecisionManager eventSecurityAccessDecisionManager;
|
||||
private final SecurityRule eventSecurityRule;
|
||||
private final SecurityRule transitionSecurityRule;
|
||||
private final StateMachineMonitor<S, E> stateMachineMonitor;
|
||||
|
||||
/**
|
||||
* Instantiates a new state machine configuration config data.
|
||||
*/
|
||||
public ConfigurationData() {
|
||||
this(null, new SyncTaskExecutor(), new ConcurrentTaskScheduler(), false, null, new ArrayList<StateMachineListener<S, E>>(), false,
|
||||
null, null, null, null, true, new DefaultStateMachineModelVerifier<S, E>(), null);
|
||||
null, null, null, null, true, new DefaultStateMachineModelVerifier<S, E>(), null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +89,7 @@ public class ConfigurationData<S, E> {
|
||||
List<StateMachineListener<S, E>> listeners, boolean securityEnabled,
|
||||
AccessDecisionManager transitionSecurityAccessDecisionManager, AccessDecisionManager eventSecurityAccessDecisionManager,
|
||||
SecurityRule eventSecurityRule, SecurityRule transitionSecurityRule, boolean verifierEnabled,
|
||||
StateMachineModelVerifier<S, E> verifier, String machineId) {
|
||||
StateMachineModelVerifier<S, E> verifier, String machineId, StateMachineMonitor<S, E> stateMachineMonitor) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.taskExecutor = taskExecutor;
|
||||
this.taskScheduler = taskScheduler;
|
||||
@@ -102,6 +104,7 @@ public class ConfigurationData<S, E> {
|
||||
this.verifierEnabled = verifierEnabled;
|
||||
this.verifier = verifier;
|
||||
this.machineId = machineId;
|
||||
this.stateMachineMonitor = stateMachineMonitor;
|
||||
}
|
||||
|
||||
public String getMachineId() {
|
||||
@@ -189,6 +192,15 @@ public class ConfigurationData<S, E> {
|
||||
return verifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the state machine monitor.
|
||||
*
|
||||
* @return the state machine monitor
|
||||
*/
|
||||
public StateMachineMonitor<S, E> getStateMachineMonitor() {
|
||||
return stateMachineMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the transition security access decision manager.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* Base implementation of a {@link StateMachineMonitor}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public abstract class AbstractStateMachineMonitor<S, E> implements StateMachineMonitor<S, E> {
|
||||
|
||||
@Override
|
||||
public void transition(StateMachine<S, E> stateMachine, Transition<S, E> transition, long duration) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.support.AbstractCompositeItems;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* Implementation of a {@link StateMachineMonitor} backed by a multiple monitors.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class CompositeStateMachineMonitor<S, E> extends AbstractCompositeItems<StateMachineMonitor<S, E>>
|
||||
implements StateMachineMonitor<S, E> {
|
||||
|
||||
@Override
|
||||
public void transition(StateMachine<S, E> stateMachine, Transition<S, E> transition, long duration) {
|
||||
for (Iterator<StateMachineMonitor<S, E>> iterator = getItems().reverse(); iterator.hasNext();) {
|
||||
StateMachineMonitor<S, E> monitor = iterator.next();
|
||||
monitor.transition(stateMachine, transition, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* {@code StateMachineMonitor} for various state machine monitoring events.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface StateMachineMonitor<S, E> {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import org.springframework.statemachine.access.StateMachineAccess;
|
||||
import org.springframework.statemachine.access.StateMachineAccessor;
|
||||
import org.springframework.statemachine.access.StateMachineFunction;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
import org.springframework.statemachine.state.AbstractState;
|
||||
import org.springframework.statemachine.state.ForkPseudoState;
|
||||
@@ -291,6 +292,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
|
||||
@Override
|
||||
public void transit(Transition<S, E> t, StateContext<S, E> ctx, Message<E> message) {
|
||||
long now = System.currentTimeMillis();
|
||||
// TODO: fix above stateContext as it's not used
|
||||
notifyTransitionStart(buildStateContext(Stage.TRANSITION_START, message, t, getRelayStateMachine()));
|
||||
notifyTransition(buildStateContext(Stage.TRANSITION, message, t, getRelayStateMachine()));
|
||||
@@ -306,6 +308,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
}
|
||||
// TODO: looks like events should be called here and anno processing earlier
|
||||
notifyTransitionEnd(buildStateContext(Stage.TRANSITION_END, message, t, getRelayStateMachine()));
|
||||
notifyTransitionMonitor(getRelayStateMachine(), t, System.currentTimeMillis() - now);
|
||||
}
|
||||
});
|
||||
stateMachineExecutor = executor;
|
||||
@@ -662,6 +665,11 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
stateMachineExecutor.addStateMachineInterceptor(interceptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStateMachineMonitor(StateMachineMonitor<S, E> monitor) {
|
||||
getStateMachineMonitor().register(monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.event.StateMachineEventPublisher;
|
||||
import org.springframework.statemachine.listener.CompositeStateMachineListener;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.monitor.CompositeStateMachineMonitor;
|
||||
import org.springframework.statemachine.processor.StateMachineHandlerCallHelper;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
@@ -46,6 +47,7 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
|
||||
private static final Log log = LogFactory.getLog(StateMachineObjectSupport.class);
|
||||
|
||||
private final CompositeStateMachineListener<S, E> stateListener = new CompositeStateMachineListener<S, E>();
|
||||
private final CompositeStateMachineMonitor<S, E> stateMachineMonitor = new CompositeStateMachineMonitor<S, E>();
|
||||
|
||||
/** Context application event publisher if exist */
|
||||
private volatile StateMachineEventPublisher stateMachineEventPublisher;
|
||||
@@ -128,6 +130,10 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
|
||||
return stateListener;
|
||||
}
|
||||
|
||||
protected CompositeStateMachineMonitor<S, E> getStateMachineMonitor() {
|
||||
return stateMachineMonitor;
|
||||
}
|
||||
|
||||
protected void notifyStateChanged(StateContext<S, E> stateContext) {
|
||||
try {
|
||||
stateMachineHandlerCallHelper.callOnStateChanged(getBeanName(), stateContext);
|
||||
@@ -304,6 +310,14 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyTransitionMonitor(StateMachine<S, E> stateMachine, Transition<S, E> transition, long duration) {
|
||||
try {
|
||||
stateMachineMonitor.transition(stateMachine, transition, 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
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.statemachine.ExtendedState;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
@@ -96,6 +97,10 @@ public class StateMachineAccessTests {
|
||||
public void addStateMachineInterceptor(StateMachineInterceptor<String, String> interceptor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStateMachineMonitor(StateMachineMonitor<String, String> monitor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRelay(StateMachine<String, String> stateMachine) {
|
||||
this.relay = stateMachine;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class StateMachineModelTests {
|
||||
|
||||
ConfigurationData<String, String> configurationData = new ConfigurationData<>(beanFactory, taskExecutor, taskScheduler, autoStart,
|
||||
ensemble, listeners, securityEnabled, transitionSecurityAccessDecisionManager, eventSecurityAccessDecisionManager,
|
||||
eventSecurityRule, transitionSecurityRule, verifierEnabled, verifier, null);
|
||||
eventSecurityRule, transitionSecurityRule, verifierEnabled, verifier, null, null);
|
||||
|
||||
Collection<StateData<String, String>> stateData = new ArrayList<>();
|
||||
StateData<String, String> stateData1 = new StateData<String, String>(null, null, "S1", null, null, null);
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
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.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineSystemConstants;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
public class StateMachineMonitorTests extends AbstractStateMachineTests {
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@Test
|
||||
public void testSimpleMonitor() throws Exception {
|
||||
context.register(Config1.class);
|
||||
context.refresh();
|
||||
StateMachine<String, String> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
|
||||
TestStateMachineMonitor monitor = context.getBean(TestStateMachineMonitor.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());
|
||||
monitor.reset();
|
||||
machine.sendEvent("E2");
|
||||
assertThat(machine.getState().getIds(), contains("S1"));
|
||||
assertThat(monitor.transition, notNullValue());
|
||||
assertThat(monitor.duration, notNullValue());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config1 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||
throws Exception {
|
||||
config
|
||||
.withMonitoring()
|
||||
.monitor(stateMachineMonitor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("S1")
|
||||
.state("S2");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("S1")
|
||||
.target("S2")
|
||||
.event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S2")
|
||||
.target("S1")
|
||||
.event("E2");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StateMachineMonitor<String, String> stateMachineMonitor() {
|
||||
return new TestStateMachineMonitor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
private static class TestStateMachineMonitor extends AbstractStateMachineMonitor<String, String> {
|
||||
|
||||
Transition<String, String> transition;
|
||||
Long duration;
|
||||
|
||||
@Override
|
||||
public void transition(StateMachine<String, String> stateMachine, Transition<String, String> transition, long duration) {
|
||||
this.transition = transition;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
transition = null;
|
||||
duration = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,3 +120,11 @@ project('spring-statemachine-samples-datajpa') {
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-monitoring') {
|
||||
description = 'Spring State Machine Monitoring Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-boot")
|
||||
compile("org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 demo.monitoring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
//tag::snippetA[]
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
//end::snippetA[]
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 demo.monitoring;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class StateMachineConfig {
|
||||
|
||||
//tag::snippetA[]
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||
throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("S1")
|
||||
.state("S2")
|
||||
.state("S3");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||
throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("S1").target("S2").event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S2").target("S3").event("E2");
|
||||
}
|
||||
}
|
||||
//end::snippetA[]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 demo.monitoring;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@Controller
|
||||
public class StateMachineController {
|
||||
|
||||
@Autowired
|
||||
private StateMachine<String, String> stateMachine;
|
||||
|
||||
@RequestMapping("/")
|
||||
public String home() {
|
||||
return "redirect:/state";
|
||||
}
|
||||
|
||||
@RequestMapping("/state")
|
||||
public String feedAndGetStates(@RequestParam(value = "events", required = false) List<String> events, Model model) throws Exception {
|
||||
StateMachineLogListener listener = new StateMachineLogListener();
|
||||
stateMachine.addStateListener(listener);
|
||||
stateMachine.start();
|
||||
if (events != null) {
|
||||
for (String event : events) {
|
||||
stateMachine.sendEvent(event);
|
||||
}
|
||||
}
|
||||
stateMachine.stop();
|
||||
model.addAttribute("allEvents", new String[]{"E1", "E2"});
|
||||
model.addAttribute("messages", createMessages(listener.getMessages()));
|
||||
return "states";
|
||||
}
|
||||
|
||||
private String createMessages(List<String> messages) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (String message : messages) {
|
||||
buf.append(message);
|
||||
buf.append("\n");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 demo.monitoring;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateContext.Stage;
|
||||
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||
|
||||
public class StateMachineLogListener extends StateMachineListenerAdapter<String, String> {
|
||||
|
||||
private final LinkedList<String> messages = new LinkedList<String>();
|
||||
|
||||
public List<String> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void resetMessages() {
|
||||
messages.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateContext(StateContext<String, String> stateContext) {
|
||||
if (stateContext.getStage() == Stage.STATE_ENTRY) {
|
||||
messages.addFirst("Enter " + stateContext.getTarget().getId());
|
||||
} else if (stateContext.getStage() == Stage.STATE_EXIT) {
|
||||
messages.addFirst("Exit " + stateContext.getSource().getId());
|
||||
} else if (stateContext.getStage() == Stage.STATEMACHINE_START) {
|
||||
messages.addLast("Machine started");
|
||||
} else if (stateContext.getStage() == Stage.STATEMACHINE_STOP) {
|
||||
messages.addFirst("Machine stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
management:
|
||||
security:
|
||||
enabled: false
|
||||
security:
|
||||
basic:
|
||||
enabled: false
|
||||
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Spring Statemachine Demo</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
</head>
|
||||
<body>
|
||||
<form action="#" data-th-action="@{/state}" data-th-object="${model}" method="post">
|
||||
<div>
|
||||
<p th:text="'Choose events'"/>
|
||||
<ul>
|
||||
<li th:each="ty : ${allEvents}">
|
||||
<input type="checkbox" name="events" th:value="${ty}" />
|
||||
<label th:text="${ty}">replaced</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button type="submit">Send Events</button>
|
||||
</form>
|
||||
<div>
|
||||
<textarea th:text="${messages}" rows="20" cols="100"/>
|
||||
</div>
|
||||
<div>
|
||||
<form action="#" data-th-action="@{/state}" method="get">
|
||||
<button type="submit">Refresh</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user