diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index c8e559ac..48ec6da3 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -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 +<>. + _@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 +<>. + +[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 diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java index 3b60de59..173ea43f 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java @@ -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}. *

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

* Return value can be anything and is effectively discarded. diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java index f1e26dca..66844a73 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java @@ -156,7 +156,14 @@ public class StateMachineHandlerCallHelper 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 implements InitializingBean, Be return handle; } + private boolean annotationHandlerEventVariableMatch(Annotation annotation, Object key) { + boolean handle = false; + Map annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation); + Object object = annotationAttributes.get("event"); + Collection 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 sourceState, State targetState) { Map annotationAttributes = AnnotationUtils.getAnnotationAttributes(methodAnnotation); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java index 95a9adc5..7b9655f0 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java @@ -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 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 extends AbstractExpression return stateContext.getStateMachine(); } + public Message getMessage() { + return stateContext.getMessage(); + } + + public Exception getException() { + return stateContext.getException(); + } + } } 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 d600307d..722d2db1 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 @@ -176,7 +176,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo public boolean sendEvent(Message 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 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 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 extends StateMachineObjectSuppo } private StateContext buildStateContext(Message message, Transition transition, StateMachine stateMachine) { - E event = message != null ? message.getPayload() : null; MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders( new HashMap()); - return new DefaultStateContext(event, messageHeaders, extendedState, transition, stateMachine); + return new DefaultStateContext(message, messageHeaders, extendedState, transition, stateMachine); + } + + private StateContext buildStateContext(Message message, Transition transition, StateMachine stateMachine, Exception exception) { + MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders( + new HashMap()); + return new DefaultStateContext(message, messageHeaders, extendedState, transition, stateMachine, null, null, exception); } private StateContext buildStateContext(Message message, Transition transition, StateMachine stateMachine, State source, State target) { - E event = message != null ? message.getPayload() : null; MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders( new HashMap()); - return new DefaultStateContext(event, messageHeaders, extendedState, transition, stateMachine, source, target); + return new DefaultStateContext(message, messageHeaders, extendedState, transition, stateMachine, source, target, null); } private State findDeepParent(State state) { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java index 4ed1032e..de317d41 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java @@ -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 the type of state + * @param the type of event + */ public class DefaultStateContext implements StateContext { - private final E event; + private final Message message; private final MessageHeaders messageHeaders; private final ExtendedState extendedState; private final Transition transition; private final StateMachine stateMachine; private final State source; private final State target; + private final Exception exception; - public DefaultStateContext(E event, MessageHeaders messageHeaders, ExtendedState extendedState, Transition transition, StateMachine stateMachine) { - this(event, messageHeaders, extendedState, transition, stateMachine, null, null); + public DefaultStateContext(Message message, MessageHeaders messageHeaders, ExtendedState extendedState, Transition transition, + StateMachine stateMachine) { + this(message, messageHeaders, extendedState, transition, stateMachine, null, null, null); } - public DefaultStateContext(E event, MessageHeaders messageHeaders, ExtendedState extendedState, Transition transition, StateMachine stateMachine, State source, State target) { - this.event = event; + public DefaultStateContext(Message message, MessageHeaders messageHeaders, ExtendedState extendedState, Transition transition, + StateMachine stateMachine, State source, State 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 getMessage() { - return null; + return message; } @Override @@ -99,6 +111,6 @@ public class DefaultStateContext implements StateContext { @Override public Exception getException() { - return null; + return exception; } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java index 9d226309..c654bfd3 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java @@ -392,8 +392,6 @@ public class DefaultStateMachineExecutor extends LifecycleObjectSupport im } private StateContext buildStateContext(Message message, Transition transition, StateMachine 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 extends LifecycleObjectSupport im // we want to keep the originating sm id map.put(StateMachineSystemConstants.STATEMACHINE_IDENTIFIER, stateMachine.getId()); } - return new DefaultStateContext(event, new MessageHeaders(map), stateMachine.getExtendedState(), transition, stateMachine); + return new DefaultStateContext(message, new MessageHeaders(map), stateMachine.getExtendedState(), transition, stateMachine); } private void registerTriggerListener() { diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java index 755659f8..e06bb3a2 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java @@ -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 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 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 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 { diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java index 879a7729..3a313076 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java @@ -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 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 { diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests4.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests4.java new file mode 100644 index 00000000..ff6731f5 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests4.java @@ -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 stateContext) { + } + } +// end::snippetB[] + +// tag::snippetBB[] + @WithStateMachine + public class Bean4 { + + @OnTransition + public void anyTransition( + @EventHeaders Map headers, + ExtendedState extendedState, + StateMachine stateMachine, + Message 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 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[] + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/MethodParameterTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/MethodParameterTests.java new file mode 100644 index 00000000..d8d62651 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/MethodParameterTests.java @@ -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 handler = new StateMachineHandler(Bean1.class, + bean1, method, annotation, annotation); + + Message message = MessageBuilder.withPayload("S").build(); + MessageHeaders messageHeaders = message.getHeaders(); + ExtendedState extendedState = new DefaultExtendedState(); + Transition transition = mock(Transition.class); + StateMachine stateMachine = mock(StateMachine.class); + State source = mock(State.class); + State target = mock(State.class); + Exception exception = new RuntimeException(); + + StateMachineRuntime runtime = new StateMachineRuntime() { + @Override + public StateContext getStateContext() { + return new DefaultStateContext(message, messageHeaders, extendedState, transition, stateMachine, source, + target, exception); + } + }; + + handler.handle(runtime); + } + + public static class Bean1 { + + @OnTransition + public void onTransition(@EventHeaders Map 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()); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java index e5c70a3b..7da0a286 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java @@ -91,7 +91,7 @@ public class StateContextExpressionMethodsTests { extendedState.getVariables().put("boolean1", true); extendedState.getVariables().put("boolean2", false); StateContext stateContext = new DefaultStateContext( - SpelEvents.E1, messageHeaders, extendedState, new MockTransition(), stateMachine); + MessageBuilder.withPayload(SpelEvents.E1).build(), messageHeaders, extendedState, new MockTransition(), stateMachine); return stateContext; }