Add new tasks sample

This commit is contained in:
Janne Valkealahti
2015-05-17 16:04:52 +01:00
parent 2a31254b91
commit cc4794d014
12 changed files with 602 additions and 0 deletions

View File

@@ -181,6 +181,7 @@ configure(rootProject) {
task copyDocsSamples(type: Copy) {
from 'spring-statemachine-core/src/test/java/org/springframework/statemachine/docs'
from 'spring-statemachine-samples/src/main/java/'
from 'spring-statemachine-samples/tasks/src/main/java/'
from 'spring-statemachine-samples/turnstile/src/main/java/'
from 'spring-statemachine-samples/showcase/src/main/java/'
from 'spring-statemachine-samples/cdplayer/src/main/java/'

View File

@@ -6,6 +6,7 @@ include 'spring-statemachine-samples'
include 'spring-statemachine-samples:turnstile'
include 'spring-statemachine-samples:showcase'
include 'spring-statemachine-samples:cdplayer'
include 'spring-statemachine-samples:tasks'
rootProject.children.find {
if (it.name == 'spring-statemachine-samples') {

View File

@@ -12,3 +12,6 @@ project('spring-statemachine-samples-cdplayer') {
description = 'Spring State Machine CD Player Sample'
}
project('spring-statemachine-samples-tasks') {
description = 'Spring State Machine Parallel Regions Sample'
}

View File

@@ -0,0 +1,19 @@
.gradle
bin
build
.settings
.classpath
.springBeans
.project
*.iml
*.ipr
*.iws
metastore_db
/samples/pig-scripting/src/main/resources/ml-100k.zip
/samples/pig-scripting/src/main/resources/ml-100k/u.data
/src/test/resources/s3.properties
/.idea/
.DS_Store
/out/
target
*.log

View File

@@ -0,0 +1,193 @@
package demo.tasks;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell.Bootstrap;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;
@Configuration
public class Application {
//tag::snippetA[]
@Configuration
@EnableStateMachine
static class StateMachineConfig
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.READY)
.fork(States.FORK)
.state(States.TASKS)
.join(States.JOIN)
.choice(States.CHOICE)
.state(States.ERROR)
.and()
.withStates()
.parent(States.TASKS)
.initial(States.T1)
.end(States.T1E)
.and()
.withStates()
.parent(States.TASKS)
.initial(States.T2)
.end(States.T2E)
.and()
.withStates()
.parent(States.TASKS)
.initial(States.T3)
.end(States.T3E)
.and()
.withStates()
.parent(States.ERROR)
.initial(States.AUTOMATIC)
.state(States.AUTOMATIC, automaticAction(), null)
.state(States.MANUAL);
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.READY).target(States.FORK)
.event(Events.RUN)
.and()
.withFork()
.source(States.FORK).target(States.TASKS)
.and()
.withExternal()
.source(States.T1).target(States.T1E)
.and()
.withExternal()
.source(States.T2).target(States.T2E)
.and()
.withExternal()
.source(States.T3).target(States.T3E)
.and()
.withJoin()
.source(States.TASKS).target(States.JOIN)
.and()
.withExternal()
.source(States.JOIN).target(States.CHOICE)
.and()
.withChoice()
.source(States.CHOICE)
.first(States.ERROR, tasksChoiceGuard())
.last(States.READY)
.and()
.withExternal()
.source(States.ERROR).target(States.READY)
.event(Events.CONTINUE)
.and()
.withExternal()
.source(States.AUTOMATIC).target(States.MANUAL)
.event(Events.FALLBACK)
.and()
.withInternal()
.source(States.MANUAL)
.action(fixAction())
.state(States.ERROR)
.event(Events.FIX);
}
@Bean
public Guard<States, Events> tasksChoiceGuard() {
return new Guard<States, Events>() {
@Override
public boolean evaluate(StateContext<States, Events> context) {
Map<Object, Object> variables = context.getExtendedState().getVariables();
return !(variables.get("T1").equals(true) && variables.get("T2").equals(true) && variables
.get("T3").equals(true));
}
};
}
@Bean
public Action<States, Events> automaticAction() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
Map<Object, Object> variables = context.getExtendedState().getVariables();
if (variables.get("T1").equals(false)) {
variables.put("T1", true);
context.getStateMachine().sendEvent(Events.CONTINUE);
} else {
context.getStateMachine().sendEvent(Events.FALLBACK);
}
}
};
}
@Bean
public Action<States, Events> fixAction() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
Map<Object, Object> variables = context.getExtendedState().getVariables();
if (variables.get("T1").equals(true) && variables.get("T2").equals(true)
&& variables.get("T3").equals(true)) {
context.getStateMachine().sendEvent(Events.CONTINUE);
}
}
};
}
@Bean
public Tasks tasks() {
return new Tasks();
}
}
//end::snippetA[]
//tag::snippetB[]
public static enum States {
READY,
FORK, JOIN, CHOICE,
TASKS, T1, T1E, T2, T2E, T3, T3E,
ERROR, AUTOMATIC, MANUAL
}
//end::snippetB[]
//tag::snippetC[]
public static enum Events {
RUN, FALLBACK, CONTINUE, FIX;
}
//end::snippetC[]
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnTransition
public static @interface StatesOnTransition {
States[] source() default {};
States[] target() default {};
}
public static void main(String[] args) throws Exception {
Bootstrap.main(args);
}
}

View File

@@ -0,0 +1,20 @@
package demo.tasks;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.stereotype.Component;
import demo.AbstractStateMachineCommands;
import demo.tasks.Application.Events;
import demo.tasks.Application.States;
@Component
public class StateMachineCommands extends AbstractStateMachineCommands<States, Events> {
@CliCommand(value = "sm event", help = "Sends an event to a state machine")
public String event(@CliOption(key = { "", "event" }, mandatory = true, help = "The event") final Events event) {
getStateMachine().sendEvent(event);
return "Event " + event + " send";
}
}

View File

@@ -0,0 +1,95 @@
package demo.tasks;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.annotation.WithStateMachine;
import demo.tasks.Application.Events;
import demo.tasks.Application.States;
import demo.tasks.Application.StatesOnTransition;
@WithStateMachine
public class Tasks {
private final static Log log = LogFactory.getLog(Tasks.class);
@Autowired
private StateMachine<States, Events> stateMachine;
private final Map<String, Boolean> tasks = new HashMap<String, Boolean>();
public Tasks() {
tasks.put("T1", true);
tasks.put("T2", true);
tasks.put("T3", true);
}
public void run() {
stateMachine.sendEvent(Events.RUN);
}
public void cont() {
stateMachine.sendEvent(Events.CONTINUE);
}
public void fix(String task) {
if (tasks.containsKey(task)) {
tasks.put(task, true);
}
stateMachine.sendEvent(Events.FIX);
}
public void fail(String task) {
if (tasks.containsKey(task)) {
tasks.put(task, false);
}
}
@StatesOnTransition(target = States.T1)
public void taskT1(ExtendedState extendedState) {
log.info("run task on T1");
extendedState.getVariables().put("T1", tasks.get("T1"));
}
@StatesOnTransition(target = States.T2)
public void taskT2(ExtendedState extendedState) {
log.info("run task on T2");
extendedState.getVariables().put("T2", tasks.get("T2"));
}
@StatesOnTransition(target = States.T3)
public void taskT3(ExtendedState extendedState) {
log.info("run task on T3");
extendedState.getVariables().put("T3", tasks.get("T3"));
}
@StatesOnTransition(target = States.AUTOMATIC)
public void automaticFix(ExtendedState extendedState) {
Map<Object, Object> variables = extendedState.getVariables();
if (variables.get("T1").equals(false)) {
variables.put("T1", true);
tasks.put("T1", true);
}
}
@StatesOnTransition(target = States.MANUAL)
public void manualFix(ExtendedState extendedState) {
Map<Object, Object> variables = extendedState.getVariables();
if (variables.get("T2").equals(false)) {
variables.put("T2", true);
tasks.put("T2", true);
}
}
@Override
public String toString() {
return "Tasks " + tasks;
}
}

View File

@@ -0,0 +1,35 @@
package demo.tasks;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.stereotype.Component;
@Component
public class TasksCommands implements CommandMarker {
@Autowired
private Tasks tasks;
@CliCommand(value = "tasks run", help = "Run tasks")
public void run() {
tasks.run();
}
@CliCommand(value = "tasks list", help = "List tasks")
public String list() {
return tasks.toString();
}
@CliCommand(value = "tasks fix", help = "Fix task")
public void fix(@CliOption(key = {"", "task"}, help = "Task id") String task) {
tasks.fix(task);
}
@CliCommand(value = "tasks fail", help = "Fail task")
public void fail(@CliOption(key = {"", "task"}, help = "Task id") String task) {
tasks.fail(task);
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="demo" />
</beans>

View File

@@ -0,0 +1,40 @@
+----------------------------------------------------------------------------+
| SM |
+----------------------------------------------------------------------------+
| |
| +---------------------------+ |
| FORK | TASKS | JOIN |
| | +---------------------------+ | |
| +-------------+ RUN | | +------------+ | | |
| *-->| READY |----->|----->| *-->| T1 |-->X |---->|----+ |
| +-------------+ | | +------------+ | | | |
| ^ ^ | |===========================| | | |
| | | | +------------+ | | |
| | | | *-->| T2 |-->X | | |
| | | | +------------+ | | |
| | | |===========================| | |
| | | | +------------+ | | |
| | | | *-->| T3 |-->X | | |
| | | | +------------+ | | |
| | | +---------------------------+ | |
| | | | |
| | | [OK] +------------+ | |
| | +--------------------------| CHOICE |<----------------+ |
| | +------------+ |
| | | |
| | | [ERROR] |
| | v |
| | +-----------------------------------------------+ |
| | | ERROR | |
| | +-----------------------------------------------+ |
| | CONTINUE | +-------------+ FALLBACK +-------------+ | |
| +-------------| *-->| AUTOMATIC |--------->| MANUAL | | |
| | | | | | | |
| | | | | FIX | | |
| | | | | +-----+ | | |
| | | | | | | | | |
| | | | | | v | | |
| | +-------------+ +-------------+ | |
| +-----------------------------------------------+ |
| |
+----------------------------------------------------------------------------+

View File

@@ -0,0 +1,179 @@
package demo.tasks;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.statemachine.EnumStateMachine;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineSystemConstants;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;
import demo.CommonConfiguration;
import demo.tasks.Application.Events;
import demo.tasks.Application.States;
public class TasksTests {
private AnnotationConfigApplicationContext context;
private StateMachine<States,Events> machine;
private Tasks tasks;
private TestListener listener;
@Test
public void testInitialState() throws InterruptedException {
assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS), is(true));
assertThat(listener.stateChangedCount, is(1));
assertThat(machine.getState().getIds(), contains(States.READY));
}
@Test
public void testRunOnce() throws InterruptedException {
listener.reset(3, 0, 0);
tasks.run();
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(machine.getState().getIds(), contains(States.READY));
}
@Test
public void testRunTwice() throws InterruptedException {
listener.reset(3, 0, 0);
tasks.run();
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(machine.getState().getIds(), contains(States.READY));
listener.reset(3, 0, 0);
tasks.run();
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(machine.getState().getIds(), contains(States.READY));
}
@Test
public void testFailAutomaticFix() throws InterruptedException {
listener.reset(11, 0, 0);
tasks.fail("T1");
tasks.run();
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(listener.stateChangedCount, is(11));
assertThat(machine.getState().getIds(), contains(States.READY));
}
@Test
public void testFailManualFix() throws InterruptedException {
listener.reset(3, 0, 0);
tasks.fail("T2");
tasks.run();
tasks.fix("T2");
tasks.cont();
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(machine.getState().getIds(), contains(States.READY));
}
@SuppressWarnings("unchecked")
@Before
public void setup() throws Exception {
context = new AnnotationConfigApplicationContext();
context.register(CommonConfiguration.class, Application.class, TestConfig.class);
context.refresh();
machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
tasks = context.getBean(Tasks.class);
listener = context.getBean(TestListener.class);
machine.start();
// lets do a little sleep to wait sm to start
Thread.sleep(1000);
}
@After
public void clean() {
machine.stop();
context.close();
context = null;
machine = null;
tasks = null;
listener = null;
}
static class TestConfig {
@Autowired
private StateMachine<States,Events> machine;
@Bean
public StateMachineListener<States, Events> stateMachineListener() {
TestListener listener = new TestListener();
machine.addStateListener(listener);
return listener;
}
}
static class TestListener extends StateMachineListenerAdapter<States, Events> {
volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
volatile CountDownLatch stateEnteredLatch = new CountDownLatch(2);
volatile CountDownLatch stateExitedLatch = new CountDownLatch(0);
volatile CountDownLatch transitionLatch = new CountDownLatch(0);
volatile int stateChangedCount = 0;
volatile int transitionCount = 0;
List<State<States, Events>> statesEntered = new ArrayList<State<States,Events>>();
List<State<States, Events>> statesExited = new ArrayList<State<States,Events>>();
@Override
public void stateChanged(State<States, Events> from, State<States, Events> to) {
stateChangedLatch.countDown();
stateChangedCount++;
}
@Override
public void stateEntered(State<States, Events> state) {
statesEntered.add(state);
stateEnteredLatch.countDown();
}
@Override
public void stateExited(State<States, Events> state) {
statesExited.add(state);
stateExitedLatch.countDown();
}
@Override
public void transitionEnded(Transition<States, Events> transition) {
transitionLatch.countDown();
transitionCount++;
}
public void reset(int c1, int c2, int c3) {
reset(c1, c2, c3, 0);
}
public void reset(int c1, int c2, int c3, int c4) {
stateChangedLatch = new CountDownLatch(c1);
stateEnteredLatch = new CountDownLatch(c2);
stateExitedLatch = new CountDownLatch(c3);
transitionLatch = new CountDownLatch(c4);
stateChangedCount = 0;
transitionCount = 0;
statesEntered.clear();
statesExited.clear();
}
}
}

View File

@@ -0,0 +1,8 @@
log4j.rootCategory=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2} [%t] - %m%n
log4j.category.org.springframework.statemachine=TRACE