Preliminary support for Spring Data persist
- Add new repository model for storing StateMachineContext via a new StateMachineRepository. - New StateMachineRuntimePersister interface to abstract needed functionality to do a runtime machine persistence. - As runtime persistence, as of now, is done via interceptors, define JpaRepositoryStateMachinePersist and JpaPersistingStateMachineInterceptor to define StateMachineRuntimePersister logic. - Add new datajpapersist sample demonstrating new concepts. - Keep tests related to jpa as there's not redis/mongo integration implemented in this first iteration. - As this is going to be WIP until features around this issues are completed, docs, etc are not yet added. Also, interfaces and impls are subject to change during a process. - Relates to #423 - Relates to #426 - Relates to #427
This commit is contained in:
@@ -234,6 +234,7 @@ project('spring-statemachine-data-common') {
|
||||
}
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-core")
|
||||
compile project(":spring-statemachine-kryo")
|
||||
compile "org.springframework.data:spring-data-commons:$springDataCommonsVersion"
|
||||
optional "org.springframework.security:spring-security-core:$springSecurityVersion"
|
||||
compile "com.fasterxml.jackson.core:jackson-core:$jackson2Version"
|
||||
|
||||
@@ -26,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:datajpapersist'
|
||||
include 'spring-statemachine-samples:monitoring'
|
||||
|
||||
include 'spring-statemachine-data'
|
||||
|
||||
@@ -79,6 +79,7 @@ import org.springframework.statemachine.state.StateHolder;
|
||||
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.tree.Tree;
|
||||
import org.springframework.statemachine.support.tree.Tree.Node;
|
||||
import org.springframework.statemachine.support.tree.TreeTraverser;
|
||||
@@ -319,6 +320,18 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
machine.addStateListener(listener);
|
||||
}
|
||||
|
||||
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>>() {
|
||||
@Override
|
||||
public void apply(StateMachineAccess<S, E> function) {
|
||||
function.addStateMachineInterceptor(interceptor);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// go through holders and fix state references which
|
||||
// were not known at a time holder was created
|
||||
for (HolderListItem<S, E> holderItem : holderList) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2017 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.
|
||||
@@ -29,10 +29,12 @@ import org.springframework.statemachine.config.configurers.ConfigurationConfigur
|
||||
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.DefaultPersistenceConfigurer;
|
||||
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.PersistenceConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.SecurityConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.VerifierConfigurer;
|
||||
import org.springframework.statemachine.config.model.ConfigurationData;
|
||||
@@ -41,7 +43,9 @@ import org.springframework.statemachine.config.model.verifier.StateMachineModelV
|
||||
import org.springframework.statemachine.ensemble.StateMachineEnsemble;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
|
||||
import org.springframework.statemachine.security.SecurityRule;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||
|
||||
/**
|
||||
* {@link AnnotationBuilder} for {@link StatesData}.
|
||||
@@ -70,6 +74,8 @@ public class StateMachineConfigurationBuilder<S, E>
|
||||
private SecurityRule eventSecurityRule;
|
||||
private SecurityRule transitionSecurityRule;
|
||||
private StateMachineMonitor<S, E> stateMachineMonitor;
|
||||
private final List<StateMachineInterceptor<S, E>> interceptors = new ArrayList<StateMachineInterceptor<S, E>>();
|
||||
private StateMachineRuntimePersister<S, E> persister;
|
||||
|
||||
/**
|
||||
* Instantiates a new state machine configuration builder.
|
||||
@@ -123,11 +129,23 @@ public class StateMachineConfigurationBuilder<S, E>
|
||||
return apply(new DefaultMonitoringConfigurer<S, E>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceConfigurer<S, E> withPersistence() throws Exception {
|
||||
return apply(new DefaultPersistenceConfigurer<S, E>());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigurationData<S, E> performBuild() throws Exception {
|
||||
ArrayList<StateMachineInterceptor<S, E>> interceptorsCopy = new ArrayList<StateMachineInterceptor<S, E>>(interceptors);
|
||||
if (persister != null) {
|
||||
StateMachineInterceptor<S, E> interceptor = persister.getInterceptor();
|
||||
if (interceptor != null) {
|
||||
interceptorsCopy.add((StateMachineInterceptor<S, E>) interceptor);
|
||||
}
|
||||
}
|
||||
return new ConfigurationData<S, E>(beanFactory, taskExecutor, taskScheculer, autoStart, ensemble, listeners,
|
||||
securityEnabled, transitionSecurityAccessDecisionManager, eventSecurityAccessDecisionManager, eventSecurityRule,
|
||||
transitionSecurityRule, verifierEnabled, verifier, machineId, stateMachineMonitor);
|
||||
transitionSecurityRule, verifierEnabled, verifier, machineId, stateMachineMonitor, interceptorsCopy);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,4 +283,13 @@ public class StateMachineConfigurationBuilder<S, E>
|
||||
public void setVerifier(StateMachineModelVerifier<S, E> verifier) {
|
||||
this.verifier = verifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state machine runtime persister.
|
||||
*
|
||||
* @param persister the state machine runtime persister
|
||||
*/
|
||||
public void setStateMachineRuntimePersister(StateMachineRuntimePersister<S, E> persister) {
|
||||
this.persister = persister;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,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.PersistenceConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.SecurityConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.VerifierConfigurer;
|
||||
|
||||
@@ -70,4 +71,12 @@ public interface StateMachineConfigurationConfigurer<S, E> {
|
||||
* @throws Exception if configuration error happens
|
||||
*/
|
||||
MonitoringConfigurer<S, E> withMonitoring() throws Exception;
|
||||
|
||||
/**
|
||||
* Gets a configurer for state machine persistence.
|
||||
*
|
||||
* @return {@link PersistenceConfigurer} for chaining
|
||||
* @throws Exception if configuration error happens
|
||||
*/
|
||||
PersistenceConfigurer<S, E> withPersistence() throws Exception;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2017 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.persist.StateMachineRuntimePersister;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link PersistenceConfigurer}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class DefaultPersistenceConfigurer<S, E> extends
|
||||
AnnotationConfigurerAdapter<ConfigurationData<S, E>, StateMachineConfigurationConfigurer<S, E>, StateMachineConfigurationBuilder<S, E>>
|
||||
implements PersistenceConfigurer<S, E> {
|
||||
|
||||
private StateMachineRuntimePersister<S, E> persister;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationBuilder<S, E> builder) throws Exception {
|
||||
builder.setStateMachineRuntimePersister(persister);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceConfigurer<S, E> runtimePersister(StateMachineRuntimePersister<S, E> persister) {
|
||||
this.persister = persister;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2017 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.persist.StateMachineRuntimePersister;
|
||||
|
||||
/**
|
||||
* Base {@code PersistenceConfigurer} interface for configuring state machine persistence.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface PersistenceConfigurer<S, E> extends
|
||||
AnnotationConfigurerBuilder<StateMachineConfigurationConfigurer<S, E>> {
|
||||
|
||||
/**
|
||||
* Specify a state machine runtime persister.
|
||||
*
|
||||
* @param persister the state machine runtime persister
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
PersistenceConfigurer<S, E> runtimePersister(StateMachineRuntimePersister<S, E> persister);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2017 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.
|
||||
@@ -31,6 +31,7 @@ import org.springframework.statemachine.ensemble.StateMachineEnsemble;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.security.SecurityRule;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||
|
||||
/**
|
||||
* Configuration object used to keep things together in {@link StateMachineConfigurationBuilder}.
|
||||
@@ -57,13 +58,14 @@ public class ConfigurationData<S, E> {
|
||||
private final SecurityRule eventSecurityRule;
|
||||
private final SecurityRule transitionSecurityRule;
|
||||
private final StateMachineMonitor<S, E> stateMachineMonitor;
|
||||
private final List<StateMachineInterceptor<S, E>> interceptors;
|
||||
|
||||
/**
|
||||
* 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, null, true, new DefaultStateMachineModelVerifier<S, E>(), null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,13 +86,15 @@ public class ConfigurationData<S, E> {
|
||||
* @param verifier the state machine model verifier
|
||||
* @param machineId the machine id
|
||||
* @param stateMachineMonitor the state machine monitor
|
||||
* @param interceptors the state machine interceptors.
|
||||
*/
|
||||
public ConfigurationData(BeanFactory beanFactory, TaskExecutor taskExecutor,
|
||||
TaskScheduler taskScheduler, boolean autoStart, StateMachineEnsemble<S, E> ensemble,
|
||||
List<StateMachineListener<S, E>> listeners, boolean securityEnabled,
|
||||
AccessDecisionManager transitionSecurityAccessDecisionManager, AccessDecisionManager eventSecurityAccessDecisionManager,
|
||||
SecurityRule eventSecurityRule, SecurityRule transitionSecurityRule, boolean verifierEnabled,
|
||||
StateMachineModelVerifier<S, E> verifier, String machineId, StateMachineMonitor<S, E> stateMachineMonitor) {
|
||||
StateMachineModelVerifier<S, E> verifier, String machineId, StateMachineMonitor<S, E> stateMachineMonitor,
|
||||
List<StateMachineInterceptor<S, E>> interceptors) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.taskExecutor = taskExecutor;
|
||||
this.taskScheduler = taskScheduler;
|
||||
@@ -106,6 +110,7 @@ public class ConfigurationData<S, E> {
|
||||
this.verifier = verifier;
|
||||
this.machineId = machineId;
|
||||
this.stateMachineMonitor = stateMachineMonitor;
|
||||
this.interceptors = interceptors;
|
||||
}
|
||||
|
||||
public String getMachineId() {
|
||||
@@ -237,4 +242,13 @@ public class ConfigurationData<S, E> {
|
||||
public SecurityRule getTransitionSecurityRule() {
|
||||
return transitionSecurityRule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the state machine interceptors.
|
||||
*
|
||||
* @return the state machine interceptors
|
||||
*/
|
||||
public List<StateMachineInterceptor<S, E>> getStateMachineInterceptors() {
|
||||
return interceptors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2017 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.persist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.statemachine.ExtendedState;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.StateMachineException;
|
||||
import org.springframework.statemachine.StateMachinePersist;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
import org.springframework.statemachine.state.AbstractState;
|
||||
import org.springframework.statemachine.state.HistoryPseudoState;
|
||||
import org.springframework.statemachine.state.PseudoState;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.support.AbstractStateMachine;
|
||||
import org.springframework.statemachine.support.DefaultExtendedState;
|
||||
import org.springframework.statemachine.support.DefaultStateMachineContext;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptorAdapter;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.statemachine.transition.TransitionKind;
|
||||
|
||||
/**
|
||||
* Base class for {@link StateMachineInterceptor} persisting {@link StateMachineContext}s.
|
||||
* This class is to be used as a base implementation which wants to persist a machine which
|
||||
* is about to kept running as normal use case for persistence is to stop machine, persist and
|
||||
* then start it again.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public abstract class AbstractPersistingStateMachineInterceptor<S, E> extends StateMachineInterceptorAdapter<S, E>
|
||||
implements StateMachinePersist<S, E, Object> {
|
||||
|
||||
@Override
|
||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
|
||||
// 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), null);
|
||||
} 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) {
|
||||
// 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), null);
|
||||
} catch (Exception e) {
|
||||
throw new StateMachineException("Unable to persist stateMachineContext", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write {@link StateMachineContext} into persistent store.
|
||||
*
|
||||
* @param context the state machine context
|
||||
* @param contextObj the context object
|
||||
*/
|
||||
public abstract void write(StateMachineContext<S, E> context, Object contextObj) throws Exception;
|
||||
|
||||
/**
|
||||
* Read {@link StateMachineContext} from persistent store.
|
||||
*
|
||||
* @param contextObj the context object
|
||||
* @return the state machine context
|
||||
*/
|
||||
public abstract StateMachineContext<S, E> read(Object contextObj) throws Exception;
|
||||
|
||||
/**
|
||||
* Builds the state machine context.
|
||||
*
|
||||
* @param stateMachine the state machine
|
||||
* @param state the state
|
||||
* @return the state machine context
|
||||
*/
|
||||
protected StateMachineContext<S, E> buildStateMachineContext(StateMachine<S, E> stateMachine, State<S, E> state) {
|
||||
ExtendedState extendedState = new DefaultExtendedState();
|
||||
extendedState.getVariables().putAll(stateMachine.getExtendedState().getVariables());
|
||||
|
||||
ArrayList<StateMachineContext<S, E>> childs = new ArrayList<StateMachineContext<S, E>>();
|
||||
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));
|
||||
}
|
||||
id = state.getId();
|
||||
} else {
|
||||
id = state.getId();
|
||||
}
|
||||
|
||||
// building history state mappings
|
||||
Map<S, S> historyStates = new HashMap<S, S>();
|
||||
PseudoState<S, E> historyState = ((AbstractStateMachine<S, E>) stateMachine).getHistoryState();
|
||||
if (historyState != null) {
|
||||
historyStates.put(null, ((HistoryPseudoState<S, E>)historyState).getState().getId());
|
||||
}
|
||||
Collection<State<S, E>> states = stateMachine.getStates();
|
||||
for (State<S, E> ss : states) {
|
||||
if (ss.isSubmachineState()) {
|
||||
StateMachine<S, E> submachine = ((AbstractState<S, E>) ss).getSubmachine();
|
||||
PseudoState<S, E> ps = ((AbstractStateMachine<S, E>) submachine).getHistoryState();
|
||||
if (ps != null) {
|
||||
State<S, E> pss = ((HistoryPseudoState<S, E>)ps).getState();
|
||||
if (pss != null) {
|
||||
historyStates.put(ss.getId(), pss.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new DefaultStateMachineContext<S, E>(childs, id, null, null, extendedState, historyStates, stateMachine.getId());
|
||||
}
|
||||
|
||||
private S getDeepState(State<S, E> state) {
|
||||
Collection<S> ids1 = state.getIds();
|
||||
@SuppressWarnings("unchecked")
|
||||
S[] ids2 = (S[]) ids1.toArray();
|
||||
// TODO: can this be empty as then we'd get error?
|
||||
return ids2[ids2.length-1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2017 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.persist;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||
|
||||
/**
|
||||
* Interface defining a runtime persistence of a {@link StateMachine}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface StateMachineRuntimePersister<S, E> {
|
||||
|
||||
/**
|
||||
* Gets a {@link StateMachineInterceptor} handling machine persistence.
|
||||
*
|
||||
* @return the interceptor handling persistence
|
||||
*/
|
||||
StateMachineInterceptor<S, E> getInterceptor();
|
||||
}
|
||||
@@ -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, null);
|
||||
eventSecurityRule, transitionSecurityRule, verifierEnabled, verifier, null, 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,72 @@
|
||||
/*
|
||||
* Copyright 2017 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.data.jpa;
|
||||
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.StateMachinePersist;
|
||||
import org.springframework.statemachine.persist.AbstractPersistingStateMachineInterceptor;
|
||||
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@code JPA} implementation of a {@link AbstractPersistingStateMachineInterceptor}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class JpaPersistingStateMachineInterceptor<S, E> extends AbstractPersistingStateMachineInterceptor<S, E>
|
||||
implements StateMachinePersist<S, E, Object>, StateMachineRuntimePersister<S, E> {
|
||||
|
||||
private final JpaRepositoryStateMachinePersist<S, E> persist;
|
||||
|
||||
/**
|
||||
* Instantiates a new jpa persisting state machine interceptor.
|
||||
*
|
||||
* @param jpaStateMachineRepository the jpa state machine repository
|
||||
*/
|
||||
public JpaPersistingStateMachineInterceptor(JpaStateMachineRepository jpaStateMachineRepository) {
|
||||
Assert.notNull(jpaStateMachineRepository, "'jpaStateMachineRepository' must be set");
|
||||
this.persist = new JpaRepositoryStateMachinePersist<S, E>(jpaStateMachineRepository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new jpa persisting state machine interceptor.
|
||||
*
|
||||
* @param persist the persist
|
||||
*/
|
||||
public JpaPersistingStateMachineInterceptor(JpaRepositoryStateMachinePersist<S, E> persist) {
|
||||
Assert.notNull(persist, "'persist' must be set");
|
||||
this.persist = persist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateMachineInterceptor<S, E> getInterceptor() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(StateMachineContext<S, E> context, Object contextObj) throws Exception {
|
||||
persist.write(context, contextObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateMachineContext<S, E> read(Object contextObj) throws Exception {
|
||||
return persist.read(contextObj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2017 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.data.jpa;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Lob;
|
||||
|
||||
import org.springframework.statemachine.data.RepositoryStateMachine;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||
|
||||
/**
|
||||
* A {@link RepositoryStateMachine} interface for JPA used for states machines.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class)
|
||||
public class JpaRepositoryStateMachine extends RepositoryStateMachine {
|
||||
|
||||
@Id
|
||||
private String machineId;
|
||||
|
||||
private String state;
|
||||
|
||||
@Lob
|
||||
private byte[] stateMachineContext;
|
||||
|
||||
@Override
|
||||
public String getMachineId() {
|
||||
return machineId;
|
||||
}
|
||||
|
||||
public void setMachineId(String machineId) {
|
||||
this.machineId = machineId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getStateMachineContext() {
|
||||
return stateMachineContext;
|
||||
}
|
||||
|
||||
public void setStateMachineContext(byte[] stateMachineContext) {
|
||||
this.stateMachineContext = stateMachineContext;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2017 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.data.jpa;
|
||||
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.data.RepositoryStateMachinePersist;
|
||||
import org.springframework.statemachine.data.StateMachineRepository;
|
||||
|
||||
/**
|
||||
* {@code JPA} based implementation of a {@link RepositoryStateMachinePersist}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class JpaRepositoryStateMachinePersist<S, E> extends RepositoryStateMachinePersist<JpaRepositoryStateMachine, S, E> {
|
||||
|
||||
private final JpaStateMachineRepository jpaStateMachineRepository;
|
||||
|
||||
/**
|
||||
* Instantiates a new jpa repository state machine persist.
|
||||
*
|
||||
* @param jpaStateMachineRepository the jpa state machine repository
|
||||
*/
|
||||
public JpaRepositoryStateMachinePersist(JpaStateMachineRepository jpaStateMachineRepository) {
|
||||
super();
|
||||
this.jpaStateMachineRepository = jpaStateMachineRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StateMachineRepository<JpaRepositoryStateMachine> getRepository() {
|
||||
return jpaStateMachineRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JpaRepositoryStateMachine build(StateMachineContext<S, E> context, byte[] serialisedContext) {
|
||||
JpaRepositoryStateMachine jpaRepositoryStateMachine = new JpaRepositoryStateMachine();
|
||||
jpaRepositoryStateMachine.setMachineId(context.getId());
|
||||
jpaRepositoryStateMachine.setState(context.getState().toString());
|
||||
jpaRepositoryStateMachine.setStateMachineContext(serialisedContext);
|
||||
return jpaRepositoryStateMachine;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2017 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.data.jpa;
|
||||
|
||||
import org.springframework.statemachine.data.StateMachineRepository;
|
||||
|
||||
/**
|
||||
* JPA entity for state machine.
|
||||
* A {@link StateMachineRepository} interface for JPA used for states machines.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public interface JpaStateMachineRepository extends StateMachineRepository<JpaRepositoryStateMachine>{
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
* Copyright 2016-2017 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.
|
||||
@@ -27,12 +27,21 @@ import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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.StateMachineConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.data.AbstractRepositoryTests;
|
||||
import org.springframework.statemachine.data.RepositoryState;
|
||||
import org.springframework.statemachine.data.RepositoryTransition;
|
||||
import org.springframework.statemachine.data.StateMachineRepository;
|
||||
import org.springframework.statemachine.data.StateRepository;
|
||||
import org.springframework.statemachine.data.TransitionRepository;
|
||||
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
|
||||
import org.springframework.statemachine.transition.TransitionKind;
|
||||
|
||||
/**
|
||||
@@ -253,6 +262,36 @@ public class JpaRepositoryTests extends AbstractRepositoryTests {
|
||||
context.refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testStateMachinePersistWithStrings() {
|
||||
context.register(TestConfig.class, ConfigWithStrings.class);
|
||||
context.refresh();
|
||||
|
||||
StateMachine<String, String> stateMachine = context.getBean(StateMachine.class);
|
||||
stateMachine.start();
|
||||
assertThat(stateMachine.getState().getId(), is("S1"));
|
||||
stateMachine.sendEvent("E1");
|
||||
assertThat(stateMachine.getState().getId(), is("S2"));
|
||||
stateMachine.sendEvent("E2");
|
||||
assertThat(stateMachine.getState().getId(), is("S1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testStateMachinePersistWithEnums() {
|
||||
context.register(TestConfig.class, ConfigWithEnums.class);
|
||||
context.refresh();
|
||||
|
||||
StateMachine<PersistTestStates, PersistTestEvents> stateMachine = context.getBean(StateMachine.class);
|
||||
stateMachine.start();
|
||||
assertThat(stateMachine.getState().getId(), is(PersistTestStates.S1));
|
||||
stateMachine.sendEvent(PersistTestEvents.E1);
|
||||
assertThat(stateMachine.getState().getId(), is(PersistTestStates.S2));
|
||||
stateMachine.sendEvent(PersistTestEvents.E2);
|
||||
assertThat(stateMachine.getState().getId(), is(PersistTestStates.S1));
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
static class TestConfig {
|
||||
}
|
||||
@@ -275,6 +314,110 @@ public class JpaRepositoryTests extends AbstractRepositoryTests {
|
||||
|
||||
@Autowired
|
||||
StateRepository<? extends RepositoryState> statesRepository4;
|
||||
|
||||
@Autowired
|
||||
JpaStateMachineRepository jpaStateMachineRepository1;
|
||||
|
||||
@Autowired
|
||||
StateMachineRepository<JpaRepositoryStateMachine> jpaStateMachineRepository2;
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class ConfigWithStrings extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Autowired
|
||||
private JpaStateMachineRepository jpaStateMachineRepository;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.machineId("xxx1")
|
||||
.and()
|
||||
.withPersistence()
|
||||
.runtimePersister(stateMachineRuntimePersister());
|
||||
}
|
||||
|
||||
@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 StateMachineRuntimePersister<String, String> stateMachineRuntimePersister() {
|
||||
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class ConfigWithEnums extends StateMachineConfigurerAdapter<PersistTestStates, PersistTestEvents> {
|
||||
|
||||
@Autowired
|
||||
private JpaStateMachineRepository jpaStateMachineRepository;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<PersistTestStates, PersistTestEvents> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.machineId("xxx2")
|
||||
.and()
|
||||
.withPersistence()
|
||||
.runtimePersister(stateMachineRuntimePersister());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<PersistTestStates, PersistTestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(PersistTestStates.S1)
|
||||
.state(PersistTestStates.S2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<PersistTestStates, PersistTestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(PersistTestStates.S1)
|
||||
.target(PersistTestStates.S2)
|
||||
.event(PersistTestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(PersistTestStates.S2)
|
||||
.target(PersistTestStates.S1)
|
||||
.event(PersistTestEvents.E2);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StateMachineRuntimePersister<PersistTestStates, PersistTestEvents> stateMachineRuntimePersister() {
|
||||
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
|
||||
}
|
||||
}
|
||||
|
||||
public enum PersistTestStates {
|
||||
S1, S2;
|
||||
}
|
||||
|
||||
public enum PersistTestEvents {
|
||||
E1, E2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2017 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.data;
|
||||
|
||||
/**
|
||||
* Generic base class representing state machine entity.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public abstract class RepositoryStateMachine extends BaseRepositoryEntity {
|
||||
|
||||
/**
|
||||
* Gets the machine id.
|
||||
*
|
||||
* @return the machine id
|
||||
*/
|
||||
public abstract String getMachineId();
|
||||
|
||||
/**
|
||||
* Gets the state.
|
||||
*
|
||||
* @return the state
|
||||
*/
|
||||
public abstract String getState();
|
||||
|
||||
/**
|
||||
* Gets the state machine context.
|
||||
*
|
||||
* @return the state machine context
|
||||
*/
|
||||
public abstract byte[] getStateMachineContext();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
* Copyright 2016-2017 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.
|
||||
@@ -30,7 +30,6 @@ import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.action.SpelExpressionAction;
|
||||
import org.springframework.statemachine.config.model.AbstractStateMachineModelFactory;
|
||||
import org.springframework.statemachine.config.model.ChoiceData;
|
||||
import org.springframework.statemachine.config.model.ConfigurationData;
|
||||
import org.springframework.statemachine.config.model.DefaultStateMachineModel;
|
||||
import org.springframework.statemachine.config.model.EntryData;
|
||||
import org.springframework.statemachine.config.model.ExitData;
|
||||
@@ -80,8 +79,6 @@ public class RepositoryStateMachineModelFactory extends AbstractStateMachineMode
|
||||
|
||||
@Override
|
||||
public StateMachineModel<String, String> build(String machineId) {
|
||||
ConfigurationData<String, String> configurationData = new ConfigurationData<>();
|
||||
|
||||
Collection<StateData<String, String>> stateDatas = new ArrayList<>();
|
||||
for (RepositoryState s : stateRepository.findByMachineId(machineId == null ? "" : machineId)) {
|
||||
|
||||
@@ -284,8 +281,7 @@ public class RepositoryStateMachineModelFactory extends AbstractStateMachineMode
|
||||
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData, choicesCopy, junctionsCopy, forks, joins,
|
||||
entrys, exits, historys);
|
||||
|
||||
StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData);
|
||||
return stateMachineModel;
|
||||
return new DefaultStateMachineModel<>(null, statesData, transitionsData);
|
||||
}
|
||||
|
||||
private Guard<String, String> resolveGuard(RepositoryTransition t) {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2017 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.data;
|
||||
|
||||
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.KryoSerialisationService;
|
||||
|
||||
/**
|
||||
* Base implementation of a {@link StateMachinePersist} using Spring Data Repositories.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
* @param <M> the type of entity
|
||||
*/
|
||||
public abstract class RepositoryStateMachinePersist<M extends RepositoryStateMachine, S, E> implements StateMachinePersist<S, E, Object> {
|
||||
|
||||
private final Log log = LogFactory.getLog(RepositoryStateMachinePersist.class);
|
||||
private final KryoSerialisationService serialisationService = new KryoSerialisationService();
|
||||
|
||||
@Override
|
||||
public void write(StateMachineContext<S, E> context, Object contextObj) throws Exception {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Persisting context " + context + " using contextObj " + contextObj);
|
||||
}
|
||||
M build = build(context, serialisationService.serializeStateMachineContext(context));
|
||||
getRepository().save(build);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateMachineContext<S, E> read(Object contextObj) throws Exception {
|
||||
M repositoryStateMachine = getRepository().findOne(contextObj.toString());
|
||||
if (repositoryStateMachine != null) {
|
||||
return serialisationService.deserializeStateMachineContext(repositoryStateMachine.getStateMachineContext());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repository.
|
||||
*
|
||||
* @return the repository
|
||||
*/
|
||||
protected abstract StateMachineRepository<M> getRepository();
|
||||
|
||||
/**
|
||||
* Builds the generic {@link RepositoryStateMachine} entity.
|
||||
*
|
||||
* @param context the context
|
||||
* @param serialisedContext the serialised context
|
||||
* @return the repository state machine entity
|
||||
*/
|
||||
protected abstract M build(StateMachineContext<S, E> context, byte[] serialisedContext);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2017 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.data;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
/**
|
||||
* Generic {@link Repository} interface for states machines.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface StateMachineRepository<M extends RepositoryStateMachine> extends CrudRepository<M, String> {
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2017 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.UUID;
|
||||
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
/**
|
||||
* Simple service and utility class helping with kryo serialisation.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public class KryoSerialisationService {
|
||||
|
||||
// kryo is not a thread safe so using thread local, also
|
||||
// adding custom serializer for state machine context.
|
||||
private static final ThreadLocal<Kryo> kryoThreadLocal = new ThreadLocal<Kryo>() {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected Kryo initialValue() {
|
||||
Kryo kryo = new Kryo();
|
||||
kryo.addDefaultSerializer(StateMachineContext.class, new StateMachineContextSerializer());
|
||||
kryo.addDefaultSerializer(MessageHeaders.class, new MessageHeadersSerializer());
|
||||
kryo.addDefaultSerializer(UUID.class, new UUIDSerializer());
|
||||
return kryo;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize state machine context into byte array.
|
||||
*
|
||||
* @param <S> the generic type
|
||||
* @param <E> the element type
|
||||
* @param context the context
|
||||
* @return the byte[]
|
||||
*/
|
||||
public <S, E> byte[] serializeStateMachineContext(StateMachineContext<S, E> context) {
|
||||
Kryo kryo = kryoThreadLocal.get();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Output output = new Output(out);
|
||||
kryo.writeObject(output, context);
|
||||
output.close();
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize state machine context from byte array.
|
||||
*
|
||||
* @param <S> the generic type
|
||||
* @param <E> the element type
|
||||
* @param data the data
|
||||
* @return the state machine context
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S, E> StateMachineContext<S, E> deserializeStateMachineContext(byte[] data) {
|
||||
if (data == null || data.length == 0) {
|
||||
return null;
|
||||
}
|
||||
Kryo kryo = kryoThreadLocal.get();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(data);
|
||||
Input input = new Input(in);
|
||||
return kryo.readObject(input, StateMachineContext.class);
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,18 @@ project('spring-statemachine-samples-datajpa') {
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-datajpapersist') {
|
||||
description = 'Spring State Machine Data Jpa Persist Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-boot")
|
||||
compile project(":spring-statemachine-data-common:spring-statemachine-data-jpa")
|
||||
compile("org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion")
|
||||
compile("org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion")
|
||||
compile("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
||||
compile("com.h2database:h2:$h2Version")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-monitoring') {
|
||||
description = 'Spring State Machine Monitoring Sample'
|
||||
dependencies {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2017 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.datajpapersist;
|
||||
|
||||
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,105 @@
|
||||
/*
|
||||
* Copyright 2017 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.datajpapersist;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.config.EnableStateMachineFactory;
|
||||
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.data.jpa.JpaPersistingStateMachineInterceptor;
|
||||
import org.springframework.statemachine.data.jpa.JpaStateMachineRepository;
|
||||
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
|
||||
|
||||
@Configuration
|
||||
public class StateMachineConfig {
|
||||
|
||||
//tag::snippetA[]
|
||||
@Configuration
|
||||
@EnableStateMachineFactory
|
||||
public static class Config extends StateMachineConfigurerAdapter<States, Events> {
|
||||
|
||||
@Autowired
|
||||
private JpaStateMachineRepository jpaStateMachineRepository;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
|
||||
throws Exception {
|
||||
config
|
||||
.withPersistence()
|
||||
.runtimePersister(stateMachineruntimePersister());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<States, Events> states)
|
||||
throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(States.S1)
|
||||
.states(EnumSet.allOf(States.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
|
||||
throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(States.S1).target(States.S2)
|
||||
.event(Events.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.S2).target(States.S3)
|
||||
.event(Events.E2)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.S3).target(States.S4)
|
||||
.event(Events.E3)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.S4).target(States.S5)
|
||||
.event(Events.E4)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.S5).target(States.S6)
|
||||
.event(Events.E5)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.S6).target(States.S1)
|
||||
.event(Events.E6);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StateMachineRuntimePersister<States, Events> stateMachineruntimePersister() {
|
||||
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
|
||||
}
|
||||
}
|
||||
//end::snippetA[]
|
||||
|
||||
//tag::snippetB[]
|
||||
public enum States {
|
||||
S1, S2, S3, S4, S5, S6;
|
||||
}
|
||||
|
||||
public enum Events {
|
||||
E1, E2, E3, E4, E5, E6;
|
||||
}
|
||||
//end::snippetB[]
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2017 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.datajpapersist;
|
||||
|
||||
import java.util.EnumSet;
|
||||
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.access.StateMachineAccess;
|
||||
import org.springframework.statemachine.access.StateMachineFunction;
|
||||
import org.springframework.statemachine.config.StateMachineFactory;
|
||||
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;
|
||||
|
||||
import demo.datajpapersist.StateMachineConfig.Events;
|
||||
import demo.datajpapersist.StateMachineConfig.States;
|
||||
|
||||
@Controller
|
||||
public class StateMachineController {
|
||||
|
||||
public final static String MACHINE_ID_1 = "datajpapersist1";
|
||||
public final static String MACHINE_ID_2 = "datajpapersist2";
|
||||
private final static String[] MACHINES = new String[] { MACHINE_ID_1, MACHINE_ID_2 };
|
||||
|
||||
@Autowired
|
||||
private StateMachineFactory<States, Events> stateMachineFactory;
|
||||
|
||||
@Autowired
|
||||
private StateMachinePersist<States, Events, String> stateMachinePersist;
|
||||
|
||||
private StateMachine<States, Events> cachedStateMachine;
|
||||
private final StateMachineLogListener listener = new StateMachineLogListener();
|
||||
|
||||
@RequestMapping("/")
|
||||
public String home() {
|
||||
return "redirect:/state";
|
||||
}
|
||||
|
||||
@RequestMapping("/state")
|
||||
public String feedAndGetStates(
|
||||
@RequestParam(value = "events", required = false) List<Events> events,
|
||||
@RequestParam(value = "machine", required = false, defaultValue = MACHINE_ID_1) String machine,
|
||||
Model model) throws Exception {
|
||||
|
||||
StateMachine<States, Events> stateMachine = getStateMachine(machine);
|
||||
if (events != null) {
|
||||
for (Events event : events) {
|
||||
stateMachine.sendEvent(event);
|
||||
}
|
||||
}
|
||||
StateMachineContext<States, Events> stateMachineContext = stateMachinePersist.read(machine);
|
||||
model.addAttribute("allMachines", MACHINES);
|
||||
model.addAttribute("machine", machine);
|
||||
model.addAttribute("allEvents", getEvents());
|
||||
model.addAttribute("messages", createMessages(listener.getMessages()));
|
||||
model.addAttribute("context", stateMachineContext != null ? stateMachineContext.toString() : "");
|
||||
return "states";
|
||||
}
|
||||
|
||||
private synchronized StateMachine<States, Events> getStateMachine(String machineId) throws Exception {
|
||||
if (cachedStateMachine == null) {
|
||||
cachedStateMachine = buildStateMachine(machineId);
|
||||
cachedStateMachine.start();
|
||||
} else {
|
||||
if (!ObjectUtils.nullSafeEquals(cachedStateMachine.getId(), machineId)) {
|
||||
cachedStateMachine.stop();
|
||||
cachedStateMachine = buildStateMachine(machineId);
|
||||
cachedStateMachine.start();
|
||||
}
|
||||
}
|
||||
return cachedStateMachine;
|
||||
}
|
||||
|
||||
private StateMachine<States, Events> buildStateMachine(String machineId) throws Exception {
|
||||
StateMachine<States, Events> stateMachine = stateMachineFactory.getStateMachine(machineId);
|
||||
stateMachine.addStateListener(listener);
|
||||
listener.resetMessages();
|
||||
return restoreStateMachine(stateMachine, stateMachinePersist.read(machineId));
|
||||
}
|
||||
|
||||
private StateMachine<States, Events> restoreStateMachine(StateMachine<States, Events> stateMachine,
|
||||
StateMachineContext<States, Events> stateMachineContext) {
|
||||
if (stateMachineContext == null) {
|
||||
return stateMachine;
|
||||
}
|
||||
stateMachine.stop();
|
||||
stateMachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess<States, Events>>() {
|
||||
|
||||
@Override
|
||||
public void apply(StateMachineAccess<States, Events> function) {
|
||||
function.resetStateMachine(stateMachineContext);
|
||||
}
|
||||
});
|
||||
return stateMachine;
|
||||
}
|
||||
|
||||
private Events[] getEvents() {
|
||||
return EnumSet.allOf(Events.class).toArray(new Events[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,52 @@
|
||||
/*
|
||||
* Copyright 2017 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.datajpapersist;
|
||||
|
||||
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;
|
||||
|
||||
import demo.datajpapersist.StateMachineConfig.Events;
|
||||
import demo.datajpapersist.StateMachineConfig.States;
|
||||
|
||||
public class StateMachineLogListener extends StateMachineListenerAdapter<States, Events> {
|
||||
|
||||
private final LinkedList<String> messages = new LinkedList<String>();
|
||||
|
||||
public List<String> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void resetMessages() {
|
||||
messages.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateContext(StateContext<States, Events> 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,6 @@
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
security:
|
||||
basic:
|
||||
enabled: false
|
||||
@@ -0,0 +1,45 @@
|
||||
<!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>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2017 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.datajpapersist;
|
||||
|
||||
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.datajpapersist.Application;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = { Application.class })
|
||||
@WebAppConfiguration
|
||||
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class DataJpaPersistTests {
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
@Test
|
||||
public void testHome() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state")).
|
||||
andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendEventE1() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state").param("events", "E1")).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(containsString("Exit S1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendEventsE1E2() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state").param("events", "E1").param("events", "E2")).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(allOf(
|
||||
containsString("Exit S1"),
|
||||
containsString("Exit S2"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendEventE4() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state").param("events", "E1").param("machine", StateMachineController.MACHINE_ID_2)).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(containsString("Exit S1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeMachineRestores() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state").param("events", "E1")).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(containsString("Exit S1")));
|
||||
mvc.
|
||||
perform(get("/state").param("events", "E1").param("machine", StateMachineController.MACHINE_ID_2)).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(containsString("Exit S1")));
|
||||
mvc.
|
||||
perform(get("/state").param("events", "E2").param("machine", StateMachineController.MACHINE_ID_1)).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(containsString("Exit S2")));
|
||||
mvc.
|
||||
perform(get("/state").param("events", "E2").param("machine", StateMachineController.MACHINE_ID_2)).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(containsString("Exit S2")));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
mvc = MockMvcBuilders.webAppContextSetup(context).build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user