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:
Janne Valkealahti
2015-12-25 09:10:36 +00:00
parent 0ac5785ec5
commit c2a458ce70
12 changed files with 646 additions and 89 deletions

View File

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

View File

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

View File

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

View File

@@ -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();
}
}
}

View File

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

View File

@@ -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;
}
}

View File

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

View File

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

View File

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

View File

@@ -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[]
}

View File

@@ -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());
}
}
}

View File

@@ -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;
}