Overhaul annotation and listener processing
- This is stage 2 for these changes. Relates to #126 and #138. - Better support for other stuff available from a state context. - Started to add docs. - Polish.
This commit is contained in:
@@ -771,15 +771,80 @@ working with. For this specific use case we have made a spring style
|
||||
context integration which easily attach state machine functionality
|
||||
into your beans.
|
||||
|
||||
=== Annotation Support
|
||||
Available annotations has been harmonised to enable access to same
|
||||
state machine execution points than what is available from
|
||||
<<sm-listeners>>.
|
||||
|
||||
_@WithStateMachine_ annotation can be used to associate a state
|
||||
machine with a existing bean. Within this annotation a property's
|
||||
machine with an existing bean. Then it is possible to start adding
|
||||
supported annotations to methods of that bean.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetA]
|
||||
----
|
||||
|
||||
It is also possible to attach to any other state machine from an
|
||||
application context by using annotation `name` field.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetAA]
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Return type of these methods doesn't matter and is effectively
|
||||
discard.
|
||||
====
|
||||
|
||||
=== Method Parameters
|
||||
Every annotation is supporting exactly same set of possible method
|
||||
parameters but runtime behaviour is different depending on an
|
||||
annotation itself and a stage where annotated method is called. To
|
||||
better understand how context works see
|
||||
<<sm-statecontext>>.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
For differences between method parameters, see individual annotation
|
||||
docs below.
|
||||
====
|
||||
|
||||
Effectively all annotated methods are called using Spring SPel
|
||||
expressions which are build dynamically during the process. As to make
|
||||
this work these expressions needs to have a root object it evaluates
|
||||
against. This root object is a `StateContext` and we have also made some
|
||||
tweaks internally so that it is possible to access `StateContext` methods
|
||||
directly without going through the context handle.
|
||||
|
||||
Simplest method parameter would naturally be a `StateContext` itself.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetB]
|
||||
----
|
||||
|
||||
Rest of the `StateContext` content can be accessed as shown below.
|
||||
Number of parameters or order of those doesn't matter.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetBB]
|
||||
----
|
||||
|
||||
=== Transition Annotations
|
||||
Annotations for transitions are `OnTransition`, `OnTransitionStart`
|
||||
and `OnTransitionEnd`.
|
||||
|
||||
These annotations behave exactly same and lets
|
||||
see how `OnTransition` is used. Within this annotation a property's
|
||||
_source_ and _target_ can be used to qualify a transition. If
|
||||
_source_ and _target_ is left empty then any transition is matched.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests.java[tags=snippetI]
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetC]
|
||||
----
|
||||
|
||||
Default _@OnTransition_ annotation can't be used with a state and
|
||||
@@ -792,7 +857,7 @@ is then called automatically with these arguments.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests.java[tags=snippetII]
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetD]
|
||||
----
|
||||
|
||||
However if you want to have a type safe annotation it is possible to
|
||||
@@ -802,7 +867,7 @@ events enums and framework will try to match these in a same way.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests.java[tags=snippetJ]
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetE]
|
||||
----
|
||||
|
||||
Above we created a _@StatesOnTransition_ annotation which defines
|
||||
@@ -810,12 +875,88 @@ Above we created a _@StatesOnTransition_ annotation which defines
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests.java[tags=snippetK]
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetF]
|
||||
----
|
||||
|
||||
In your own bean you can then use this _@StatesOnTransition_ as is and
|
||||
use type safe `source` and `target`.
|
||||
|
||||
=== State Annotations
|
||||
Annotations for states are `OnStateChanged`, `OnStateEntry` and
|
||||
`OnStateExit`.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetG]
|
||||
----
|
||||
|
||||
In a same way that in transition anotations it's possible to define
|
||||
target and source states.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetGG]
|
||||
----
|
||||
|
||||
For type safety a new annotation needs to be created for enums with
|
||||
`OnStateChanged` as a meta annotation.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetGGG]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetGGGG]
|
||||
----
|
||||
|
||||
Methods for state entry and exit behave in a same way.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetGGGGG]
|
||||
----
|
||||
|
||||
=== Event Annotation
|
||||
There is one event related annotation named `OnEventNotAccepted`. It
|
||||
is possible to listen only specific event by defining `event` property
|
||||
with the annotation.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetH]
|
||||
----
|
||||
|
||||
=== State Machine Annotations
|
||||
Annotations for state machine are `OnStateMachineStart`,
|
||||
`OnStateMachineStop` and `OnStateMachineError`.
|
||||
|
||||
During a state machine start and stop lifecycle methods are called.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetI]
|
||||
----
|
||||
|
||||
In case a state machine goes into an error with exception, below
|
||||
annotation is called.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetII]
|
||||
----
|
||||
|
||||
=== Extended State Annotation
|
||||
There is one extended state related annotation named
|
||||
`OnExtendedStateChanged`. It's also possible to listen changes only
|
||||
for specific `key` changes.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests4.java[tags=snippetJ]
|
||||
----
|
||||
|
||||
[[sm-accessor]]
|
||||
== State Machine Accessor
|
||||
`StateMachine` is a main interface to communicate with a state machine
|
||||
|
||||
@@ -27,10 +27,10 @@ import org.springframework.statemachine.ExtendedState;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* Indicates that a method is candidate to be called with a {@link Transition}.
|
||||
* Indicates that a method is a candidate to be called with a {@link Transition}.
|
||||
* <p>
|
||||
* A method annotated with @OnTransition may accept a parameter of type
|
||||
* {@link ExtendedState} or {@link Map} if map argument is itself is annotated
|
||||
* {@link ExtendedState} or {@link Map} if map argument itself is annotated
|
||||
* with {@link EventHeaders}.
|
||||
* <p>
|
||||
* Return value can be anything and is effectively discarded.
|
||||
|
||||
@@ -156,7 +156,14 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
|
||||
return;
|
||||
}
|
||||
for (CacheEntry entry : list) {
|
||||
handlersList.add(entry.handler);
|
||||
E event = stateContext.getEvent();
|
||||
if (event != null) {
|
||||
if (annotationHandlerEventVariableMatch(entry.metaAnnotation, new String[]{event.toString()})) {
|
||||
handlersList.add(entry.handler);
|
||||
}
|
||||
} else {
|
||||
handlersList.add(entry.handler);
|
||||
}
|
||||
}
|
||||
getStateMachineHandlerResults(handlersList, stateContext);
|
||||
}
|
||||
@@ -282,6 +289,21 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
|
||||
return handle;
|
||||
}
|
||||
|
||||
private boolean annotationHandlerEventVariableMatch(Annotation annotation, Object key) {
|
||||
boolean handle = false;
|
||||
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation);
|
||||
Object object = annotationAttributes.get("event");
|
||||
Collection<String> scoll = StateMachineUtils.toStringCollection(object);
|
||||
if (!scoll.isEmpty()) {
|
||||
if (StateMachineUtils.containsAtleastOne(scoll, StateMachineUtils.toStringCollection(key))) {
|
||||
handle = true;
|
||||
}
|
||||
} else {
|
||||
handle = true;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
private boolean annotationHandlerSourceTargetMatch(String[] msources, String[] mtargets, Annotation methodAnnotation,
|
||||
State<S, E> sourceState, State<S, E> targetState) {
|
||||
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(methodAnnotation);
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.statemachine.ExtendedState;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
@@ -441,10 +442,16 @@ public class StateMachineMethodInvokerHelper<T, S, E> extends AbstractExpression
|
||||
sb.append("variables.get('" + key + "')");
|
||||
}
|
||||
|
||||
} else if (StateContext.class.isAssignableFrom(parameterType)) {
|
||||
sb.append("stateContext");
|
||||
} else if (ExtendedState.class.isAssignableFrom(parameterType)) {
|
||||
sb.append("extendedState");
|
||||
} else if (StateMachine.class.isAssignableFrom(parameterType)) {
|
||||
sb.append("stateMachine");
|
||||
} else if (Message.class.isAssignableFrom(parameterType)) {
|
||||
sb.append("message");
|
||||
} else if (Exception.class.isAssignableFrom(parameterType)) {
|
||||
sb.append("exception");
|
||||
}
|
||||
}
|
||||
if (hasUnqualifiedMapParameter) {
|
||||
@@ -522,6 +529,14 @@ public class StateMachineMethodInvokerHelper<T, S, E> extends AbstractExpression
|
||||
return stateContext.getStateMachine();
|
||||
}
|
||||
|
||||
public Message<EE> getMessage() {
|
||||
return stateContext.getMessage();
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return stateContext.getException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
public boolean sendEvent(Message<E> event) {
|
||||
if (hasStateMachineError()) {
|
||||
// TODO: should we throw exception?
|
||||
notifyEventNotAccepted(event, buildStateContext(null, null, getRelayStateMachine()));
|
||||
notifyEventNotAccepted(event, buildStateContext(event, null, getRelayStateMachine()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -184,18 +184,18 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
event = getStateMachineInterceptors().preEvent(event, this);
|
||||
} catch (Exception e) {
|
||||
log.info("Event " + event + " threw exception in interceptors, not accepting event");
|
||||
notifyEventNotAccepted(event, buildStateContext(null, null, getRelayStateMachine()));
|
||||
notifyEventNotAccepted(event, buildStateContext(event, null, getRelayStateMachine()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isComplete() || !isRunning()) {
|
||||
notifyEventNotAccepted(event, buildStateContext(null, null, getRelayStateMachine()));
|
||||
notifyEventNotAccepted(event, buildStateContext(event, null, getRelayStateMachine()));
|
||||
return false;
|
||||
}
|
||||
boolean accepted = acceptEvent(event);
|
||||
stateMachineExecutor.execute();
|
||||
if (!accepted) {
|
||||
notifyEventNotAccepted(event, buildStateContext(null, null, getRelayStateMachine()));
|
||||
notifyEventNotAccepted(event, buildStateContext(event, null, getRelayStateMachine()));
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
@@ -346,7 +346,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
currentError = exception;
|
||||
}
|
||||
if (currentError != null) {
|
||||
notifyStateMachineError(this, currentError, buildStateContext(null, null, this));
|
||||
notifyStateMachineError(this, currentError, buildStateContext(null, null, this, currentError));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,17 +678,21 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
}
|
||||
|
||||
private StateContext<S, E> buildStateContext(Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
|
||||
E event = message != null ? message.getPayload() : null;
|
||||
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
|
||||
new HashMap<String, Object>());
|
||||
return new DefaultStateContext<S, E>(event, messageHeaders, extendedState, transition, stateMachine);
|
||||
return new DefaultStateContext<S, E>(message, messageHeaders, extendedState, transition, stateMachine);
|
||||
}
|
||||
|
||||
private StateContext<S, E> buildStateContext(Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine, Exception exception) {
|
||||
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
|
||||
new HashMap<String, Object>());
|
||||
return new DefaultStateContext<S, E>(message, messageHeaders, extendedState, transition, stateMachine, null, null, exception);
|
||||
}
|
||||
|
||||
private StateContext<S, E> buildStateContext(Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine, State<S, E> source, State<S, E> target) {
|
||||
E event = message != null ? message.getPayload() : null;
|
||||
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
|
||||
new HashMap<String, Object>());
|
||||
return new DefaultStateContext<S, E>(event, messageHeaders, extendedState, transition, stateMachine, source, target);
|
||||
return new DefaultStateContext<S, E>(message, messageHeaders, extendedState, transition, stateMachine, source, target, null);
|
||||
}
|
||||
|
||||
private State<S, E> findDeepParent(State<S, E> state) {
|
||||
|
||||
@@ -23,38 +23,50 @@ import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link StateContext}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class DefaultStateContext<S, E> implements StateContext<S, E> {
|
||||
|
||||
private final E event;
|
||||
private final Message<E> message;
|
||||
private final MessageHeaders messageHeaders;
|
||||
private final ExtendedState extendedState;
|
||||
private final Transition<S,E> transition;
|
||||
private final StateMachine<S, E> stateMachine;
|
||||
private final State<S, E> source;
|
||||
private final State<S, E> target;
|
||||
private final Exception exception;
|
||||
|
||||
public DefaultStateContext(E event, MessageHeaders messageHeaders, ExtendedState extendedState, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
|
||||
this(event, messageHeaders, extendedState, transition, stateMachine, null, null);
|
||||
public DefaultStateContext(Message<E> message, MessageHeaders messageHeaders, ExtendedState extendedState, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine) {
|
||||
this(message, messageHeaders, extendedState, transition, stateMachine, null, null, null);
|
||||
}
|
||||
|
||||
public DefaultStateContext(E event, MessageHeaders messageHeaders, ExtendedState extendedState, Transition<S,E> transition, StateMachine<S, E> stateMachine, State<S, E> source, State<S, E> target) {
|
||||
this.event = event;
|
||||
public DefaultStateContext(Message<E> message, MessageHeaders messageHeaders, ExtendedState extendedState, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine, State<S, E> source, State<S, E> target, Exception exception) {
|
||||
this.message = message;
|
||||
this.messageHeaders = messageHeaders;
|
||||
this.extendedState = extendedState;
|
||||
this.transition = transition;
|
||||
this.stateMachine = stateMachine;
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getEvent() {
|
||||
return event;
|
||||
return message != null ? message.getPayload() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<E> getMessage() {
|
||||
return null;
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,6 +111,6 @@ public class DefaultStateContext<S, E> implements StateContext<S, E> {
|
||||
|
||||
@Override
|
||||
public Exception getException() {
|
||||
return null;
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,8 +392,6 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
}
|
||||
|
||||
private StateContext<S, E> buildStateContext(Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
|
||||
E event = message != null ? message.getPayload() : null;
|
||||
|
||||
// TODO: maybe a direct use of MessageHeaders is wring, combine
|
||||
// payload and headers as a message?
|
||||
|
||||
@@ -407,7 +405,7 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
// we want to keep the originating sm id
|
||||
map.put(StateMachineSystemConstants.STATEMACHINE_IDENTIFIER, stateMachine.getId());
|
||||
}
|
||||
return new DefaultStateContext<S, E>(event, new MessageHeaders(map), stateMachine.getExtendedState(), transition, stateMachine);
|
||||
return new DefaultStateContext<S, E>(message, new MessageHeaders(map), stateMachine.getExtendedState(), transition, stateMachine);
|
||||
}
|
||||
|
||||
private void registerTriggerListener() {
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.junit.Test;
|
||||
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.messaging.support.MessageBuilder;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.ExtendedState;
|
||||
@@ -241,7 +242,33 @@ public class MethodAnnotationTests extends AbstractStateMachineTests {
|
||||
machine.start();
|
||||
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).build());
|
||||
assertThat(bean6.onEventNotAcceptedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean6.onEventNotAccepted1Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean6.onEventNotAccepted2Latch.await(2, TimeUnit.SECONDS), is(false));
|
||||
assertThat(bean6.onEventNotAccepted3Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean6.onEventNotAccepted4Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean6.onEventNotAccepted4Message, notNullValue());
|
||||
assertThat(bean6.onEventNotAccepted4Message.getPayload(), is(TestEvents.E4));
|
||||
assertThat(bean6.onEventNotAccepted5Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMethodAnnotations6() throws Exception {
|
||||
context.register(BaseConfig.class, BeanConfig7.class, Config1.class);
|
||||
context.refresh();
|
||||
|
||||
ObjectStateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
|
||||
Bean7 bean7 = context.getBean(Bean7.class);
|
||||
machine.start();
|
||||
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).build());
|
||||
|
||||
machine.setStateMachineError(new RuntimeException());
|
||||
|
||||
assertThat(bean7.OnStateMachineError1Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean7.OnStateMachineError2Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean7.OnStateMachineError2Exception, notNullValue());
|
||||
}
|
||||
|
||||
@WithStateMachine
|
||||
@@ -450,15 +477,68 @@ public class MethodAnnotationTests extends AbstractStateMachineTests {
|
||||
|
||||
@WithStateMachine
|
||||
static class Bean6 {
|
||||
CountDownLatch onEventNotAcceptedLatch = new CountDownLatch(1);
|
||||
CountDownLatch onEventNotAccepted1Latch = new CountDownLatch(1);
|
||||
CountDownLatch onEventNotAccepted2Latch = new CountDownLatch(1);
|
||||
CountDownLatch onEventNotAccepted3Latch = new CountDownLatch(1);
|
||||
CountDownLatch onEventNotAccepted4Latch = new CountDownLatch(1);
|
||||
CountDownLatch onEventNotAccepted5Latch = new CountDownLatch(1);
|
||||
Message<TestEvents> onEventNotAccepted4Message;
|
||||
|
||||
@OnEventNotAccepted
|
||||
public void onEventNotAcceptedLatch() {
|
||||
onEventNotAcceptedLatch.countDown();
|
||||
public void onEventNotAccepted1() {
|
||||
onEventNotAccepted1Latch.countDown();
|
||||
}
|
||||
|
||||
@OnEventNotAccepted(event = "E1")
|
||||
public void onEventNotAccepted2() {
|
||||
onEventNotAccepted2Latch.countDown();
|
||||
}
|
||||
|
||||
@OnEventNotAccepted(event = "E4")
|
||||
public void onEventNotAccepted3() {
|
||||
onEventNotAccepted3Latch.countDown();
|
||||
}
|
||||
|
||||
@OnEventNotAccepted()
|
||||
public void onEventNotAccepted4(Message<TestEvents> message) {
|
||||
onEventNotAccepted4Message = message;
|
||||
onEventNotAccepted4Latch.countDown();
|
||||
}
|
||||
|
||||
@OnEventNotAccepted(event = {"E1", "E4"})
|
||||
public void onEventNotAccepted5() {
|
||||
onEventNotAccepted5Latch.countDown();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
onEventNotAccepted1Latch = new CountDownLatch(1);
|
||||
onEventNotAccepted2Latch = new CountDownLatch(1);
|
||||
onEventNotAccepted3Latch = new CountDownLatch(1);
|
||||
onEventNotAccepted4Latch = new CountDownLatch(1);
|
||||
onEventNotAccepted4Message = null;
|
||||
}
|
||||
}
|
||||
|
||||
@WithStateMachine
|
||||
static class Bean7 {
|
||||
CountDownLatch OnStateMachineError1Latch = new CountDownLatch(1);
|
||||
CountDownLatch OnStateMachineError2Latch = new CountDownLatch(1);
|
||||
Exception OnStateMachineError2Exception;
|
||||
|
||||
@OnStateMachineError
|
||||
public void OnStateMachineError1() {
|
||||
OnStateMachineError1Latch.countDown();
|
||||
}
|
||||
|
||||
@OnStateMachineError
|
||||
public void OnStateMachineError2(Exception e) {
|
||||
OnStateMachineError2Exception = e;
|
||||
OnStateMachineError2Latch.countDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class BeanConfig1 {
|
||||
|
||||
@@ -519,6 +599,16 @@ public class MethodAnnotationTests extends AbstractStateMachineTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class BeanConfig7 {
|
||||
|
||||
@Bean
|
||||
public Bean7 bean7() {
|
||||
return new Bean7();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine(name = {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, "fooMachine"})
|
||||
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@@ -15,14 +15,9 @@
|
||||
*/
|
||||
package org.springframework.statemachine.docs;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.support.StaticListableBeanFactory;
|
||||
@@ -37,16 +32,12 @@ import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.ExtendedState;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.access.StateMachineAccess;
|
||||
import org.springframework.statemachine.access.StateMachineFunction;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.action.SpelExpressionAction;
|
||||
import org.springframework.statemachine.annotation.EventHeaders;
|
||||
import org.springframework.statemachine.annotation.OnTransition;
|
||||
import org.springframework.statemachine.annotation.WithStateMachine;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.EnableStateMachineFactory;
|
||||
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||
@@ -408,52 +399,6 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
|
||||
}
|
||||
// end::snippetH[]
|
||||
|
||||
// tag::snippetI[]
|
||||
@WithStateMachine
|
||||
public class Bean1 {
|
||||
|
||||
@OnTransition(source = "S1", target = "S2")
|
||||
public void fromS1ToS2() {
|
||||
}
|
||||
|
||||
@OnTransition
|
||||
public void anyTransition() {
|
||||
}
|
||||
}
|
||||
// end::snippetI[]
|
||||
|
||||
// tag::snippetII[]
|
||||
@WithStateMachine
|
||||
public class Bean4 {
|
||||
|
||||
@StatesOnTransition(source = States.S1, target = States.S2)
|
||||
public void fromS1ToS2(@EventHeaders Map<String, Object> headers, ExtendedState extendedState) {
|
||||
}
|
||||
}
|
||||
// end::snippetII[]
|
||||
|
||||
// tag::snippetJ[]
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@OnTransition
|
||||
public @interface StatesOnTransition {
|
||||
|
||||
States[] source() default {};
|
||||
|
||||
States[] target() default {};
|
||||
}
|
||||
// end::snippetJ[]
|
||||
|
||||
// tag::snippetK[]
|
||||
@WithStateMachine
|
||||
public class Bean2 {
|
||||
|
||||
@StatesOnTransition(source = States.S1, target = States.S2)
|
||||
public void fromS1ToS2() {
|
||||
}
|
||||
}
|
||||
// end::snippetK[]
|
||||
|
||||
// tag::snippetL[]
|
||||
public class Bean3 {
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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.docs;
|
||||
|
||||
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.messaging.Message;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.ExtendedState;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.annotation.EventHeaders;
|
||||
import org.springframework.statemachine.annotation.OnEventNotAccepted;
|
||||
import org.springframework.statemachine.annotation.OnExtendedStateChanged;
|
||||
import org.springframework.statemachine.annotation.OnStateChanged;
|
||||
import org.springframework.statemachine.annotation.OnStateEntry;
|
||||
import org.springframework.statemachine.annotation.OnStateExit;
|
||||
import org.springframework.statemachine.annotation.OnStateMachineError;
|
||||
import org.springframework.statemachine.annotation.OnStateMachineStart;
|
||||
import org.springframework.statemachine.annotation.OnStateMachineStop;
|
||||
import org.springframework.statemachine.annotation.OnTransition;
|
||||
import org.springframework.statemachine.annotation.WithStateMachine;
|
||||
|
||||
public class DocsConfigurationSampleTests4 extends AbstractStateMachineTests {
|
||||
|
||||
// tag::snippetA[]
|
||||
@WithStateMachine
|
||||
public class Bean1 {
|
||||
|
||||
@OnTransition
|
||||
public void anyTransition() {
|
||||
}
|
||||
}
|
||||
// end::snippetA[]
|
||||
|
||||
// tag::snippetAA[]
|
||||
@WithStateMachine(name = "myMachineBeanName")
|
||||
public class Bean2 {
|
||||
|
||||
@OnTransition
|
||||
public void anyTransition() {
|
||||
}
|
||||
}
|
||||
// end::snippetAA[]
|
||||
|
||||
// tag::snippetB[]
|
||||
@WithStateMachine
|
||||
public class Bean3 {
|
||||
|
||||
@OnTransition
|
||||
public void anyTransition(StateContext<String, String> stateContext) {
|
||||
}
|
||||
}
|
||||
// end::snippetB[]
|
||||
|
||||
// tag::snippetBB[]
|
||||
@WithStateMachine
|
||||
public class Bean4 {
|
||||
|
||||
@OnTransition
|
||||
public void anyTransition(
|
||||
@EventHeaders Map<String, Object> headers,
|
||||
ExtendedState extendedState,
|
||||
StateMachine<String, String> stateMachine,
|
||||
Message<String> message,
|
||||
Exception e) {
|
||||
}
|
||||
}
|
||||
// end::snippetBB[]
|
||||
|
||||
// tag::snippetC[]
|
||||
@WithStateMachine
|
||||
public class Bean5 {
|
||||
|
||||
@OnTransition(source = "S1", target = "S2")
|
||||
public void fromS1ToS2() {
|
||||
}
|
||||
|
||||
@OnTransition
|
||||
public void anyTransition() {
|
||||
}
|
||||
}
|
||||
// end::snippetC[]
|
||||
|
||||
// tag::snippetD[]
|
||||
@WithStateMachine
|
||||
public class Bean6 {
|
||||
|
||||
@StatesOnTransition(source = States.S1, target = States.S2)
|
||||
public void fromS1ToS2(@EventHeaders Map<String, Object> headers, ExtendedState extendedState) {
|
||||
}
|
||||
}
|
||||
// end::snippetD[]
|
||||
|
||||
// tag::snippetE[]
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@OnTransition
|
||||
public @interface StatesOnTransition {
|
||||
|
||||
States[] source() default {};
|
||||
|
||||
States[] target() default {};
|
||||
}
|
||||
// end::snippetE[]
|
||||
|
||||
// tag::snippetF[]
|
||||
@WithStateMachine
|
||||
public class Bean7 {
|
||||
|
||||
@StatesOnTransition(source = States.S1, target = States.S2)
|
||||
public void fromS1ToS2() {
|
||||
}
|
||||
}
|
||||
// end::snippetF[]
|
||||
|
||||
// tag::snippetG[]
|
||||
@WithStateMachine
|
||||
public class Bean8 {
|
||||
|
||||
@OnStateChanged
|
||||
public void anyStateChange() {
|
||||
}
|
||||
}
|
||||
// end::snippetG[]
|
||||
|
||||
// tag::snippetGG[]
|
||||
@WithStateMachine
|
||||
public class Bean9 {
|
||||
|
||||
@OnStateChanged(source = "S1", target = "S2")
|
||||
public void stateChangeFromS1toS2() {
|
||||
}
|
||||
}
|
||||
// end::snippetGG[]
|
||||
|
||||
// tag::snippetGGG[]
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@OnStateChanged
|
||||
public @interface StatesOnStates {
|
||||
|
||||
States[] source() default {};
|
||||
|
||||
States[] target() default {};
|
||||
}
|
||||
// end::snippetGGG[]
|
||||
|
||||
// tag::snippetGGGG[]
|
||||
@WithStateMachine
|
||||
public class Bean10 {
|
||||
|
||||
@StatesOnStates(source = States.S1, target = States.S2)
|
||||
public void fromS1ToS2() {
|
||||
}
|
||||
}
|
||||
// end::snippetGGGG[]
|
||||
|
||||
// tag::snippetGGGGG[]
|
||||
@WithStateMachine
|
||||
public class Bean11 {
|
||||
|
||||
@OnStateEntry
|
||||
public void anyStateEntry() {
|
||||
}
|
||||
|
||||
@OnStateExit
|
||||
public void anyStateExit() {
|
||||
}
|
||||
}
|
||||
// end::snippetGGGGG[]
|
||||
|
||||
// tag::snippetH[]
|
||||
@WithStateMachine
|
||||
public class Bean12 {
|
||||
|
||||
@OnEventNotAccepted
|
||||
public void anyEventNotAccepted() {
|
||||
}
|
||||
|
||||
@OnEventNotAccepted(event = "E1")
|
||||
public void e1EventNotAccepted() {
|
||||
}
|
||||
}
|
||||
// end::snippetH[]
|
||||
|
||||
// tag::snippetI[]
|
||||
@WithStateMachine
|
||||
public class Bean13 {
|
||||
|
||||
@OnStateMachineStart
|
||||
public void onStateMachineStart() {
|
||||
}
|
||||
|
||||
@OnStateMachineStop
|
||||
public void onStateMachineStop() {
|
||||
}
|
||||
}
|
||||
// end::snippetI[]
|
||||
|
||||
// tag::snippetII[]
|
||||
@WithStateMachine
|
||||
public class Bean14 {
|
||||
|
||||
@OnStateMachineError
|
||||
public void onStateMachineError() {
|
||||
}
|
||||
}
|
||||
// end::snippetII[]
|
||||
|
||||
// tag::snippetJ[]
|
||||
@WithStateMachine
|
||||
public class Bean15 {
|
||||
|
||||
@OnExtendedStateChanged
|
||||
public void anyStateChange() {
|
||||
}
|
||||
|
||||
@OnExtendedStateChanged(key = "key1")
|
||||
public void key1Changed() {
|
||||
}
|
||||
}
|
||||
// end::snippetJ[]
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.processor;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.statemachine.ExtendedState;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.annotation.EventHeaders;
|
||||
import org.springframework.statemachine.annotation.OnTransition;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.support.DefaultExtendedState;
|
||||
import org.springframework.statemachine.support.DefaultStateContext;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
public class MethodParameterTests {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testOnTransition() {
|
||||
Bean1 bean1 = new Bean1();
|
||||
Method method = ReflectionUtils.findMethod(Bean1.class, "onTransition", Map.class, ExtendedState.class, StateMachine.class,
|
||||
Message.class, Exception.class, StateContext.class);
|
||||
OnTransition annotation = AnnotationUtils.findAnnotation(method, OnTransition.class);
|
||||
StateMachineHandler<OnTransition, String, String> handler = new StateMachineHandler<OnTransition, String, String>(Bean1.class,
|
||||
bean1, method, annotation, annotation);
|
||||
|
||||
Message<String> message = MessageBuilder.withPayload("S").build();
|
||||
MessageHeaders messageHeaders = message.getHeaders();
|
||||
ExtendedState extendedState = new DefaultExtendedState();
|
||||
Transition<String, String> transition = mock(Transition.class);
|
||||
StateMachine<String, String> stateMachine = mock(StateMachine.class);
|
||||
State<String, String> source = mock(State.class);
|
||||
State<String, String> target = mock(State.class);
|
||||
Exception exception = new RuntimeException();
|
||||
|
||||
StateMachineRuntime<String, String> runtime = new StateMachineRuntime<String, String>() {
|
||||
@Override
|
||||
public StateContext<String, String> getStateContext() {
|
||||
return new DefaultStateContext<String, String>(message, messageHeaders, extendedState, transition, stateMachine, source,
|
||||
target, exception);
|
||||
}
|
||||
};
|
||||
|
||||
handler.handle(runtime);
|
||||
}
|
||||
|
||||
public static class Bean1 {
|
||||
|
||||
@OnTransition
|
||||
public void onTransition(@EventHeaders Map<String, Object> headers, ExtendedState extendedState, StateMachine<?, ?> stateMachine,
|
||||
Message<?> message, Exception e, StateContext<?, ?> stateContext) {
|
||||
assertThat(headers, notNullValue());
|
||||
assertThat(extendedState, notNullValue());
|
||||
assertThat(stateMachine, notNullValue());
|
||||
assertThat(message, notNullValue());
|
||||
assertThat(e, notNullValue());
|
||||
assertThat(stateContext, notNullValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -91,7 +91,7 @@ public class StateContextExpressionMethodsTests {
|
||||
extendedState.getVariables().put("boolean1", true);
|
||||
extendedState.getVariables().put("boolean2", false);
|
||||
StateContext<SpelStates, SpelEvents> stateContext = new DefaultStateContext<SpelStates, SpelEvents>(
|
||||
SpelEvents.E1, messageHeaders, extendedState, new MockTransition(), stateMachine);
|
||||
MessageBuilder.withPayload(SpelEvents.E1).build(), messageHeaders, extendedState, new MockTransition(), stateMachine);
|
||||
return stateContext;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user