Add redis persist sample
- New sample app eventservice which is used to demonstrate how redis is used as a repository for StateMachineContext. - Uses pooled request scope machine instances so that with every request a machine is reseted from a redis. - Provides UI to play around. - Provides rest endpoint to send 'pageview' events for processing. For example using curl command like: curl http://localhost:8080/feed -H "Content-Type: application/json" --data '{"user":"joe","id":"VIEW_I"}' - Fixes #160
This commit is contained in:
@@ -226,7 +226,8 @@ configure(sampleProjects()) {
|
||||
configurations.archives.artifacts.removeAll { it.archiveTask.is jar }
|
||||
tasks.findByPath("artifactoryPublish")?.enabled = false
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-samples-common")
|
||||
// compile project(":spring-statemachine-samples-common")
|
||||
compile project(":spring-statemachine-core")
|
||||
compile "org.springframework:spring-context-support:$springVersion"
|
||||
testCompile "org.springframework:spring-test:$springVersion"
|
||||
testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
|
||||
@@ -19,6 +19,7 @@ include 'spring-statemachine-samples:persist'
|
||||
include 'spring-statemachine-samples:web'
|
||||
include 'spring-statemachine-samples:scope'
|
||||
include 'spring-statemachine-samples:security'
|
||||
include 'spring-statemachine-samples:eventservice'
|
||||
|
||||
rootProject.children.find {
|
||||
if (it.name == 'spring-statemachine-recipes') {
|
||||
|
||||
@@ -132,6 +132,11 @@ public class ObservableMap<K, V> implements Map<K, V> {
|
||||
return delegate.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return delegate.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the delegating map instance.
|
||||
*
|
||||
|
||||
@@ -2,27 +2,43 @@ description = 'Spring State Machine Samples Common'
|
||||
|
||||
project('spring-statemachine-samples-turnstile') {
|
||||
description = 'Spring State Machine Turnstile Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-samples-common")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-showcase') {
|
||||
description = 'Spring State Machine Showcase Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-samples-common")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-cdplayer') {
|
||||
description = 'Spring State Machine CD Player Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-samples-common")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-tasks') {
|
||||
description = 'Spring State Machine Parallel Regions Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-samples-common")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-washer') {
|
||||
description = 'Spring State Machine History State Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-samples-common")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-zookeeper') {
|
||||
description = 'Spring State Machine Distributed Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-samples-common")
|
||||
compile project(":spring-statemachine-zookeeper")
|
||||
}
|
||||
}
|
||||
@@ -30,6 +46,7 @@ project('spring-statemachine-samples-zookeeper') {
|
||||
project('spring-statemachine-samples-persist') {
|
||||
description = 'Spring State Machine Persist Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-samples-common")
|
||||
compile project(":spring-statemachine-recipes-common")
|
||||
compile ("org.hsqldb:hsqldb:2.3.1")
|
||||
compile ("org.springframework:spring-jdbc:$springVersion")
|
||||
@@ -66,3 +83,13 @@ project('spring-statemachine-samples-security') {
|
||||
compile("org.springframework.security:spring-security-web:$springSecurityVersion")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-samples-eventservice') {
|
||||
description = 'Spring State Machine Event Service Sample'
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-redis")
|
||||
compile("org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion")
|
||||
// compile("org.springframework.boot:spring-boot-starter-redis:$springBootVersion")
|
||||
compile("org.apache.commons:commons-pool2:2.4.2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package demo.eventservice;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package demo.eventservice;
|
||||
|
||||
import demo.eventservice.StateMachineConfig.Events;
|
||||
|
||||
public class Pageview {
|
||||
|
||||
private String user;
|
||||
private Events id;
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Events getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Events id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Pageview [user=" + user + ", id=" + id + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package demo.eventservice;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactoryBean;
|
||||
import org.springframework.aop.target.CommonsPool2TargetSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.annotation.ScopedProxyMode;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachinePersist;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.config.StateMachineBuilder;
|
||||
import org.springframework.statemachine.config.StateMachineBuilder.Builder;
|
||||
import org.springframework.statemachine.redis.RedisStateMachineContextRepository;
|
||||
import org.springframework.statemachine.support.RepositoryStateMachinePersist;
|
||||
|
||||
@Configuration
|
||||
public class StateMachineConfig {
|
||||
|
||||
@Bean
|
||||
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
|
||||
public ProxyFactoryBean stateMachine() {
|
||||
ProxyFactoryBean pfb = new ProxyFactoryBean();
|
||||
pfb.setTargetSource(poolTargetSource());
|
||||
return pfb;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CommonsPool2TargetSource poolTargetSource() {
|
||||
CommonsPool2TargetSource pool = new CommonsPool2TargetSource();
|
||||
pool.setMaxSize(3);
|
||||
pool.setTargetBeanName("stateMachineTarget");
|
||||
return pool;
|
||||
}
|
||||
|
||||
@Bean(name = "stateMachineTarget")
|
||||
@Scope(scopeName="prototype")
|
||||
public StateMachine<States, Events> stateMachineTarget() throws Exception {
|
||||
Builder<States, Events> builder = StateMachineBuilder.<States, Events>builder();
|
||||
|
||||
builder.configureConfiguration()
|
||||
.withConfiguration()
|
||||
.autoStartup(true);
|
||||
|
||||
builder.configureStates()
|
||||
.withStates()
|
||||
.initial(States.HOME)
|
||||
.states(EnumSet.allOf(States.class));
|
||||
|
||||
builder.configureTransitions()
|
||||
.withInternal()
|
||||
.source(States.ITEMS).event(Events.ADD)
|
||||
.action(addAction())
|
||||
.and()
|
||||
.withInternal()
|
||||
.source(States.CART).event(Events.DEL)
|
||||
.action(delAction())
|
||||
.and()
|
||||
.withInternal()
|
||||
.source(States.PAYMENT).event(Events.PAY)
|
||||
.action(payAction())
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.HOME).target(States.ITEMS)
|
||||
.action(pageviewAction())
|
||||
.event(Events.VIEW_I)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.CART).target(States.ITEMS)
|
||||
.action(pageviewAction())
|
||||
.event(Events.VIEW_I)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.ITEMS).target(States.CART)
|
||||
.action(pageviewAction())
|
||||
.event(Events.VIEW_C)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.PAYMENT).target(States.CART)
|
||||
.action(pageviewAction())
|
||||
.event(Events.VIEW_C)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.CART).target(States.PAYMENT)
|
||||
.action(pageviewAction())
|
||||
.event(Events.VIEW_P)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.ITEMS).target(States.HOME)
|
||||
.action(resetAction())
|
||||
.event(Events.RESET)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.CART).target(States.HOME)
|
||||
.action(resetAction())
|
||||
.event(Events.RESET)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.PAYMENT).target(States.HOME)
|
||||
.action(resetAction())
|
||||
.event(Events.RESET);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Action<States, Events> pageviewAction() {
|
||||
return new Action<States, Events>() {
|
||||
|
||||
@Override
|
||||
public void execute(StateContext<States, Events> context) {
|
||||
String variable = context.getTarget().getId().toString();
|
||||
Integer count = context.getExtendedState().get(variable, Integer.class);
|
||||
if (count == null) {
|
||||
context.getExtendedState().getVariables().put(variable, 1);
|
||||
} else {
|
||||
context.getExtendedState().getVariables().put(variable, (count + 1));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Action<States, Events> addAction() {
|
||||
return new Action<States, Events>() {
|
||||
|
||||
@Override
|
||||
public void execute(StateContext<States, Events> context) {
|
||||
Integer count = context.getExtendedState().get("COUNT", Integer.class);
|
||||
if (count == null) {
|
||||
context.getExtendedState().getVariables().put("COUNT", 1);
|
||||
} else {
|
||||
context.getExtendedState().getVariables().put("COUNT", (count + 1));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Action<States, Events> delAction() {
|
||||
return new Action<States, Events>() {
|
||||
|
||||
@Override
|
||||
public void execute(StateContext<States, Events> context) {
|
||||
Integer count = context.getExtendedState().get("COUNT", Integer.class);
|
||||
if (count != null && count > 0) {
|
||||
context.getExtendedState().getVariables().put("COUNT", (count - 1));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Action<States, Events> payAction() {
|
||||
return new Action<States, Events>() {
|
||||
|
||||
@Override
|
||||
public void execute(StateContext<States, Events> context) {
|
||||
context.getExtendedState().getVariables().put("PAYED", true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Action<States, Events> resetAction() {
|
||||
return new Action<States, Events>() {
|
||||
|
||||
@Override
|
||||
public void execute(StateContext<States, Events> context) {
|
||||
context.getExtendedState().getVariables().clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisConnectionFactory redisConnectionFactory() {
|
||||
return new JedisConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StateMachinePersist<States, Events, String> stateMachinePersist(RedisConnectionFactory connectionFactory) {
|
||||
return new RepositoryStateMachinePersist<States, Events>(new RedisStateMachineContextRepository<States, Events>(connectionFactory));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public String stateChartModel() throws IOException {
|
||||
ClassPathResource model = new ClassPathResource("statechartmodel.txt");
|
||||
InputStream inputStream = model.getInputStream();
|
||||
Scanner scanner = new Scanner(inputStream);
|
||||
String content = scanner.useDelimiter("\\Z").next();
|
||||
scanner.close();
|
||||
return content;
|
||||
}
|
||||
|
||||
public enum States {
|
||||
HOME,
|
||||
ITEMS,
|
||||
CART,
|
||||
PAYMENT
|
||||
}
|
||||
|
||||
public enum Events {
|
||||
VIEW_I,
|
||||
VIEW_C,
|
||||
VIEW_P,
|
||||
RESET,
|
||||
ADD,
|
||||
DEL,
|
||||
PAY
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package demo.eventservice;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
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.support.DefaultStateMachineContext;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import demo.eventservice.StateMachineConfig.Events;
|
||||
import demo.eventservice.StateMachineConfig.States;
|
||||
|
||||
@Controller
|
||||
public class StateMachineController {
|
||||
|
||||
@Autowired
|
||||
private StateMachine<States, Events> stateMachine;
|
||||
|
||||
@Autowired
|
||||
private StateMachinePersist<States, Events, String> stateMachinePersist;
|
||||
|
||||
@Autowired
|
||||
private String stateChartModel;
|
||||
|
||||
@RequestMapping("/")
|
||||
public String home() {
|
||||
return "redirect:/state";
|
||||
}
|
||||
|
||||
@RequestMapping("/state")
|
||||
public String feedAndGetState(@RequestParam(value = "user", required = false) String user,
|
||||
@RequestParam(value = "id", required = false) Events id, Model model) throws Exception {
|
||||
model.addAttribute("user", user);
|
||||
model.addAttribute("allTypes", Events.values());
|
||||
model.addAttribute("stateChartModel", stateChartModel);
|
||||
// we may get into this page without a user so
|
||||
// do nothing with a state machine
|
||||
if (StringUtils.hasText(user)) {
|
||||
resetStateMachineFromStore(user);
|
||||
if (id != null) {
|
||||
feedMachine(user, id);
|
||||
}
|
||||
model.addAttribute("states", stateMachine.getState().getIds());
|
||||
model.addAttribute("extendedState", stateMachine.getExtendedState().getVariables());
|
||||
}
|
||||
return "states";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/feed",method= RequestMethod.POST)
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public void feedPageview(@RequestBody(required = true) Pageview event) throws Exception {
|
||||
Assert.notNull(event.getUser(), "User must be set");
|
||||
Assert.notNull(event.getId(), "Id must be set");
|
||||
resetStateMachineFromStore(event.getUser());
|
||||
feedMachine(event.getUser(), event.getId());
|
||||
}
|
||||
|
||||
private void feedMachine(String user, Events id) throws Exception {
|
||||
stateMachine.sendEvent(id);
|
||||
stateMachinePersist.write(new DefaultStateMachineContext<States, Events>(stateMachine.getState().getId(), null, null,
|
||||
stateMachine.getExtendedState()), "testprefix:" + user);
|
||||
}
|
||||
|
||||
private StateMachine<States, Events> resetStateMachineFromStore(String user) throws Exception {
|
||||
final StateMachineContext<States, Events> context = stateMachinePersist.read("testprefix:" + user);
|
||||
stateMachine.stop();
|
||||
stateMachine.getStateMachineAccessor()
|
||||
.doWithAllRegions(new StateMachineFunction<StateMachineAccess<States, Events>>() {
|
||||
|
||||
@Override
|
||||
public void apply(StateMachineAccess<States, Events> function) {
|
||||
function.resetStateMachine(context);
|
||||
}
|
||||
});
|
||||
stateMachine.start();
|
||||
return stateMachine;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
security:
|
||||
basic:
|
||||
enabled: false
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
|
||||
<logger name="org.springframework" level="INFO"/>
|
||||
<logger name="org.springframework.statemachine" level="DEBUG"/>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,20 @@
|
||||
+---------------------------------------------------------------------------------------------------------------------+
|
||||
| SM |
|
||||
+---------------------------------------------------------------------------------------------------------------------+
|
||||
| |
|
||||
| +------------------+ VIEW_I +------------------+ VIEW_C +------------------+ VIEW_P +------------------+ |
|
||||
| *-->| HOME |--------->| ITEMS |--------->| CART |--------->| PAYMENT | |
|
||||
| | | | | | | | | |
|
||||
| | | | ADD | | DEL | | PAY | |
|
||||
| | | | +------------+ | | +------------+ | | +------------+ | |
|
||||
| | | RESET | | | | VIEW_I | | | | VIEW_C | | | | |
|
||||
| | |<---------| | v |<---------| | v |<---------| | v | |
|
||||
| +------------------+ +------------------+ +------------------+ +------------------+ |
|
||||
| ^ ^ ^ | | | |
|
||||
| | | RESET | | | | |
|
||||
| | +----------------------------------------------------+ | | |
|
||||
| | | VIEW_I | | |
|
||||
| | RESET +---------------------------------------------------+ | |
|
||||
| +--------------------------------------------------------------------------------------------------+ |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------------------------------------------+
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Spring Statemachine Event Service Demo</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
</head>
|
||||
<body>
|
||||
<div th:if="${user != null}">
|
||||
<p th:text="'User: ' + ${user}" />
|
||||
<p th:text="'States: ' + ${states}" />
|
||||
<p th:text="'Extended State: ' + ${extendedState}" />
|
||||
</div>
|
||||
|
||||
<form action="#" data-th-action="@{/state}" data-th-object="${model}" method="post">
|
||||
<ul>
|
||||
<li th:each="ty : ${allTypes}">
|
||||
<input type="radio" name="id" th:value="${ty}" />
|
||||
<label th:text="${ty}">replaced</label>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="submit" name="user" value="joe">joe</button>
|
||||
<button type="submit" name="user" value="bob">bob</button>
|
||||
<button type="submit" name="user" value="dave">dave</button>
|
||||
</form>
|
||||
|
||||
<pre th:text="${stateChartModel}"/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package demo.eventservice;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
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.SpringApplicationConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.statemachine.StateMachineContext;
|
||||
import org.springframework.statemachine.StateMachinePersist;
|
||||
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.eventservice.EventServiceTests.Config;
|
||||
import demo.eventservice.StateMachineConfig.Events;
|
||||
import demo.eventservice.StateMachineConfig.States;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = { Application.class, Config.class })
|
||||
@WebAppConfiguration
|
||||
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class EventServiceTests {
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
@Test
|
||||
public void testHome() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state")).
|
||||
andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendEvent() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state").param("user", "joe").param("id", "VIEW_I")).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(containsString("States: [ITEMS]")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd() throws Exception {
|
||||
mvc.
|
||||
perform(get("/state").param("user", "joe").param("id", "VIEW_I")).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(containsString("States: [ITEMS]")));
|
||||
mvc.
|
||||
perform(get("/state").param("user", "joe").param("id", "ADD")).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(containsString("States: [ITEMS]"))).
|
||||
andExpect(content().string(containsString("Extended State: {COUNT=1, ITEMS=1}")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeed() throws Exception {
|
||||
mvc.
|
||||
perform(post("/feed").content("{\"user\":\"joe\",\"id\":\"VIEW_I\"}").contentType(MediaType.APPLICATION_JSON)).
|
||||
andExpect(status().isOk());
|
||||
mvc.
|
||||
perform(post("/feed").content("{\"user\":\"joe\",\"id\":\"ADD\"}").contentType(MediaType.APPLICATION_JSON)).
|
||||
andExpect(status().isOk());
|
||||
mvc.
|
||||
perform(get("/state").param("user", "joe")).
|
||||
andExpect(status().isOk()).
|
||||
andExpect(content().string(containsString("States: [ITEMS]"))).
|
||||
andExpect(content().string(containsString("Extended State: {COUNT=1, ITEMS=1}")));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
mvc = MockMvcBuilders.webAppContextSetup(context).build();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public StateMachinePersist<States, Events, String> stateMachinePersist() {
|
||||
// use stateMachinePersist without redis to ease testing
|
||||
return new InMemoryStateMachinePersist();
|
||||
}
|
||||
}
|
||||
|
||||
static class InMemoryStateMachinePersist implements StateMachinePersist<States, Events, String> {
|
||||
|
||||
private final HashMap<String, StateMachineContext<States, Events>> contexts = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void write(StateMachineContext<States, Events> context, String contextOjb) throws Exception {
|
||||
contexts.put(contextOjb, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateMachineContext<States, Events> read(String contextOjb) throws Exception {
|
||||
return contexts.get(contextOjb);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user