Rename sample datajpapersist to datapersist

- As it now supports mongo/redis using profiles,
  remove jpa from name.
- Change build and docs.
- Backport #509
This commit is contained in:
jvalkeal
2018-02-21 17:26:19 +02:00
parent 282d395ba0
commit 9788d2dfeb
11 changed files with 57 additions and 29 deletions

View File

@@ -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.datapersist;
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[]

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2017-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.datapersist;
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.context.annotation.Profile;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.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.data.mongodb.MongoDbPersistingStateMachineInterceptor;
import org.springframework.statemachine.data.mongodb.MongoDbStateMachineRepository;
import org.springframework.statemachine.data.redis.RedisPersistingStateMachineInterceptor;
import org.springframework.statemachine.data.redis.RedisStateMachineRepository;
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
import org.springframework.statemachine.service.DefaultStateMachineService;
import org.springframework.statemachine.service.StateMachineService;
@Configuration
public class StateMachineConfig {
//tag::snippetA[]
@Configuration
@Profile("jpa")
public static class JpaPersisterConfig {
@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
JpaStateMachineRepository jpaStateMachineRepository) {
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}
//end::snippetA[]
//tag::snippetB[]
@Configuration
@Profile("mongo")
public static class MongoPersisterConfig {
@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
MongoDbStateMachineRepository jpaStateMachineRepository) {
return new MongoDbPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}
//end::snippetB[]
//tag::snippetC[]
@Configuration
@Profile("redis")
public static class RedisPersisterConfig {
@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
RedisStateMachineRepository jpaStateMachineRepository) {
return new RedisPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}
//end::snippetC[]
@Configuration
@EnableStateMachineFactory
public static class MachineConfig extends StateMachineConfigurerAdapter<States, Events> {
//tag::snippetD[]
@Autowired
private StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister;
@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
throws Exception {
config
.withPersistence()
.runtimePersister(stateMachineRuntimePersister);
}
//end::snippetD[]
@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);
}
}
@Configuration
public static class ServiceConfig {
//tag::snippetE[]
@Bean
public StateMachineService<States, Events> stateMachineService(
StateMachineFactory<States, Events> stateMachineFactory,
StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister) {
return new DefaultStateMachineService<States, Events>(stateMachineFactory, stateMachineRuntimePersister);
}
//end::snippetE[]
}
public enum States {
S1, S2, S3, S4, S5, S6;
}
public enum Events {
E1, E2, E3, E4, E5, E6;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2017-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.datapersist;
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.service.StateMachineService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import demo.datapersist.StateMachineConfig.Events;
import demo.datapersist.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 };
private final StateMachineLogListener listener = new StateMachineLogListener();
private StateMachine<States, Events> currentStateMachine;
@Autowired
private StateMachineService<States, Events> stateMachineService;
@Autowired
private StateMachinePersist<States, Events, String> stateMachinePersist;
@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";
}
//tag::snippetA[]
private synchronized StateMachine<States, Events> getStateMachine(String machineId) throws Exception {
listener.resetMessages();
if (currentStateMachine == null) {
currentStateMachine = stateMachineService.acquireStateMachine(machineId);
currentStateMachine.addStateListener(listener);
currentStateMachine.start();
} else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) {
stateMachineService.releaseStateMachine(currentStateMachine.getId());
currentStateMachine.stop();
currentStateMachine = stateMachineService.acquireStateMachine(machineId);
currentStateMachine.addStateListener(listener);
currentStateMachine.start();
}
return currentStateMachine;
}
//end::snippetA[]
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();
}
}

View File

@@ -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.datapersist;
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.datapersist.StateMachineConfig.Events;
import demo.datapersist.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");
}
}
}

View File

@@ -0,0 +1,43 @@
logging:
level:
root: INFO
spring:
profiles:
active: jpa
security:
basic:
enabled: false
---
spring:
profiles: jpa
statemachine:
data:
mongo:
repositories:
enabled: false
redis:
repositories:
enabled: false
---
spring:
profiles: mongo
statemachine:
data:
jpa:
repositories:
enabled: false
redis:
repositories:
enabled: false
---
spring:
profiles: redis
statemachine:
data:
jpa:
repositories:
enabled: false
mongo:
repositories:
enabled: false

View File

@@ -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>

View File

@@ -0,0 +1,108 @@
/*
* 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.datapersist;
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.datapersist.Application;
import demo.datapersist.StateMachineController;
@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();
}
}