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
This commit is contained in:
Janne Valkealahti
2015-07-05 15:43:04 +01:00
parent bba4bc1d28
commit 9c67299a35
10 changed files with 181 additions and 4 deletions

View File

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

View File

@@ -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 + "]";
}
}

View File

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

View File

@@ -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<S,E> extends AbstractCompositeListene
}
}
@Override
public void eventNotAccepted(Message<E> event) {
for (Iterator<StateMachineListener<S, E>> iterator = getListeners().reverse(); iterator.hasNext();) {
StateMachineListener<S, E> listener = iterator.next();
listener.eventNotAccepted(event);
}
}
@Override
public void transition(Transition<S, E> transition) {
for (Iterator<StateMachineListener<S, E>> iterator = getListeners().reverse(); iterator.hasNext();) {

View File

@@ -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<S,E> {
*/
void stateExited(State<S,E> state);
/**
* Notified when event was not accepted.
*
* @param event the event
*/
void eventNotAccepted(Message<E> event);
/**
* Notified when transition happened.
*

View File

@@ -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<S, E> implements StateMachineListener<S
public void stateExited(State<S, E> state) {
}
@Override
public void eventNotAccepted(Message<E> event) {
}
@Override
public void transition(Transition<S, E> transition) {
}

View File

@@ -182,10 +182,14 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
@Override
public boolean sendEvent(Message<E> event) {
if (isComplete() || !isRunning()) {
notifyEventNotAccepted(event);
return false;
}
boolean accepted = acceptEvent(event);
stateMachineExecutor.execute();
if (!accepted) {
notifyEventNotAccepted(event);
}
return accepted;
}

View File

@@ -125,6 +125,16 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyEventNotAccepted(Message<E> event) {
stateListener.eventNotAccepted(event);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
if (eventPublisher != null) {
eventPublisher.publishEventNotAccepted(this, event);
}
}
}
protected void notifyTransitionStart(Transition<S,E> transition) {
stateListener.transitionStarted(transition);
if (contextEventsEnabled) {
@@ -217,6 +227,11 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
stateListener.stateExited(state);
}
@Override
public void eventNotAccepted(Message<E> event) {
stateListener.eventNotAccepted(event);
}
@Override
public void transition(Transition<S, E> transition) {
stateListener.transition(transition);

View File

@@ -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<TestStates,TestEvents> 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<StateMachineEvent> {
CountDownLatch onEventLatch = new CountDownLatch(6);
volatile CountDownLatch onEventLatch = new CountDownLatch(6);
ArrayList<StateMachineEvent> events = new ArrayList<StateMachineEvent>();
volatile ArrayList<StateMachineEvent> stateChangedEvents = new ArrayList<StateMachineEvent>();
volatile ArrayList<StateMachineEvent> eventNotAcceptedEvents = new ArrayList<StateMachineEvent>();
@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<StateMachineEvent>();
stateChangedEvents.clear();
eventNotAcceptedEvents.clear();
}
}
static class TestListener extends StateMachineListenerAdapter<TestStates, TestEvents> {
volatile CountDownLatch eventNotAcceptedLatch = new CountDownLatch(1);
volatile ArrayList<Message<TestEvents>> eventNotAccepted = new ArrayList<Message<TestEvents>>();
@Override
public void eventNotAccepted(Message<TestEvents> event) {
eventNotAccepted.add(event);
eventNotAcceptedLatch.countDown();
}
}
}

View File

@@ -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<TestEvents> event) {
}
@Override
public void transition(Transition<TestStates, TestEvents> transition) {
}