From 178ee816a4db45827891fa196404f218eddb20ff Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sat, 25 Jul 2015 17:09:54 +0100 Subject: [PATCH] Enhancements to tasks recipe - Fix AbstractStateMachineFactory so that it does not try to create transitions with null source or target. Basically we just skip transition source or target which doesn't belong to region. - Change DefaultExtendedState to use ConcurrentHashMap so that there's less change to get into trouble with concurrency. - Add statechart for tasks recipe. - Fix various concepts in TasksHandler and its tests. - Prepare some examples for docs. - Fixes #74 --- .../reference/asciidoc/images/statechart9.png | Bin 0 -> 17134 bytes docs/src/statecharts/statechart9.txt | 46 ++++++ .../config/AbstractStateMachineFactory.java | 2 +- .../support/DefaultExtendedState.java | 4 +- .../recipes/tasks/TasksHandler.java | 111 +++++++++++++- .../recipes/TasksHandlerTests.java | 139 ++++++++++++----- .../recipes/docs/DocsTasksSampleTests.java | 143 ++++++++++++++++++ 7 files changed, 399 insertions(+), 46 deletions(-) create mode 100644 docs/src/reference/asciidoc/images/statechart9.png create mode 100644 docs/src/statecharts/statechart9.txt create mode 100644 spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/docs/DocsTasksSampleTests.java diff --git a/docs/src/reference/asciidoc/images/statechart9.png b/docs/src/reference/asciidoc/images/statechart9.png new file mode 100644 index 0000000000000000000000000000000000000000..cc83233812ff91289b0914e8378ce75dfee98efb GIT binary patch literal 17134 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 literal 0 HcmV?d00001 diff --git a/docs/src/statecharts/statechart9.txt b/docs/src/statecharts/statechart9.txt new file mode 100644 index 00000000..93e4c527 --- /dev/null +++ b/docs/src/statecharts/statechart9.txt @@ -0,0 +1,46 @@ ++----------------------------------------------------------------------------+ +| SM | ++----------------------------------------------------------------------------+ +| | +| +---------------------------+ | +| FORK | TASKS | JOIN | +| | +---------------------------+ | | +| +-------------+ RUN | | +-------------------+ | | | +| *-->| READY |----->|----->| *-->| TASK_id_INITIAL | |---->|----+ | +| +-------------+ | | +-------------------+ | | | | +| ^ ^ | | | | | | | +| | | | v | | | +| | | | +-------------------+ | | | +| | | | | TASK_id | | | | +| | | | +-------------------+ | | | +| | | |===========================| | | +| | | | +-------------------+ | | | +| | | | *-->| TASK_id_INITIAL | | | | +| | | | +-------------------+ | | | +| | | | | | | | +| | | | v | | | +| | | | +-------------------+ | | | +| | | | | TASK_id | | | | +| | | | +-------------------+ | | | +| | | +---------------------------+ | | +| | | | | +| | | [OK] +------------+ | | +| | +--------------------------| CHOICE |<----------------+ | +| | +------------+ | +| | | | +| | | [ERROR] | +| | v | +| | +-----------------------------------------------+ | +| | | ERROR | | +| | +-----------------------------------------------+ | +| | CONTINUE | +-------------+ FALLBACK +-------------+ | | +| +-------------| *-->| AUTOMATIC |--------->| MANUAL | | | +| | | | | | | | +| | | | | FIX | | | +| | | | | +-----+ | | | +| | | | | | | | | | +| | | | | | v | | | +| | +-------------+ +-------------+ | | +| +-----------------------------------------------+ | +| | ++----------------------------------------------------------------------------+ diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java index 7eb48f9a..a6975558 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java @@ -510,7 +510,7 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS if (transitionData.getKind() == TransitionKind.EXTERNAL) { // TODO can we do this? - if (stateMap.get(source) == null && stateMap.get(target) == null) { + if (stateMap.get(source) == null || stateMap.get(target) == null) { continue; } DefaultExternalTransition transition = new DefaultExternalTransition(stateMap.get(source), diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java index 51537e2f..d3535bfa 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java @@ -15,8 +15,8 @@ */ package org.springframework.statemachine.support; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.statemachine.ExtendedState; @@ -34,7 +34,7 @@ public class DefaultExtendedState implements ExtendedState { * Instantiates a new default extended state. */ public DefaultExtendedState() { - this.variables = new HashMap(); + this.variables = new ConcurrentHashMap(); } /** diff --git a/spring-statemachine-recipes/src/main/java/org/springframework/statemachine/recipes/tasks/TasksHandler.java b/spring-statemachine-recipes/src/main/java/org/springframework/statemachine/recipes/tasks/TasksHandler.java index 86664093..ab0dcb28 100644 --- a/spring-statemachine-recipes/src/main/java/org/springframework/statemachine/recipes/tasks/TasksHandler.java +++ b/spring-statemachine-recipes/src/main/java/org/springframework/statemachine/recipes/tasks/TasksHandler.java @@ -151,6 +151,23 @@ public class TasksHandler { return new Builder(); } + /** + * Mark all extended state variables related to tasks fixed. + */ + public void markAllTasksFixed() { + Map variables = getStateMachine().getExtendedState().getVariables(); + for (Entry entry : variables.entrySet()) { + if (entry.getKey() instanceof String && ((String)entry.getKey()).startsWith(STATE_TASKS_PREFIX)) { + if (entry.getValue() instanceof Integer) { + Integer value = (Integer) entry.getValue(); + if (value < 0) { + variables.put(entry.getKey(), 0); + } + } + } + } + } + private StateMachine buildStateMachine(List tasks, TaskExecutor taskExecutor) throws Exception { StateMachineBuilder.Builder builder = StateMachineBuilder.builder(); @@ -201,6 +218,7 @@ public class TasksHandler { stateMachineTransitionConfigurer .withExternal() + .state(parent) .source(initial) .target(task); } @@ -425,11 +443,40 @@ public class TasksHandler { }; } + /** + * {@link Action} calls {@link TasksListener#onTasksAutomaticFix(StateContext)} + * before checking status of extended state variables related to tasks. If all + * variables are ok, event {@code EVENT_CONTINUE} is sent, otherwise event + * {@code EVENT_FALLBACK} is send which takes state machine into a manual handling. + * + * @return the action + */ private Action automaticAction() { return new Action() { @Override public void execute(StateContext context) { + + listener.onTasksAutomaticFix(TasksHandler.this, context); + + boolean hasErrors = false; + Map variables = context.getExtendedState().getVariables(); + for (Entry entry : variables.entrySet()) { + if (entry.getKey() instanceof String && ((String)entry.getKey()).startsWith(STATE_TASKS_PREFIX)) { + if (entry.getValue() instanceof Integer) { + Integer value = (Integer) entry.getValue(); + if (value < 0) { + hasErrors = true; + break; + } + } + } + } + if (hasErrors) { + context.getStateMachine().sendEvent(EVENT_FALLBACK); + } else { + context.getStateMachine().sendEvent(EVENT_CONTINUE); + } } }; } @@ -451,7 +498,7 @@ public class TasksHandler { if (entry.getValue() instanceof Integer) { Integer value = (Integer) entry.getValue(); if (value < 0) { - variables.put(entry.getValue(), 0); + variables.put(entry.getKey(), 0); } } } @@ -460,6 +507,49 @@ public class TasksHandler { }; } + /** + * Adapter class for {@link TasksListener}. + */ + public static class TasksListenerAdapter implements TasksListener { + + @Override + public void onTasksStarted() { + } + + @Override + public void onTasksContinue() { + } + + @Override + public void onTaskPreExecute(Object id) { + } + + @Override + public void onTaskPostExecute(Object id) { + } + + @Override + public void onTaskFailed(Object id, Exception exception) { + } + + @Override + public void onTaskSuccess(Object id) { + } + + @Override + public void onTasksSuccess() { + } + + @Override + public void onTasksError() { + } + + @Override + public void onTasksAutomaticFix(TasksHandler handler, StateContext context) { + } + + } + /** * {@code TasksListener} is a generic interface listening tasks * execution events. Methods in this interface will be called in a @@ -520,6 +610,16 @@ public class TasksHandler { * tasks executed with an error. */ void onTasksError(); + + /** + * Called when tasks execution resulted an error and AUTOMATIC state + * is entered. This is a moment where extended state variables can be + * modified to allow continue into a READY state. + * + * @param handler the tasks handler + * @param context the state context + */ + void onTasksAutomaticFix(TasksHandler handler, StateContext context); } private class CompositeTasksListener extends AbstractCompositeListener implements @@ -581,6 +681,13 @@ public class TasksHandler { } } + @Override + public void onTasksAutomaticFix(TasksHandler handler, StateContext context) { + for (Iterator iterator = getListeners().reverse(); iterator.hasNext();) { + iterator.next().onTasksAutomaticFix(handler, context); + } + } + } /** @@ -613,7 +720,7 @@ public class TasksHandler { } /** - * {@link Action} which is execution with every registered {@link Runnable}. + * {@link Action} which is executed with every registered {@link Runnable}. */ private class LocalRunnableAction extends RunnableAction { diff --git a/spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/TasksHandlerTests.java b/spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/TasksHandlerTests.java index 412bc3a4..7aeb4c4e 100644 --- a/spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/TasksHandlerTests.java +++ b/spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/TasksHandlerTests.java @@ -26,10 +26,11 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Test; +import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.statemachine.recipes.tasks.TasksHandler; -import org.springframework.statemachine.recipes.tasks.TasksHandler.TasksListener; +import org.springframework.statemachine.recipes.tasks.TasksHandler.TasksListenerAdapter; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; @@ -84,7 +85,7 @@ public class TasksHandlerTests { } @Test - public void testRunFailAndContinue() throws InterruptedException { + public void testRunFailAndFixAndContinue() throws InterruptedException { TasksHandler handler = TasksHandler.builder() .task("1", sleepRunnable()) .task("2", sleepRunnable()) @@ -92,7 +93,7 @@ public class TasksHandlerTests { .build(); TestListener listener = new TestListener(); - listener.reset(11, 0, 0); + listener.reset(12, 0, 0); StateMachine machine = handler.getStateMachine(); machine.addStateListener(listener); machine.start(); @@ -101,8 +102,8 @@ public class TasksHandlerTests { handler.runTasks(); assertThat(listener.stateChangedLatch.await(8, TimeUnit.SECONDS), is(true)); - assertThat(listener.stateChangedCount, is(11)); - assertThat(machine.getState().getIds(), contains(TasksHandler.STATE_ERROR, TasksHandler.STATE_AUTOMATIC)); + assertThat(listener.stateChangedCount, is(12)); + assertThat(machine.getState().getIds(), contains(TasksHandler.STATE_ERROR, TasksHandler.STATE_MANUAL)); handler.fixCurrentProblems(); handler.continueFromError(); @@ -112,6 +113,32 @@ public class TasksHandlerTests { assertThat(machine.getState().getIds(), contains(TasksHandler.STATE_READY)); } + @Test + public void testRunFailAndAutomaticFix() throws InterruptedException { + TasksHandler handler = TasksHandler.builder() + .task("1", sleepRunnable()) + .task("2", sleepRunnable()) + .task("3", failRunnable()) + .build(); + + TestTasksListener tasksListener = new TestTasksListener(); + tasksListener.fix = true; + handler.addTasksListener(tasksListener); + + TestListener listener = new TestListener(); + listener.reset(12, 0, 0); + StateMachine machine = handler.getStateMachine(); + machine.addStateListener(listener); + machine.start(); + assertThat(listener.stateMachineStartedLatch.await(1, TimeUnit.SECONDS), is(true)); + + handler.runTasks(); + + assertThat(listener.stateChangedLatch.await(8, TimeUnit.SECONDS), is(true)); + assertThat(listener.stateChangedCount, is(12)); + assertThat(machine.getState().getIds(), contains(TasksHandler.STATE_READY)); + } + @Test public void testDagSingleRoot() throws InterruptedException { TasksHandler handler = TasksHandler.builder() @@ -249,7 +276,7 @@ public class TasksHandlerTests { .build(); TestListener listener = new TestListener(); - listener.reset(11, 0, 0); + listener.reset(12, 0, 0); StateMachine machine = handler.getStateMachine(); machine.addStateListener(listener); machine.start(); @@ -259,8 +286,8 @@ public class TasksHandlerTests { handler.runTasks(); assertThat(listener.stateChangedLatch.await(8, TimeUnit.SECONDS), is(true)); - assertThat(listener.stateChangedCount, is(11)); - assertThat(machine.getState().getIds(), contains(TasksHandler.STATE_ERROR, TasksHandler.STATE_AUTOMATIC)); + assertThat(listener.stateChangedCount, is(12)); + assertThat(machine.getState().getIds(), contains(TasksHandler.STATE_ERROR, TasksHandler.STATE_MANUAL)); listener.reset(1, 0, 0); handler.fixCurrentProblems(); @@ -354,7 +381,9 @@ public class TasksHandlerTests { } - private class TestTasksListener implements TasksListener { + private static class TestTasksListener extends TasksListenerAdapter { + + final Object lock = new Object(); volatile CountDownLatch onTasksStartedLatch = new CountDownLatch(1); volatile CountDownLatch onTasksContinueLatch = new CountDownLatch(1); @@ -374,71 +403,99 @@ public class TasksHandlerTests { volatile int onTasksSuccess; volatile int onTasksError; + volatile boolean fix = false; + @Override public void onTasksStarted() { - onTasksStarted++; - onTasksStartedLatch.countDown(); + synchronized (lock) { + onTasksStarted++; + onTasksStartedLatch.countDown(); + } } @Override public void onTasksContinue() { - onTasksContinue++; - onTasksContinueLatch.countDown(); + synchronized (lock) { + onTasksContinue++; + onTasksContinueLatch.countDown(); + } } @Override public void onTaskPreExecute(Object id) { - onTaskPreExecute++; - onTaskPreExecuteLatch.countDown(); + synchronized (lock) { + onTaskPreExecute++; + onTaskPreExecuteLatch.countDown(); + } } @Override public void onTaskPostExecute(Object id) { - onTaskPostExecute++; - onTaskPostExecuteLatch.countDown(); + synchronized (lock) { + onTaskPostExecute++; + onTaskPostExecuteLatch.countDown(); + } } @Override public void onTaskFailed(Object id, Exception exception) { - onTaskFailed++; - onTaskFailedLatch.countDown(); + synchronized (lock) { + onTaskFailed++; + onTaskFailedLatch.countDown(); + } } @Override public void onTaskSuccess(Object id) { - onTaskSuccess++; - onTaskSuccessLatch.countDown(); + synchronized (lock) { + onTaskSuccess++; + onTaskSuccessLatch.countDown(); + } } @Override public void onTasksSuccess() { - onTasksSuccess++; - onTasksSuccessLatch.countDown(); + synchronized (lock) { + onTasksSuccess++; + onTasksSuccessLatch.countDown(); + } } @Override public void onTasksError() { - onTasksError++; - onTasksErrorLatch.countDown(); + synchronized (lock) { + onTasksError++; + onTasksErrorLatch.countDown(); + } + } + + @Override + public void onTasksAutomaticFix(TasksHandler handler, StateContext context) { + if (!fix) { + return; + } + handler.markAllTasksFixed(); } public void reset(int c1, int c2, int c3, int c4, int c5, int c6, int c7, int c8) { - onTasksStartedLatch = new CountDownLatch(c1); - onTasksContinueLatch = new CountDownLatch(c2); - onTaskPreExecuteLatch = new CountDownLatch(c3); - onTaskPostExecuteLatch = new CountDownLatch(c4); - onTaskFailedLatch = new CountDownLatch(c5); - onTaskSuccessLatch = new CountDownLatch(c6); - onTasksSuccessLatch = new CountDownLatch(c7); - onTasksErrorLatch = new CountDownLatch(c8); - onTasksStarted = 0; - onTasksContinue = 0; - onTaskPreExecute = 0; - onTaskPostExecute = 0; - onTaskFailed = 0; - onTaskSuccess = 0; - onTasksSuccess = 0; - onTasksError = 0; + synchronized (lock) { + onTasksStartedLatch = new CountDownLatch(c1); + onTasksContinueLatch = new CountDownLatch(c2); + onTaskPreExecuteLatch = new CountDownLatch(c3); + onTaskPostExecuteLatch = new CountDownLatch(c4); + onTaskFailedLatch = new CountDownLatch(c5); + onTaskSuccessLatch = new CountDownLatch(c6); + onTasksSuccessLatch = new CountDownLatch(c7); + onTasksErrorLatch = new CountDownLatch(c8); + onTasksStarted = 0; + onTasksContinue = 0; + onTaskPreExecute = 0; + onTaskPostExecute = 0; + onTaskFailed = 0; + onTaskSuccess = 0; + onTasksSuccess = 0; + onTasksError = 0; + } } } diff --git a/spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/docs/DocsTasksSampleTests.java b/spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/docs/DocsTasksSampleTests.java new file mode 100644 index 00000000..bd10f386 --- /dev/null +++ b/spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/docs/DocsTasksSampleTests.java @@ -0,0 +1,143 @@ +/* + * Copyright 2015 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.recipes.docs; + +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.recipes.tasks.TasksHandler; +import org.springframework.statemachine.recipes.tasks.TasksHandler.TasksListenerAdapter; + +public class DocsTasksSampleTests { + + public void sample1() { +// tag::snippetB[] + TasksHandler handler = TasksHandler.builder() + .task("1", sleepRunnable()) + .task("2", sleepRunnable()) + .task("3", sleepRunnable()) + .build(); + + handler.runTasks(); +// end::snippetB[] + } + + public void sample2() { +// tag::snippetC[] + MyTasksListener listener1 = new MyTasksListener(); + MyTasksListener listener2 = new MyTasksListener(); + + TasksHandler handler = TasksHandler.builder() + .task("1", sleepRunnable()) + .task("2", sleepRunnable()) + .task("3", sleepRunnable()) + .listener(listener1) + .build(); + + handler.addTasksListener(listener2); + handler.removeTasksListener(listener2); + + handler.runTasks(); +// end::snippetC[] + } + + public void sample3() { +// tag::snippetD[] + TasksHandler handler = TasksHandler.builder() + .task("1", sleepRunnable()) + .task("1", "12", sleepRunnable()) + .task("1", "13", sleepRunnable()) + .task("2", sleepRunnable()) + .task("2", "22", sleepRunnable()) + .task("2", "23", sleepRunnable()) + .task("3", sleepRunnable()) + .task("3", "32", sleepRunnable()) + .task("3", "33", sleepRunnable()) + .build(); + + handler.runTasks(); +// end::snippetD[] + } + + public void sample4() { +// tag::snippetE[] + TasksHandler handler = TasksHandler.builder() + .task("1", sleepRunnable()) + .task("2", sleepRunnable()) + .task("3", sleepRunnable()) + .build(); + + handler.runTasks(); + handler.fixCurrentProblems(); + handler.continueFromError(); +// end::snippetE[] + } + +// tag::snippetAA[] + private static Runnable sleepRunnable() { + return new Runnable() { + + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + } + } + }; + } +// end::snippetAA[] + +// tag::snippetAB[] + private static class MyTasksListener extends TasksListenerAdapter { + + @Override + public void onTasksStarted() { + } + + @Override + public void onTasksContinue() { + } + + @Override + public void onTaskPreExecute(Object id) { + } + + @Override + public void onTaskPostExecute(Object id) { + } + + @Override + public void onTaskFailed(Object id, Exception exception) { + } + + @Override + public void onTaskSuccess(Object id) { + } + + @Override + public void onTasksSuccess() { + } + + @Override + public void onTasksError() { + } + + @Override + public void onTasksAutomaticFix(TasksHandler handler, StateContext context) { + } + } +// end::snippetAB[] + +}