Refactor region handling and persistence
- Make kryo in AbstractKryoStateMachineSerialisationService aware of same classloader most likely use in an app. This takes away some of those weird kryo errors you see with a web apps. - Add context references concept to StateMachineContext which can be used to store reference id and then individual running machines with regions can independently store their states. Whole machine state can then get restored more accurately. - Add new `region(String id)` to StateConfigurer which can be used to set region id. This is equivalent as setting region id with json based machine structure where you need to define region id's with orthogonal regions are in use. - Add new datajpamultipersist sample showing running regions and how those are persisted to a database. - Fixes #617 - Fixes #605 - Fixes #615
This commit is contained in:
@@ -27,6 +27,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:datajpamultipersist'
|
||||
include 'spring-statemachine-samples:datapersist'
|
||||
include 'spring-statemachine-samples:monitoring'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -42,6 +42,13 @@ public interface StateMachineContext<S, E> {
|
||||
*/
|
||||
List<StateMachineContext<S, E>> getChilds();
|
||||
|
||||
/**
|
||||
* Gets the child context references if any.
|
||||
*
|
||||
* @return the child context references
|
||||
*/
|
||||
List<String> getChildReferences();
|
||||
|
||||
/**
|
||||
* Gets the state.
|
||||
*
|
||||
|
||||
@@ -80,6 +80,7 @@ import org.springframework.statemachine.state.StateMachineState;
|
||||
import org.springframework.statemachine.support.DefaultExtendedState;
|
||||
import org.springframework.statemachine.support.LifecycleObjectSupport;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptorAdapter;
|
||||
import org.springframework.statemachine.support.tree.Tree;
|
||||
import org.springframework.statemachine.support.tree.Tree.Node;
|
||||
import org.springframework.statemachine.support.tree.TreeTraverser;
|
||||
@@ -219,9 +220,14 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
|
||||
if (initialCount > 1) {
|
||||
for (Collection<StateData<S, E>> regionStateDatas : regionsStateDatas) {
|
||||
// try to build reqion id's
|
||||
Object rId = regionStateDatas.iterator().next().getRegion();
|
||||
String mId = machineId != null ? machineId : stateMachineModel.getConfigurationData().getMachineId();
|
||||
mId = mId + "#" + (rId != null ? rId.toString() : "");
|
||||
|
||||
machine = buildMachine(machineMap, stateMap, holderList, regionStateDatas, transitionsData, resolveBeanFactory(stateMachineModel),
|
||||
contextEvents, defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(stateMachineModel),
|
||||
resolveTaskScheduler(stateMachineModel), machineId, null, stateMachineModel);
|
||||
resolveTaskScheduler(stateMachineModel), mId, null, stateMachineModel);
|
||||
regionStack.push(new MachineStackItem<S, E>(machine));
|
||||
machines.add(machine);
|
||||
}
|
||||
@@ -359,10 +365,12 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
List<StateMachineInterceptor<S,E>> interceptors = stateMachineModel.getConfigurationData().getStateMachineInterceptors();
|
||||
if (interceptors != null) {
|
||||
for (final StateMachineInterceptor<S, E> interceptor : interceptors) {
|
||||
machine.getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S,E>>() {
|
||||
// add persisting interceptor hooks to all regions
|
||||
RegionPersistingInterceptorAdapter<S, E> adapter = new RegionPersistingInterceptorAdapter<>(interceptor, machine);
|
||||
machine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess<S,E>>() {
|
||||
@Override
|
||||
public void apply(StateMachineAccess<S, E> function) {
|
||||
function.addStateMachineInterceptor(interceptor);
|
||||
function.addStateMachineInterceptor(adapter);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -377,6 +385,42 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
return delegateAutoStartup(machine);
|
||||
}
|
||||
|
||||
private static class RegionPersistingInterceptorAdapter<S, E> extends StateMachineInterceptorAdapter<S, E> {
|
||||
|
||||
private final StateMachineInterceptor<S, E> interceptor;
|
||||
private final StateMachine<S, E> rootStateMachine;
|
||||
|
||||
public RegionPersistingInterceptorAdapter(StateMachineInterceptor<S, E> interceptor, StateMachine<S, E> rootStateMachine) {
|
||||
this.interceptor = interceptor;
|
||||
this.rootStateMachine = rootStateMachine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine) {
|
||||
interceptor.preStateChange(state, message, transition, stateMachine, rootStateMachine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine) {
|
||||
interceptor.preStateChange(state, message, transition, stateMachine, rootStateMachine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine) {
|
||||
interceptor.postStateChange(state, message, transition, stateMachine, rootStateMachine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine) {
|
||||
interceptor.postStateChange(state, message, transition, stateMachine, rootStateMachine);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs this factory to handle auto-start flag manually
|
||||
* by calling lifecycle start method.
|
||||
|
||||
@@ -47,7 +47,7 @@ public class DefaultStateConfigurer<S, E>
|
||||
implements StateConfigurer<S, E> {
|
||||
|
||||
private Object parent;
|
||||
private final Object region = UUID.randomUUID().toString();
|
||||
private Object region = UUID.randomUUID().toString();
|
||||
private final Map<S, StateData<S, E>> incomplete = new HashMap<S, StateData<S, E>>();
|
||||
private S initialState;
|
||||
private Action<S, E> initialAction;
|
||||
@@ -123,6 +123,12 @@ public class DefaultStateConfigurer<S, E>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateConfigurer<S, E> region(String id) {
|
||||
this.region = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateConfigurer<S, E> end(S end) {
|
||||
this.ends.add(end);
|
||||
|
||||
@@ -63,6 +63,14 @@ public interface StateConfigurer<S, E> extends
|
||||
*/
|
||||
StateConfigurer<S, E> parent(S state);
|
||||
|
||||
/**
|
||||
* Specify a region for these states configured by this configurer instance.
|
||||
*
|
||||
* @param id the region id
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
StateConfigurer<S, E> region(String id);
|
||||
|
||||
/**
|
||||
* Specify a state {@code S}.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -211,11 +211,22 @@ public class DistributedStateMachine<S, E> extends LifecycleObjectSupport implem
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine) {
|
||||
preStateChange(state, message, transition, stateMachine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateContext<S, E> preTransition(StateContext<S, E> stateContext) {
|
||||
return stateContext;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2019 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.
|
||||
@@ -18,8 +18,11 @@ package org.springframework.statemachine.persist;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.statemachine.ExtendedState;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
@@ -56,36 +59,61 @@ import org.springframework.util.Assert;
|
||||
public abstract class AbstractPersistingStateMachineInterceptor<S, E, T> extends StateMachineInterceptorAdapter<S, E>
|
||||
implements StateMachinePersist<S, E, T> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(AbstractPersistingStateMachineInterceptor.class);
|
||||
private Function<StateMachine<S, E>, Map<Object, Object>> extendedStateVariablesFunction = new AllVariablesFunction<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
|
||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("preStateChange with stateMachine " + stateMachine);
|
||||
log.debug("preStateChange with root stateMachine " + rootStateMachine);
|
||||
log.debug("preStateChange with state " + state);
|
||||
}
|
||||
// try to persist context and in case of failure, interceptor
|
||||
// call chain aborts transition
|
||||
// TODO: should probably come up with a policy vs. not force feeding this functionality
|
||||
try {
|
||||
write(buildStateMachineContext(stateMachine, state), (T)stateMachine.getId());
|
||||
write(buildStateMachineContext(stateMachine, rootStateMachine, state), (T)stateMachine.getId());
|
||||
} catch (Exception e) {
|
||||
throw new StateMachineException("Unable to persist stateMachineContext", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine) {
|
||||
preStateChange(state, message, transition, stateMachine, stateMachine);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
|
||||
public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("postStateChange with stateMachine " + stateMachine);
|
||||
log.debug("postStateChange with root stateMachine " + rootStateMachine);
|
||||
log.debug("postStateChange with state " + state);
|
||||
}
|
||||
// initial transitions are never intercepted as those cannot fail or get aborted.
|
||||
// for now, handle persistence in post state change
|
||||
// TODO: consider intercept initial transition, but not aborting if error is thrown?
|
||||
if (state != null && transition != null && transition.getKind() == TransitionKind.INITIAL) {
|
||||
try {
|
||||
write(buildStateMachineContext(stateMachine, state), (T)stateMachine.getId());
|
||||
write(buildStateMachineContext(stateMachine, rootStateMachine, state), (T)stateMachine.getId());
|
||||
} catch (Exception e) {
|
||||
throw new StateMachineException("Unable to persist stateMachineContext", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine) {
|
||||
postStateChange(state, message, transition, stateMachine, stateMachine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write {@link StateMachineContext} into persistent store.
|
||||
*
|
||||
@@ -119,22 +147,27 @@ public abstract class AbstractPersistingStateMachineInterceptor<S, E, T> extends
|
||||
* Builds the state machine context.
|
||||
*
|
||||
* @param stateMachine the state machine
|
||||
* @param rootStateMachine the root state machine
|
||||
* @param state the state
|
||||
* @return the state machine context
|
||||
*/
|
||||
protected StateMachineContext<S, E> buildStateMachineContext(StateMachine<S, E> stateMachine, State<S, E> state) {
|
||||
protected StateMachineContext<S, E> buildStateMachineContext(StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine, State<S, E> state) {
|
||||
ExtendedState extendedState = new DefaultExtendedState();
|
||||
extendedState.getVariables().putAll(extendedStateVariablesFunction.apply(stateMachine));
|
||||
|
||||
ArrayList<StateMachineContext<S, E>> childs = new ArrayList<StateMachineContext<S, E>>();
|
||||
List<StateMachineContext<S, E>> childs = new ArrayList<StateMachineContext<S, E>>();
|
||||
List<String> childRefs = new ArrayList<>();
|
||||
S id = null;
|
||||
if (state.isSubmachineState()) {
|
||||
id = getDeepState(state);
|
||||
} else if (state.isOrthogonal()) {
|
||||
Collection<Region<S, E>> regions = ((AbstractState<S, E>)state).getRegions();
|
||||
for (Region<S, E> r : regions) {
|
||||
StateMachine<S, E> rsm = (StateMachine<S, E>) r;
|
||||
childs.add(buildStateMachineContext(rsm, state));
|
||||
if (stateMachine.getState().isOrthogonal()) {
|
||||
Collection<Region<S, E>> regions = ((AbstractState<S, E>)state).getRegions();
|
||||
for (Region<S, E> r : regions) {
|
||||
// realistically we can only add refs because reqions are independent
|
||||
// and when restoring, those child contexts need to get dehydrated
|
||||
childRefs.add(r.getId());
|
||||
}
|
||||
}
|
||||
id = state.getId();
|
||||
} else {
|
||||
@@ -160,7 +193,7 @@ public abstract class AbstractPersistingStateMachineInterceptor<S, E, T> extends
|
||||
}
|
||||
}
|
||||
}
|
||||
return new DefaultStateMachineContext<S, E>(childs, id, null, null, extendedState, historyStates, stateMachine.getId());
|
||||
return new DefaultStateMachineContext<S, E>(childRefs, childs, id, null, null, extendedState, historyStates, stateMachine.getId());
|
||||
}
|
||||
|
||||
private S getDeepState(State<S, E> state) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2019 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.
|
||||
@@ -152,7 +152,8 @@ public class DefaultStateMachineService<S, E> implements StateMachineService<S,
|
||||
return stateMachine;
|
||||
}
|
||||
stateMachine.stop();
|
||||
stateMachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess<S, E>>() {
|
||||
// only go via top region
|
||||
stateMachine.getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S, E>>() {
|
||||
|
||||
@Override
|
||||
public void apply(StateMachineAccess<S, E> function) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2018 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -67,6 +67,7 @@ import org.springframework.statemachine.transition.TransitionKind;
|
||||
import org.springframework.statemachine.trigger.DefaultTriggerContext;
|
||||
import org.springframework.statemachine.trigger.Trigger;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -697,19 +698,23 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
}
|
||||
stateSet = true;
|
||||
break;
|
||||
} else if (!stateMachineContext.getChilds().isEmpty()) {
|
||||
} else if (stateMachineContext.getChilds() != null && !stateMachineContext.getChilds().isEmpty()) {
|
||||
// we're here because root machine only have regions
|
||||
if (s.isOrthogonal()) {
|
||||
Collection<Region<S, E>> regions = ((AbstractState<S, E>)s).getRegions();
|
||||
|
||||
for (Region<S, E> region : regions) {
|
||||
for (final StateMachineContext<S, E> child : stateMachineContext.getChilds()) {
|
||||
((StateMachine<S, E>)region).getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S,E>>() {
|
||||
// only call if reqion id matches with context id
|
||||
if (ObjectUtils.nullSafeEquals(region.getId(), child.getId())) {
|
||||
((StateMachine<S, E>)region).getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S,E>>() {
|
||||
|
||||
@Override
|
||||
public void apply(StateMachineAccess<S, E> function) {
|
||||
function.resetStateMachine(child);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void apply(StateMachineAccess<S, E> function) {
|
||||
function.resetStateMachine(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -872,7 +877,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
|
||||
private boolean callPreStateChangeInterceptors(State<S,E> state, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
|
||||
try {
|
||||
getStateMachineInterceptors().preStateChange(state, message, transition, stateMachine);
|
||||
getStateMachineInterceptors().preStateChange(state, message, transition, this, stateMachine);
|
||||
} catch (Exception e) {
|
||||
log.info("Interceptors threw exception, skipping state change", e);
|
||||
return false;
|
||||
@@ -882,8 +887,9 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
|
||||
private void callPostStateChangeInterceptors(State<S,E> state, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
|
||||
try {
|
||||
getStateMachineInterceptors().postStateChange(state, message, transition, stateMachine);
|
||||
getStateMachineInterceptors().postStateChange(state, message, transition, this, stateMachine);
|
||||
} catch (Exception e) {
|
||||
log.warn("Interceptors threw exception in post state change", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -35,6 +35,7 @@ public class DefaultStateMachineContext<S, E> implements StateMachineContext<S,
|
||||
|
||||
private final String id;
|
||||
private final List<StateMachineContext<S, E>> childs;
|
||||
private final List<String> childRefs;
|
||||
private final S state;
|
||||
private final Map<S, S> historyStates;
|
||||
private final E event;
|
||||
@@ -125,6 +126,31 @@ public class DefaultStateMachineContext<S, E> implements StateMachineContext<S,
|
||||
public DefaultStateMachineContext(List<StateMachineContext<S, E>> childs, S state, E event,
|
||||
Map<String, Object> eventHeaders, ExtendedState extendedState, Map<S, S> historyStates, String id) {
|
||||
this.childs = childs;
|
||||
this.childRefs = null;
|
||||
this.state = state;
|
||||
this.event = event;
|
||||
this.eventHeaders = eventHeaders;
|
||||
this.extendedState = extendedState;
|
||||
this.historyStates = historyStates != null ? historyStates : new HashMap<S, S>();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new default state machine context.
|
||||
*
|
||||
* @param childRefs the child state machine context refs
|
||||
* @param childs the child state machine contexts
|
||||
* @param state the state
|
||||
* @param event the event
|
||||
* @param eventHeaders the event headers
|
||||
* @param extendedState the extended state
|
||||
* @param historyStates the history state mappings
|
||||
* @param id the machine id
|
||||
*/
|
||||
public DefaultStateMachineContext(List<String> childRefs, List<StateMachineContext<S, E>> childs, S state, E event,
|
||||
Map<String, Object> eventHeaders, ExtendedState extendedState, Map<S, S> historyStates, String id) {
|
||||
this.childs = childs;
|
||||
this.childRefs = childRefs;
|
||||
this.state = state;
|
||||
this.event = event;
|
||||
this.eventHeaders = eventHeaders;
|
||||
@@ -143,6 +169,11 @@ public class DefaultStateMachineContext<S, E> implements StateMachineContext<S,
|
||||
return childs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getChildReferences() {
|
||||
return childRefs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public S getState() {
|
||||
return state;
|
||||
@@ -170,7 +201,8 @@ public class DefaultStateMachineContext<S, E> implements StateMachineContext<S,
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultStateMachineContext [id=" + id + ", childs=" + childs + ", state=" + state + ", historyStates=" + historyStates
|
||||
+ ", event=" + event + ", eventHeaders=" + eventHeaders + ", extendedState=" + extendedState + "]";
|
||||
return "DefaultStateMachineContext [id=" + id + ", childs=" + childs + ", childRefs=" + childRefs + ", state="
|
||||
+ state + ", historyStates=" + historyStates + ", event=" + event + ", eventHeaders=" + eventHeaders
|
||||
+ ", extendedState=" + extendedState + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -54,6 +54,19 @@ public interface StateMachineInterceptor<S, E> {
|
||||
void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine);
|
||||
|
||||
/**
|
||||
* Called prior of a state change. Throwing an exception
|
||||
* from this method will stop a state change logic.
|
||||
*
|
||||
* @param state the state
|
||||
* @param message the message
|
||||
* @param transition the transition
|
||||
* @param stateMachine the state machine
|
||||
* @param rootStateMachine the root state machine
|
||||
*/
|
||||
void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine);
|
||||
|
||||
/**
|
||||
* Called after a state change.
|
||||
*
|
||||
@@ -65,6 +78,18 @@ public interface StateMachineInterceptor<S, E> {
|
||||
void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine);
|
||||
|
||||
/**
|
||||
* Called after a state change.
|
||||
*
|
||||
* @param state the state
|
||||
* @param message the message
|
||||
* @param transition the transition
|
||||
* @param stateMachine the state machine
|
||||
* @param rootStateMachine the root state machine
|
||||
*/
|
||||
void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine);
|
||||
|
||||
/**
|
||||
* Called prior of a start of a transition. Returning
|
||||
* {@code null} from this method will break the transtion
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -41,11 +41,21 @@ public class StateMachineInterceptorAdapter<S, E> implements StateMachineInterce
|
||||
StateMachine<S, E> stateMachine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateContext<S, E> preTransition(StateContext<S, E> stateContext) {
|
||||
return stateContext;
|
||||
@@ -60,5 +70,4 @@ public class StateMachineInterceptorAdapter<S, E> implements StateMachineInterce
|
||||
public Exception stateMachineError(StateMachine<S, E> stateMachine, Exception exception) {
|
||||
return exception;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -102,6 +102,13 @@ public class StateMachineInterceptorList<S, E> {
|
||||
}
|
||||
}
|
||||
|
||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine) {
|
||||
for (StateMachineInterceptor<S, E> interceptor : interceptors) {
|
||||
interceptor.preStateChange(state, message, transition, stateMachine, rootStateMachine);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post state change.
|
||||
*
|
||||
@@ -117,6 +124,13 @@ public class StateMachineInterceptorList<S, E> {
|
||||
}
|
||||
}
|
||||
|
||||
public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, StateMachine<S, E> rootStateMachine) {
|
||||
for (StateMachineInterceptor<S, E> interceptor : interceptors) {
|
||||
interceptor.postStateChange(state, message, transition, stateMachine, rootStateMachine);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre transition.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2018 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -1294,6 +1294,12 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
|
||||
Transition<String, String> transition, StateMachine<String, String> stateMachine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStateChange(State<String, String> state, Message<String> message,
|
||||
Transition<String, String> transition, StateMachine<String, String> stateMachine,
|
||||
StateMachine<String, String> rootStateMachine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateContext<String, String> postTransition(StateContext<String, String> stateContext) {
|
||||
return stateContext;
|
||||
@@ -1304,6 +1310,12 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
|
||||
Transition<String, String> transition, StateMachine<String, String> stateMachine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStateChange(State<String, String> state, Message<String> message,
|
||||
Transition<String, String> transition, StateMachine<String, String> stateMachine,
|
||||
StateMachine<String, String> rootStateMachine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exception stateMachineError(StateMachine<String, String> stateMachine,
|
||||
Exception exception) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
* Copyright 2016-2019 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.
|
||||
@@ -323,6 +323,39 @@ public class StateMachinePersistTests extends AbstractStateMachineTests {
|
||||
assertThat(stateMachine.isComplete(), is(true));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testRegions2() throws Exception {
|
||||
context.register(Config9.class);
|
||||
context.refresh();
|
||||
StateMachine<String, String> stateMachine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
stateMachine.start();
|
||||
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S11", "S21", "S31"));
|
||||
|
||||
stateMachine.sendEvent("E1");
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S12", "S21", "S31"));
|
||||
stateMachine.sendEvent("E2");
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S12", "S22", "S31"));
|
||||
stateMachine.sendEvent("E3");
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S12", "S22", "S32"));
|
||||
|
||||
InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
|
||||
StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);
|
||||
|
||||
persister.persist(stateMachine, "xxx");
|
||||
stateMachine = persister.restore(stateMachine, "xxx");
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S12", "S22", "S32"));
|
||||
|
||||
stateMachine.sendEvent("E4");
|
||||
stateMachine.sendEvent("E5");
|
||||
stateMachine.sendEvent("E6");
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S13", "S23", "S33"));
|
||||
|
||||
stateMachine = persister.restore(stateMachine, "xxx");
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S12", "S22", "S32"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config1 extends StateMachineConfigurerAdapter<String, String> {
|
||||
@@ -685,6 +718,67 @@ public class StateMachinePersistTests extends AbstractStateMachineTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config9 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("S11")
|
||||
.state("S11")
|
||||
.state("S12")
|
||||
.state("S13")
|
||||
.and()
|
||||
.withStates()
|
||||
.initial("S21")
|
||||
.state("S21")
|
||||
.state("S22")
|
||||
.state("S23")
|
||||
.and()
|
||||
.withStates()
|
||||
.initial("S31")
|
||||
.state("S31")
|
||||
.state("S32")
|
||||
.state("S33");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("S11")
|
||||
.target("S12")
|
||||
.event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S21")
|
||||
.target("S22")
|
||||
.event("E2")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S31")
|
||||
.target("S32")
|
||||
.event("E3")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S12")
|
||||
.target("S13")
|
||||
.event("E4")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S22")
|
||||
.target("S23")
|
||||
.event("E5")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S32")
|
||||
.target("S33")
|
||||
.event("E6");
|
||||
}
|
||||
}
|
||||
|
||||
static class InMemoryStateMachinePersist1 implements StateMachinePersist<String, String, String> {
|
||||
|
||||
private final HashMap<String, StateMachineContext<String, String>> contexts = new HashMap<>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2017 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -619,6 +619,13 @@ public class StateChangeInterceptorTests extends AbstractStateMachineTests {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStateChange(State<States, Events> state, Message<Events> message,
|
||||
Transition<States, Events> transition, StateMachine<States, Events> stateMachine,
|
||||
StateMachine<States, Events> rootStateMachine) {
|
||||
preStateChange(state, message, transition, stateMachine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStateChange(State<States, Events> state, Message<Events> message,
|
||||
Transition<States, Events> transition, StateMachine<States, Events> stateMachine) {
|
||||
@@ -627,6 +634,13 @@ public class StateChangeInterceptorTests extends AbstractStateMachineTests {
|
||||
postStateChangeLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStateChange(State<States, Events> state, Message<Events> message,
|
||||
Transition<States, Events> transition, StateMachine<States, Events> stateMachine,
|
||||
StateMachine<States, Events> rootStateMachine) {
|
||||
postStateChange(state, message, transition, stateMachine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateContext<States, Events> preTransition(StateContext<States, Events> stateContext) {
|
||||
return stateContext;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2019 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.
|
||||
@@ -63,7 +63,7 @@ public class JpaRepositoryStateMachinePersist<S, E> extends RepositoryStateMachi
|
||||
protected JpaRepositoryStateMachine build(StateMachineContext<S, E> context, Object contextObj, byte[] serialisedContext) {
|
||||
JpaRepositoryStateMachine jpaRepositoryStateMachine = new JpaRepositoryStateMachine();
|
||||
jpaRepositoryStateMachine.setMachineId(context.getId());
|
||||
jpaRepositoryStateMachine.setState(context.getState().toString());
|
||||
jpaRepositoryStateMachine.setState(context.getState() != null ? context.getState().toString() : null);
|
||||
jpaRepositoryStateMachine.setStateMachineContext(serialisedContext);
|
||||
return jpaRepositoryStateMachine;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2018 the original author or authors.
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,12 +16,15 @@
|
||||
package org.springframework.statemachine.data.jpa;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -314,6 +317,30 @@ public class JpaRepositoryTests extends AbstractRepositoryTests {
|
||||
assertThat(stateMachine.getState().getId(), is(PersistTestStates.S1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testStateMachinePersistWithRootRegions() {
|
||||
context.register(TestConfig.class, ConfigWithRootRegions.class);
|
||||
context.refresh();
|
||||
JpaStateMachineRepository stateMachineRepository = context.getBean(JpaStateMachineRepository.class);
|
||||
|
||||
StateMachine<String, String> stateMachine = context.getBean(StateMachine.class);
|
||||
stateMachine.start();
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S10", "S20"));
|
||||
stateMachine.sendEvent("E1");
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S11", "S21"));
|
||||
|
||||
assertThat(stateMachineRepository.count(), is(3l));
|
||||
|
||||
List<String> ids = StreamSupport.stream(stateMachineRepository.findAll().spliterator(), false)
|
||||
.map(jrsm -> jrsm.getMachineId()).collect(Collectors.toList());
|
||||
assertThat(ids.size(), is(3));
|
||||
|
||||
// [null#238e8cc0-a932-4583-b696-2c057e5ebefe, null#486e20be-853e-4e4d-9a68-c62c061469ef, testid]
|
||||
assertThat(ids, containsInAnyOrder("testid", "testid#R1", "testid#R2"));
|
||||
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
static class TestConfig {
|
||||
}
|
||||
@@ -442,4 +469,57 @@ public class JpaRepositoryTests extends AbstractRepositoryTests {
|
||||
public enum PersistTestEvents {
|
||||
E1, E2;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class ConfigWithRootRegions extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Autowired
|
||||
private JpaStateMachineRepository jpaStateMachineRepository;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.machineId("testid")
|
||||
.and()
|
||||
.withPersistence()
|
||||
.runtimePersister(stateMachineRuntimePersister());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.region("R1")
|
||||
.initial("S10")
|
||||
.state("S10")
|
||||
.state("S11")
|
||||
.and()
|
||||
.withStates()
|
||||
.region("R2")
|
||||
.initial("S20")
|
||||
.state("S20")
|
||||
.state("S21");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("S10")
|
||||
.target("S11")
|
||||
.event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S20")
|
||||
.target("S21")
|
||||
.event("E1");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister() {
|
||||
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2018 the original author or authors.
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,12 +15,16 @@
|
||||
*/
|
||||
package org.springframework.statemachine.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.StateMachinePersist;
|
||||
import org.springframework.statemachine.kryo.KryoStateMachineSerialisationService;
|
||||
import org.springframework.statemachine.service.StateMachineSerialisationService;
|
||||
import org.springframework.statemachine.support.DefaultStateMachineContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -66,8 +70,27 @@ public abstract class RepositoryStateMachinePersist<M extends RepositoryStateMac
|
||||
@Override
|
||||
public StateMachineContext<S, E> read(Object contextObj) throws Exception {
|
||||
M repositoryStateMachine = getRepository().findById(contextObj.toString()).orElse(null);
|
||||
// use child contexts if we have those, otherwise fall back to child context refs.
|
||||
if (repositoryStateMachine != null) {
|
||||
return serialisationService.deserialiseStateMachineContext(repositoryStateMachine.getStateMachineContext());
|
||||
StateMachineContext<S, E> context = serialisationService
|
||||
.deserialiseStateMachineContext(repositoryStateMachine.getStateMachineContext());;
|
||||
if (context != null && context.getChilds() != null && context.getChilds().isEmpty()
|
||||
&& context.getChildReferences() != null) {
|
||||
List<StateMachineContext<S, E>> contexts = new ArrayList<>();
|
||||
for (String childRef : context.getChildReferences()) {
|
||||
repositoryStateMachine = getRepository().findById(childRef).orElse(null);
|
||||
if (repositoryStateMachine != null) {
|
||||
contexts.add(serialisationService
|
||||
.deserialiseStateMachineContext(repositoryStateMachine.getStateMachineContext()));
|
||||
}
|
||||
}
|
||||
return new DefaultStateMachineContext<S, E>(contexts, context.getState(), context.getEvent(),
|
||||
context.getEventHeaders(), context.getExtendedState(), context.getHistoryStates(),
|
||||
context.getId());
|
||||
} else {
|
||||
return context;
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,7 @@ import java.io.OutputStream;
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.service.StateMachineSerialisationService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
@@ -49,6 +50,10 @@ public abstract class AbstractKryoStateMachineSerialisationService<S, E> impleme
|
||||
@Override
|
||||
public Kryo create() {
|
||||
Kryo kryo = new Kryo();
|
||||
// kryo is really getting trouble checking things if class loaders
|
||||
// doesn't match. for now just use below trick before we try
|
||||
// to go fully on beans and get a bean class loader.
|
||||
kryo.setClassLoader(ClassUtils.getDefaultClassLoader());
|
||||
configureKryoInstance(kryo);
|
||||
return kryo;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2017 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -46,6 +46,10 @@ public class StateMachineContextSerializer<S, E> extends Serializer<StateMachine
|
||||
kryo.writeClassAndObject(output, context.getChilds());
|
||||
kryo.writeClassAndObject(output, context.getHistoryStates());
|
||||
kryo.writeClassAndObject(output, context.getId());
|
||||
// child refs is added after initial implementation, leaving this not here
|
||||
// in case it's starting to cause issues with any existing serialized contexts
|
||||
// which doesn't have this field
|
||||
kryo.writeClassAndObject(output, context.getChildReferences());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -58,7 +62,8 @@ public class StateMachineContextSerializer<S, E> extends Serializer<StateMachine
|
||||
List<StateMachineContext<S, E>> childs = (List<StateMachineContext<S, E>>) kryo.readClassAndObject(input);
|
||||
Map<S, S> historyStates = (Map<S, S>) kryo.readClassAndObject(input);
|
||||
String id = (String) kryo.readClassAndObject(input);
|
||||
return new DefaultStateMachineContext<S, E>(childs, state, event, eventHeaders, new DefaultExtendedState(variables), historyStates, id);
|
||||
List<String> childRefs = (List<String>) kryo.readClassAndObject(input);
|
||||
return new DefaultStateMachineContext<S, E>(childRefs, childs, state, event, eventHeaders,
|
||||
new DefaultExtendedState(variables), historyStates, id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2018 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.kryo;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.support.DefaultExtendedState;
|
||||
import org.springframework.statemachine.support.DefaultStateMachineContext;
|
||||
|
||||
/**
|
||||
* Tests for {@link KryoStateMachineSerialisationService}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public class KryoStateMachineSerialisationServiceTests {
|
||||
|
||||
@Test
|
||||
public void testContextWithChilds() throws Exception {
|
||||
StateMachineContext<String, String> child1 = new DefaultStateMachineContext<String, String>("child1", null, null,
|
||||
new DefaultExtendedState());
|
||||
StateMachineContext<String, String> child2 = new DefaultStateMachineContext<String, String>("child2", null, null,
|
||||
new DefaultExtendedState());
|
||||
List<StateMachineContext<String, String>> childs = new ArrayList<>();
|
||||
childs.add(child1);
|
||||
childs.add(child2);
|
||||
StateMachineContext<String, String> root = new DefaultStateMachineContext<String, String>(childs, "root", null,
|
||||
null, new DefaultExtendedState());
|
||||
|
||||
KryoStateMachineSerialisationService<String, String> service = new KryoStateMachineSerialisationService<>();
|
||||
byte[] bytes = service.serialiseStateMachineContext(root);
|
||||
|
||||
StateMachineContext<String, String> context = service.deserialiseStateMachineContext(bytes);
|
||||
assertThat(context.getChilds().size(), is(2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2018 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.kryo;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.support.DefaultExtendedState;
|
||||
import org.springframework.statemachine.support.DefaultStateMachineContext;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
/**
|
||||
* Tests for {@link StateMachineContextSerializer}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public class StateMachineContextSerializerTests {
|
||||
|
||||
private Kryo kryo;
|
||||
private Output output;
|
||||
private Input input;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
kryo = new Kryo();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextWithChilds() {
|
||||
StateMachineContextSerializer<String, String> serializer = new StateMachineContextSerializer<>();
|
||||
kryo.addDefaultSerializer(StateMachineContext.class, serializer);
|
||||
|
||||
StateMachineContext<String, String> child = new DefaultStateMachineContext<String, String>(new ArrayList<>(), "child", "event1",
|
||||
new HashMap<String, Object>(), new DefaultExtendedState());
|
||||
List<StateMachineContext<String, String>> childs = new ArrayList<>();
|
||||
childs.add(child);
|
||||
StateMachineContext<String, String> root = new DefaultStateMachineContext<String, String>(childs, "root", "event2",
|
||||
new HashMap<String, Object>(), new DefaultExtendedState());
|
||||
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
output = new Output(outStream);
|
||||
kryo.writeClassAndObject(output, root);
|
||||
output.flush();
|
||||
|
||||
input = new Input(new ByteArrayInputStream(outStream.toByteArray()));
|
||||
kryo.readClassAndObject(input);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -120,6 +120,13 @@ public class PersistStateMachineHandler extends LifecycleObjectSupport {
|
||||
Transition<String, String> transition, StateMachine<String, String> stateMachine) {
|
||||
listeners.onPersist(state, message, transition, stateMachine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStateChange(State<String, String> state, Message<String> message,
|
||||
Transition<String, String> transition, StateMachine<String, String> stateMachine,
|
||||
StateMachine<String, String> rootStateMachine) {
|
||||
listeners.onPersist(state, message, transition, stateMachine);
|
||||
}
|
||||
}
|
||||
|
||||
private class CompositePersistStateChangeListener extends AbstractCompositeListener<PersistStateChangeListener> implements
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-2019 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.
|
||||
@@ -878,6 +878,13 @@ public class TasksHandler {
|
||||
throw new StateMachineException("Error persisting", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStateChange(State<String, String> state, Message<String> message,
|
||||
Transition<String, String> transition, StateMachine<String, String> stateMachine,
|
||||
StateMachine<String, String> rootStateMachine) {
|
||||
preStateChange(state, message, transition, stateMachine);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -128,6 +128,19 @@ project('spring-statemachine-samples-datajpa') {
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-datajpamultipersist') {
|
||||
description = 'Spring State Machine Data Jpa Multi Persist Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-autoconfigure")
|
||||
compile project(":spring-statemachine-data-common:spring-statemachine-data-jpa")
|
||||
compile("org.springframework.boot:spring-boot-starter-web")
|
||||
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
|
||||
compile("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||
compile("org.springframework.boot:spring-boot-devtools")
|
||||
compile("com.h2database:h2")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-datapersist') {
|
||||
description = 'Spring State Machine Data Persist Sample'
|
||||
dependencies {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2018 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.datajpamultipersist;
|
||||
|
||||
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,98 @@
|
||||
/*
|
||||
* Copyright 2018 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.datajpamultipersist;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.statemachine.config.EnableStateMachineFactory;
|
||||
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.StateMachineFactory;
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineModelConfigurer;
|
||||
import org.springframework.statemachine.config.model.StateMachineModelFactory;
|
||||
import org.springframework.statemachine.data.RepositoryState;
|
||||
import org.springframework.statemachine.data.RepositoryStateMachineModelFactory;
|
||||
import org.springframework.statemachine.data.RepositoryTransition;
|
||||
import org.springframework.statemachine.data.StateRepository;
|
||||
import org.springframework.statemachine.data.TransitionRepository;
|
||||
import org.springframework.statemachine.data.jpa.JpaPersistingStateMachineInterceptor;
|
||||
import org.springframework.statemachine.data.jpa.JpaStateMachineRepository;
|
||||
import org.springframework.statemachine.data.support.StateMachineJackson2RepositoryPopulatorFactoryBean;
|
||||
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
|
||||
import org.springframework.statemachine.service.DefaultStateMachineService;
|
||||
import org.springframework.statemachine.service.StateMachineService;
|
||||
|
||||
@Configuration
|
||||
public class StateMachineConfig {
|
||||
|
||||
@Bean
|
||||
public StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister(
|
||||
JpaStateMachineRepository jpaStateMachineRepository) {
|
||||
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StateMachineService<String, String> stateMachineService(
|
||||
StateMachineFactory<String, String> stateMachineFactory,
|
||||
StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister) {
|
||||
return new DefaultStateMachineService<String, String>(stateMachineFactory, stateMachineRuntimePersister);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() {
|
||||
StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean();
|
||||
factoryBean.setResources(new Resource[] { new ClassPathResource("data.json") });
|
||||
return factoryBean;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachineFactory
|
||||
public static class Config extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Autowired
|
||||
private StateRepository<? extends RepositoryState> stateRepository;
|
||||
|
||||
@Autowired
|
||||
private TransitionRepository<? extends RepositoryTransition> transitionRepository;
|
||||
|
||||
@Autowired
|
||||
private StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||
throws Exception {
|
||||
config
|
||||
.withPersistence()
|
||||
.runtimePersister(stateMachineRuntimePersister);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineModelConfigurer<String, String> model)
|
||||
throws Exception {
|
||||
model
|
||||
.withModel()
|
||||
.factory(modelFactory());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StateMachineModelFactory<String, String> modelFactory() {
|
||||
return new RepositoryStateMachineModelFactory(stateRepository, transitionRepository);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2019 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.datajpamultipersist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.StateMachinePersist;
|
||||
import org.springframework.statemachine.data.RepositoryTransition;
|
||||
import org.springframework.statemachine.data.TransitionRepository;
|
||||
import org.springframework.statemachine.service.StateMachineService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@Controller
|
||||
public class StateMachineController {
|
||||
|
||||
public final static String MACHINE_ID_1 = "datajpamultipersist1";
|
||||
public final static String MACHINE_ID_2 = "datajpamultipersist2";
|
||||
public final static String MACHINE_ID_2R1 = "datajpamultipersist2#R1";
|
||||
public final static String MACHINE_ID_2R2 = "datajpamultipersist2#R2";
|
||||
private final static String[] MACHINES = new String[] { MACHINE_ID_1, MACHINE_ID_2 };
|
||||
private final StateMachineLogListener listener = new StateMachineLogListener();
|
||||
|
||||
@Autowired
|
||||
private StateMachineService<String, String> stateMachineService;
|
||||
|
||||
@Autowired
|
||||
private StateMachinePersist<String, String, String> stateMachinePersist;
|
||||
|
||||
@Autowired
|
||||
private TransitionRepository<? extends RepositoryTransition> transitionRepository;
|
||||
|
||||
private StateMachine<String, String> currentStateMachine;
|
||||
|
||||
@RequestMapping("/")
|
||||
public String home() {
|
||||
return "redirect:/state";
|
||||
}
|
||||
|
||||
@RequestMapping("/state")
|
||||
public String feedAndGetStates(
|
||||
@RequestParam(value = "events", required = false) List<String> events,
|
||||
@RequestParam(value = "machine", required = false, defaultValue = MACHINE_ID_1) String machine,
|
||||
Model model) throws Exception {
|
||||
|
||||
|
||||
StateMachine<String, String> stateMachine = getStateMachine(machine);
|
||||
if (events != null) {
|
||||
for (String event : events) {
|
||||
stateMachine.sendEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder contextBuf = new StringBuilder();
|
||||
|
||||
StateMachineContext<String, String> stateMachineContext = stateMachinePersist.read(machine);
|
||||
if (stateMachineContext != null) {
|
||||
contextBuf.append(stateMachineContext.toString());
|
||||
}
|
||||
if (ObjectUtils.nullSafeEquals(machine, MACHINE_ID_2)) {
|
||||
stateMachineContext = stateMachinePersist.read(MACHINE_ID_2R1);
|
||||
if (stateMachineContext != null) {
|
||||
contextBuf.append("\n---\n");
|
||||
contextBuf.append(stateMachineContext.toString());
|
||||
}
|
||||
stateMachineContext = stateMachinePersist.read(MACHINE_ID_2R2);
|
||||
if (stateMachineContext != null) {
|
||||
contextBuf.append("\n---\n");
|
||||
contextBuf.append(stateMachineContext.toString());
|
||||
}
|
||||
}
|
||||
|
||||
model.addAttribute("allMachines", MACHINES);
|
||||
model.addAttribute("machine", machine);
|
||||
model.addAttribute("currentMachine", currentStateMachine);
|
||||
model.addAttribute("allEvents", getEvents());
|
||||
model.addAttribute("messages", createMessages(listener.getMessages()));
|
||||
model.addAttribute("context", contextBuf.toString());
|
||||
return "states";
|
||||
}
|
||||
|
||||
private synchronized StateMachine<String, String> getStateMachine(String machineId) throws Exception {
|
||||
listener.resetMessages();
|
||||
if (currentStateMachine == null) {
|
||||
currentStateMachine = stateMachineService.acquireStateMachine(machineId, false);
|
||||
currentStateMachine.addStateListener(listener);
|
||||
currentStateMachine.start();
|
||||
} else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) {
|
||||
stateMachineService.releaseStateMachine(currentStateMachine.getId());
|
||||
currentStateMachine.stop();
|
||||
currentStateMachine = stateMachineService.acquireStateMachine(machineId, false);
|
||||
currentStateMachine.addStateListener(listener);
|
||||
currentStateMachine.start();
|
||||
}
|
||||
return currentStateMachine;
|
||||
}
|
||||
|
||||
private String[] getEvents() {
|
||||
List<String> events = new ArrayList<>();
|
||||
for (RepositoryTransition t : transitionRepository.findAll()) {
|
||||
events.add(t.getEvent());
|
||||
}
|
||||
return events.toArray(new String[0]);
|
||||
}
|
||||
|
||||
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 2018 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.datajpamultipersist;
|
||||
|
||||
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,7 @@
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
org.springframework.statemachine: DEBUG
|
||||
security:
|
||||
basic:
|
||||
enabled: false
|
||||
@@ -0,0 +1,172 @@
|
||||
[
|
||||
{
|
||||
"@id": "100",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
|
||||
"spel": "T(System).out.println('hello exit S1')"
|
||||
},
|
||||
{
|
||||
"@id": "101",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
|
||||
"spel": "T(System).out.println('hello entry S2')"
|
||||
},
|
||||
{
|
||||
"@id": "102",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
|
||||
"spel": "T(System).out.println('hello state S3')"
|
||||
},
|
||||
{
|
||||
"@id": "103",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
|
||||
"spel": "T(System).out.println('hello')"
|
||||
},
|
||||
{
|
||||
"@id": "10",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
|
||||
"machineId": "datajpamultipersist1",
|
||||
"initial": true,
|
||||
"state": "S1",
|
||||
"exitActions": ["100"]
|
||||
},
|
||||
{
|
||||
"@id": "11",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
|
||||
"machineId": "datajpamultipersist1",
|
||||
"initial": false,
|
||||
"state": "S2",
|
||||
"entryActions": ["101"]
|
||||
},
|
||||
{
|
||||
"@id": "12",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
|
||||
"machineId": "datajpamultipersist1",
|
||||
"initial": false,
|
||||
"state": "S3",
|
||||
"stateActions": ["102"]
|
||||
},
|
||||
{
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
|
||||
"machineId": "datajpamultipersist1",
|
||||
"source": "10",
|
||||
"target": "11",
|
||||
"event": "E1",
|
||||
"kind": "EXTERNAL"
|
||||
},
|
||||
{
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
|
||||
"machineId": "datajpamultipersist1",
|
||||
"source": "11",
|
||||
"target": "12",
|
||||
"event": "E2",
|
||||
"actions": ["103"]
|
||||
},
|
||||
{
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
|
||||
"machineId": "datajpamultipersist1",
|
||||
"source": "12",
|
||||
"target": "11",
|
||||
"event": "E3",
|
||||
"actions": ["103"]
|
||||
},
|
||||
{
|
||||
"@id": "20",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"region": "R1",
|
||||
"initial": true,
|
||||
"state": "S10",
|
||||
"exitActions": ["100"]
|
||||
},
|
||||
{
|
||||
"@id": "21",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"region": "R1",
|
||||
"initial": false,
|
||||
"state": "S11",
|
||||
"entryActions": ["101"]
|
||||
},
|
||||
{
|
||||
"@id": "22",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"region": "R1",
|
||||
"initial": false,
|
||||
"state": "S12",
|
||||
"stateActions": ["102"]
|
||||
},
|
||||
{
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"source": "20",
|
||||
"target": "21",
|
||||
"event": "E10",
|
||||
"kind": "EXTERNAL"
|
||||
},
|
||||
{
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"source": "21",
|
||||
"target": "22",
|
||||
"event": "E11",
|
||||
"actions": ["103"]
|
||||
},
|
||||
{
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"source": "22",
|
||||
"target": "21",
|
||||
"event": "E12",
|
||||
"actions": ["103"]
|
||||
},
|
||||
{
|
||||
"@id": "30",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"region": "R2",
|
||||
"initial": true,
|
||||
"state": "S20",
|
||||
"exitActions": ["100"]
|
||||
},
|
||||
{
|
||||
"@id": "31",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"region": "R2",
|
||||
"initial": false,
|
||||
"state": "S21",
|
||||
"entryActions": ["101"]
|
||||
},
|
||||
{
|
||||
"@id": "32",
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"region": "R2",
|
||||
"initial": false,
|
||||
"state": "S22",
|
||||
"stateActions": ["102"]
|
||||
},
|
||||
{
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"source": "30",
|
||||
"target": "31",
|
||||
"event": "E20",
|
||||
"kind": "EXTERNAL"
|
||||
},
|
||||
{
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"source": "31",
|
||||
"target": "32",
|
||||
"event": "E21",
|
||||
"actions": ["103"]
|
||||
},
|
||||
{
|
||||
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
|
||||
"machineId": "datajpamultipersist2",
|
||||
"source": "32",
|
||||
"target": "31",
|
||||
"event": "E22",
|
||||
"actions": ["103"]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,52 @@
|
||||
<!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>
|
||||
<div>
|
||||
<a href="/h2-console" target="_blank">h2 console</a>
|
||||
</div>
|
||||
|
||||
<form action="#" data-th-action="@{/state}" data-th-object="${model}" method="post">
|
||||
<div>
|
||||
<p th:text="'Choose machine'"/>
|
||||
<ul>
|
||||
<li th:each="ty : ${allMachines}">
|
||||
<input type="radio" name="machine" th:value="${ty}" th:checked="${ty} == ${machine}"/>
|
||||
<label th:text="${ty}">replaced</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<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>
|
||||
<h3>Events</h3>
|
||||
</div>
|
||||
<div>
|
||||
<textarea th:text="${messages}" rows="15" cols="100"/>
|
||||
</div>
|
||||
<div>
|
||||
<h3>StateMachineContext</h3>
|
||||
</div>
|
||||
<div>
|
||||
<textarea th:text="${context}" rows="15" cols="100"/>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Current Machine</h3>
|
||||
</div>
|
||||
<div>
|
||||
<textarea th:text="${currentMachine}" rows="10" cols="100"/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2019 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.datajpamultipersist;
|
||||
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import demo.datajpamultipersist.Application;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = { Application.class })
|
||||
@WebAppConfiguration
|
||||
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class DataJpaMultiPersistTests {
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
@Test
|
||||
public void testHome() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state")).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(allOf(
|
||||
containsString("Enter S1"),
|
||||
containsString("Machine started"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendEventE1WithMachine1() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state")
|
||||
.param("events", "E1")
|
||||
.param("machine", StateMachineController.MACHINE_ID_1)).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(allOf(
|
||||
containsString("Enter S1"),
|
||||
containsString("Exit S1"),
|
||||
containsString("Enter S2"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendEventsE1E2WithMachine1() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state")
|
||||
.param("events", "E1")
|
||||
.param("events", "E2")
|
||||
.param("machine", StateMachineController.MACHINE_ID_1)).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(allOf(
|
||||
containsString("Enter S1"),
|
||||
containsString("Exit S1"),
|
||||
containsString("Enter S2"),
|
||||
containsString("Exit S2"),
|
||||
containsString("Enter S3"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithMachine2() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state")
|
||||
.param("machine", StateMachineController.MACHINE_ID_2)).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(allOf(
|
||||
containsString("Enter S10"),
|
||||
containsString("Enter S20"),
|
||||
containsString("Enter null"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendEventsE10E20WithMachine2() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state")
|
||||
.param("events", "E10")
|
||||
.param("events", "E20")
|
||||
.param("machine", StateMachineController.MACHINE_ID_2)).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(allOf(
|
||||
containsString("Enter S10"),
|
||||
containsString("Enter S20"),
|
||||
containsString("Enter S11"),
|
||||
containsString("Enter S21"),
|
||||
containsString("Enter null"))));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
mvc = MockMvcBuilders.webAppContextSetup(context).build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user