From 9c67299a351cf5da63fec1b8f2c1440573e4ffd8 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sun, 5 Jul 2015 15:43:04 +0100 Subject: [PATCH] Add event if event is not accepted - OnStateNotAcceptedEvent is fired via context if event is not accepted. - eventNotAccepted called via StateMachineListener if event is not accepted. - Test in StateMachineEventTests.testEventNotAccepted() - Fixes #43 --- .../DefaultStateMachineEventPublisher.java | 8 +++ .../event/OnEventNotAcceptedEvent.java | 57 ++++++++++++++++ .../event/StateMachineEventPublisher.java | 9 +++ .../CompositeStateMachineListener.java | 9 +++ .../listener/StateMachineListener.java | 8 +++ .../listener/StateMachineListenerAdapter.java | 5 ++ .../support/AbstractStateMachine.java | 4 ++ .../support/StateMachineObjectSupport.java | 15 +++++ .../event/StateMachineEventTests.java | 65 +++++++++++++++++-- .../statemachine/listener/ListenerTests.java | 5 ++ 10 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/event/OnEventNotAcceptedEvent.java diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/DefaultStateMachineEventPublisher.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/DefaultStateMachineEventPublisher.java index f56a1dd9..2ee2efd2 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/DefaultStateMachineEventPublisher.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/DefaultStateMachineEventPublisher.java @@ -17,6 +17,7 @@ package org.springframework.statemachine.event; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.messaging.Message; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; @@ -72,6 +73,13 @@ public class DefaultStateMachineEventPublisher implements StateMachineEventPubli } } + @Override + public void publishEventNotAccepted(Object source, Message event) { + if (applicationEventPublisher != null) { + applicationEventPublisher.publishEvent(new OnEventNotAcceptedEvent(source, event)); + } + } + @Override public void publishTransitionStart(Object source, Transition transition) { if (applicationEventPublisher != null) { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/OnEventNotAcceptedEvent.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/OnEventNotAcceptedEvent.java new file mode 100644 index 00000000..6166641f --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/OnEventNotAcceptedEvent.java @@ -0,0 +1,57 @@ +/* + * 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.event; + +import org.springframework.messaging.Message; + +/** + * Generic event representing that a state machine did not + * accept an event. + * + * @author Janne Valkealahti + * + */ +@SuppressWarnings("serial") +public class OnEventNotAcceptedEvent extends StateMachineEvent { + + private final Message event; + + /** + * Instantiates a new on state not accepted event. + * + * @param source the source + * @param event the event + */ + public OnEventNotAcceptedEvent(Object source, Message event) { + super(source); + this.event = event; + } + + /** + * Gets the event. + * + * @return the event + */ + public Message getEvent() { + return event; + } + + @Override + public String toString() { + return "OnStateNotAcceptedEvent [event=" + event + "]"; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/StateMachineEventPublisher.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/StateMachineEventPublisher.java index 670c27af..33d5ca20 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/StateMachineEventPublisher.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/StateMachineEventPublisher.java @@ -15,6 +15,7 @@ */ package org.springframework.statemachine.event; +import org.springframework.messaging.Message; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; @@ -52,6 +53,14 @@ public interface StateMachineEventPublisher { */ void publishStateExited(Object source, State state); + /** + * Publish event not accepted event. + * + * @param source the source + * @param event the event + */ + void publishEventNotAccepted(Object source, Message event); + /** * Publish a transition start event. * diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java index b6e37bce..d45a9c0e 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java @@ -17,6 +17,7 @@ package org.springframework.statemachine.listener; import java.util.Iterator; +import org.springframework.messaging.Message; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; @@ -56,6 +57,14 @@ public class CompositeStateMachineListener extends AbstractCompositeListene } } + @Override + public void eventNotAccepted(Message event) { + for (Iterator> iterator = getListeners().reverse(); iterator.hasNext();) { + StateMachineListener listener = iterator.next(); + listener.eventNotAccepted(event); + } + } + @Override public void transition(Transition transition) { for (Iterator> iterator = getListeners().reverse(); iterator.hasNext();) { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java index 74038adf..c026fdb1 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java @@ -15,6 +15,7 @@ */ package org.springframework.statemachine.listener; +import org.springframework.messaging.Message; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; @@ -51,6 +52,13 @@ public interface StateMachineListener { */ void stateExited(State state); + /** + * Notified when event was not accepted. + * + * @param event the event + */ + void eventNotAccepted(Message event); + /** * Notified when transition happened. * diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListenerAdapter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListenerAdapter.java index ba49ba3b..7de85f75 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListenerAdapter.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListenerAdapter.java @@ -15,6 +15,7 @@ */ package org.springframework.statemachine.listener; +import org.springframework.messaging.Message; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; @@ -42,6 +43,10 @@ public class StateMachineListenerAdapter implements StateMachineListener state) { } + @Override + public void eventNotAccepted(Message event) { + } + @Override public void transition(Transition transition) { } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java index 93a7e436..e61da32f 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -182,10 +182,14 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo @Override public boolean sendEvent(Message event) { if (isComplete() || !isRunning()) { + notifyEventNotAccepted(event); return false; } boolean accepted = acceptEvent(event); stateMachineExecutor.execute(); + if (!accepted) { + notifyEventNotAccepted(event); + } return accepted; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java index a24e1a99..601e4205 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java @@ -125,6 +125,16 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup } } + protected void notifyEventNotAccepted(Message event) { + stateListener.eventNotAccepted(event); + if (contextEventsEnabled) { + StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); + if (eventPublisher != null) { + eventPublisher.publishEventNotAccepted(this, event); + } + } + } + protected void notifyTransitionStart(Transition transition) { stateListener.transitionStarted(transition); if (contextEventsEnabled) { @@ -217,6 +227,11 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup stateListener.stateExited(state); } + @Override + public void eventNotAccepted(Message event) { + stateListener.eventNotAccepted(event); + } + @Override public void transition(Transition transition) { stateListener.transition(transition); diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/StateMachineEventTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/StateMachineEventTests.java index b8cfdcf7..70d4c513 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/StateMachineEventTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/StateMachineEventTests.java @@ -16,6 +16,7 @@ package org.springframework.statemachine.event; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; @@ -31,6 +32,7 @@ import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.Message; import org.springframework.statemachine.AbstractStateMachineTests; import org.springframework.statemachine.ObjectStateMachine; import org.springframework.statemachine.StateMachineSystemConstants; @@ -38,6 +40,7 @@ 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.listener.StateMachineListenerAdapter; /** * Tests from state machine app context events. @@ -72,7 +75,37 @@ public class StateMachineEventTests extends AbstractStateMachineTests { // 6 events instead of 5, first one is initial transition // to SI where source state is null assertThat(listener.onEventLatch.await(5, TimeUnit.SECONDS), is(true)); - assertThat(listener.events.size(), is(6)); + assertThat(listener.stateChangedEvents.size(), is(6)); + } + + @Test + public void testEventNotAccepted() throws Exception { + context.register(BaseConfig.class, StateMachineEventPublisherConfiguration.class, Config1.class); + context.refresh(); + assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + TestEventListener eventListener = context.getBean(TestEventListener.class); + TestListener listener = new TestListener(); + @SuppressWarnings("unchecked") + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + eventListener.reset(2, 0); + machine.addStateListener(listener); + machine.start(); + assertThat(machine, notNullValue()); + machine.sendEvent(TestEvents.E1); + + assertThat(eventListener.onEventLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(eventListener.stateChangedEvents.size(), is(2)); + + eventListener.reset(0, 1); + machine.sendEvent(TestEvents.E3); + assertThat(eventListener.onEventLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(eventListener.eventNotAcceptedEvents.size(), is(1)); + assertThat(eventListener.eventNotAcceptedEvents.get(0), instanceOf(OnEventNotAcceptedEvent.class)); + assertThat(((OnEventNotAcceptedEvent)eventListener.eventNotAcceptedEvents.get(0)).getEvent().getPayload(), is(TestEvents.E3)); + + assertThat(listener.eventNotAcceptedLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(listener.eventNotAccepted.size(), is(1)); } @Test @@ -185,18 +218,42 @@ public class StateMachineEventTests extends AbstractStateMachineTests { static class TestEventListener implements ApplicationListener { - CountDownLatch onEventLatch = new CountDownLatch(6); + volatile CountDownLatch onEventLatch = new CountDownLatch(6); - ArrayList events = new ArrayList(); + volatile ArrayList stateChangedEvents = new ArrayList(); + volatile ArrayList eventNotAcceptedEvents = new ArrayList(); @Override public void onApplicationEvent(StateMachineEvent event) { if (event instanceof OnStateChangedEvent) { - events.add(event); + stateChangedEvents.add(event); + onEventLatch.countDown(); + } else if (event instanceof OnEventNotAcceptedEvent) { + eventNotAcceptedEvents.add(event); onEventLatch.countDown(); } } + public void reset(int c1,int c2) { + onEventLatch = new CountDownLatch(c1); + eventNotAcceptedEvents = new ArrayList(); + stateChangedEvents.clear(); + eventNotAcceptedEvents.clear(); + } + + } + + static class TestListener extends StateMachineListenerAdapter { + + volatile CountDownLatch eventNotAcceptedLatch = new CountDownLatch(1); + volatile ArrayList> eventNotAccepted = new ArrayList>(); + + @Override + public void eventNotAccepted(Message event) { + eventNotAccepted.add(event); + eventNotAcceptedLatch.countDown(); + } + } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java index dcbcc752..0fc53293 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java @@ -33,6 +33,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; +import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import org.springframework.statemachine.AbstractStateMachineTests; import org.springframework.statemachine.ObjectStateMachine; @@ -148,6 +149,10 @@ public class ListenerTests extends AbstractStateMachineTests { } } + @Override + public void eventNotAccepted(Message event) { + } + @Override public void transition(Transition transition) { }