From b30ad73e00e842e374a43df96f6fccb4fe81b0bd Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sat, 7 Mar 2015 15:15:46 +0000 Subject: [PATCH] Fixes for annotation method handling - adding generics - tidy up annotation handling for methods - add EventHeaders annotation --- .../statemachine/annotation/EventHeaders.java | 28 ++++ ...dInvokingStateMachineRuntimeProcessor.java | 14 +- ...chineActivatorAnnotationPostProcessor.java | 4 +- .../processor/StateMachineHandler.java | 24 ++-- .../StateMachineMethodInvokerHelper.java | 128 ++++++------------ .../StateMachineOnTransitionHandler.java | 28 +++- .../processor/StateMachineRuntime.java | 11 +- .../StateMachineRuntimeProcessor.java | 8 +- .../support/AbstractStateMachine.java | 33 +++-- .../annotation/MethodAnnotationTests.java | 71 +++++++++- 10 files changed, 217 insertions(+), 132 deletions(-) create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/EventHeaders.java diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/EventHeaders.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/EventHeaders.java new file mode 100644 index 00000000..e5111acb --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/EventHeaders.java @@ -0,0 +1,28 @@ +/* + * 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface EventHeaders { +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java index 37261fd4..8af63c75 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java @@ -25,25 +25,27 @@ import java.lang.reflect.Method; * @author Janne Valkealahti * * @param the return type + * @param the type of state + * @param the type of event */ -public class MethodInvokingStateMachineRuntimeProcessor implements StateMachineRuntimeProcessor { +public class MethodInvokingStateMachineRuntimeProcessor implements StateMachineRuntimeProcessor { - private final StateMachineMethodInvokerHelper delegate; + private final StateMachineMethodInvokerHelper delegate; public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, Method method) { - delegate = new StateMachineMethodInvokerHelper(targetObject, method); + delegate = new StateMachineMethodInvokerHelper(targetObject, method); } public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, String methodName) { - delegate = new StateMachineMethodInvokerHelper(targetObject, methodName); + delegate = new StateMachineMethodInvokerHelper(targetObject, methodName); } public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, Class annotationType) { - delegate = new StateMachineMethodInvokerHelper(targetObject, annotationType); + delegate = new StateMachineMethodInvokerHelper(targetObject, annotationType); } @Override - public T process(StateMachineRuntime stateMachineRuntime) { + public T process(StateMachineRuntime stateMachineRuntime) { try { return delegate.process(stateMachineRuntime); } catch (Exception e) { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java index f6055e7a..8cf009c7 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java @@ -40,8 +40,8 @@ public class StateMachineActivatorAnnotationPostProcessor implements MethodAnnot @Override public Object postProcess(Object bean, String beanName, Method method, OnTransition annotation) { - StateMachineHandler handler = new StateMachineOnTransitionHandler(bean, method, annotation); - + StateMachineHandler handler = new StateMachineOnTransitionHandler(bean, method, annotation); + Integer order = findOrder(bean, method); if (order != null) { handler.setOrder(order); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java index df31233b..a8fc6875 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java @@ -28,22 +28,16 @@ import org.springframework.statemachine.annotation.WithStateMachine; * * @author Janne Valkealahti * + * @param the return type + * @param the type of state + * @param the type of event */ -public class StateMachineHandler implements Ordered { +public class StateMachineHandler implements Ordered { - private final StateMachineRuntimeProcessor processor; + private final StateMachineRuntimeProcessor processor; private int order = Ordered.LOWEST_PRECEDENCE; - /** - * Instantiates a new container handler. - * - * @param target the target bean - */ -// public StateMachineHandler(Object target) { -// this(new MethodInvokingStateMachineRuntimeProcessor(target, OnTransition.class)); -// } - /** * Instantiates a new container handler. * @@ -51,7 +45,7 @@ public class StateMachineHandler implements Ordered { * @param method the method */ public StateMachineHandler(Object target, Method method) { - this(new MethodInvokingStateMachineRuntimeProcessor(target, method)); + this(new MethodInvokingStateMachineRuntimeProcessor(target, method)); } /** @@ -61,7 +55,7 @@ public class StateMachineHandler implements Ordered { * @param methodName the method name */ public StateMachineHandler(Object target, String methodName) { - this(new MethodInvokingStateMachineRuntimeProcessor(target, methodName)); + this(new MethodInvokingStateMachineRuntimeProcessor(target, methodName)); } /** @@ -70,7 +64,7 @@ public class StateMachineHandler implements Ordered { * @param the generic type * @param processor the processor */ - public StateMachineHandler(MethodInvokingStateMachineRuntimeProcessor processor) { + public StateMachineHandler(MethodInvokingStateMachineRuntimeProcessor processor) { this.processor = processor; } @@ -95,7 +89,7 @@ public class StateMachineHandler implements Ordered { * @param stateMachineRuntime the state machine runtime * @return the result value */ - public Object handle(StateMachineRuntime stateMachineRuntime) { + public Object handle(StateMachineRuntime stateMachineRuntime) { return processor.process(stateMachineRuntime); } 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 f6f1e830..1160f1a9 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 @@ -29,9 +29,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.Advised; import org.springframework.aop.support.AopUtils; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.MethodParameter; -import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationException; @@ -39,7 +37,9 @@ 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.statemachine.ExtendedState; import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.annotation.EventHeaders; import org.springframework.statemachine.support.AbstractExpressionEvaluator; import org.springframework.statemachine.support.AnnotatedMethodFilter; import org.springframework.statemachine.support.FixedMethodFilter; @@ -56,7 +56,7 @@ import org.springframework.util.ReflectionUtils.MethodFilter; * * @param the return type */ -public class StateMachineMethodInvokerHelper extends AbstractExpressionEvaluator { +public class StateMachineMethodInvokerHelper extends AbstractExpressionEvaluator { private static final String CANDIDATE_METHODS = "CANDIDATE_METHODS"; @@ -105,8 +105,8 @@ public class StateMachineMethodInvokerHelper extends AbstractExpressionEvalua this(targetObject, annotationType, (String) null, expectedType); } - public T process(StateMachineRuntime stateMachineRuntime) throws Exception { - ParametersWrapper wrapper = new ParametersWrapper(stateMachineRuntime.getStateContext()); + public T process(StateMachineRuntime stateMachineRuntime) throws Exception { + ParametersWrapper wrapper = new ParametersWrapper(stateMachineRuntime.getStateContext()); return processInternal(wrapper); } @@ -214,7 +214,7 @@ public class StateMachineMethodInvokerHelper extends AbstractExpressionEvalua return false; } - private T processInternal(ParametersWrapper parameters) throws Exception { + private T processInternal(ParametersWrapper parameters) throws Exception { HandlerMethod candidate = this.findHandlerMethodForParameters(parameters); Assert.notNull(candidate, "No candidate methods found for messages."); Expression expression = candidate.getExpression(); @@ -347,7 +347,7 @@ public class StateMachineMethodInvokerHelper extends AbstractExpressionEvalua return targetClass; } - private HandlerMethod findHandlerMethodForParameters(ParametersWrapper parameters) { + private HandlerMethod findHandlerMethodForParameters(ParametersWrapper parameters) { if (this.handlerMethod != null) { return this.handlerMethod; } else { @@ -378,7 +378,7 @@ public class StateMachineMethodInvokerHelper extends AbstractExpressionEvalua private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); - private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer(); +// private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer(); private final Method method; @@ -429,19 +429,12 @@ public class StateMachineMethodInvokerHelper extends AbstractExpressionEvalua if (mappingAnnotation != null) { Class annotationType = mappingAnnotation.annotationType(); -// if (annotationType.equals(YarnEnvironments.class)) { -// sb.append("environment"); -// } else if (annotationType.equals(YarnEnvironment.class)) { -// YarnEnvironment headerAnnotation = (YarnEnvironment) mappingAnnotation; -// sb.append(this.determineEnvironmentExpression(headerAnnotation, methodParameter)); -// } else if (annotationType.equals(YarnParameters.class)) { -// Assert.isTrue(Map.class.isAssignableFrom(parameterType), -// "The @YarnParameters annotation can only be applied to a Map-typed parameter."); -// sb.append("parameters"); -// } else if (annotationType.equals(YarnParameter.class)) { -// YarnParameter headerAnnotation = (YarnParameter) mappingAnnotation; -// sb.append(this.determineParameterExpression(headerAnnotation, methodParameter)); -// } + if (annotationType.equals(EventHeaders.class)) { + sb.append("headers"); + } + + } else if (ExtendedState.class.isAssignableFrom(parameterType)) { + sb.append("extendedState"); } } if (hasUnqualifiedMapParameter) { @@ -465,89 +458,44 @@ public class StateMachineMethodInvokerHelper extends AbstractExpressionEvalua Annotation match = null; for (Annotation annotation : annotations) { Class type = annotation.annotationType(); -// if (type.equals(YarnParameters.class) || type.equals(YarnParameter.class) -// || type.equals(YarnEnvironments.class) || type.equals(YarnEnvironment.class)) { -// if (match != null) { -// throw new IllegalArgumentException( -// "At most one parameter annotation can be provided for message mapping, " -// + "but found two: [" + match.annotationType().getName() + "] and [" -// + annotation.annotationType().getName() + "]"); -// } -// match = annotation; -// } + if (type.equals(EventHeaders.class)) { + if (match != null) { + throw new IllegalArgumentException( + "At most one parameter annotation can be provided for message mapping, " + + "but found two: [" + match.annotationType().getName() + "] and [" + + annotation.annotationType().getName() + "]"); + } + match = annotation; + } } return match; } -// private String determineParameterExpression(YarnParameter parameterAnnotation, MethodParameter methodParameter) { -// methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER); -// String headerName = null; -// String relativeExpression = ""; -// String valueAttribute = parameterAnnotation.value(); -// if (!StringUtils.hasText(valueAttribute)) { -// headerName = methodParameter.getParameterName(); -// } else if (valueAttribute.indexOf('.') != -1) { -// String tokens[] = valueAttribute.split("\\.", 2); -// headerName = tokens[0]; -// if (StringUtils.hasText(tokens[1])) { -// relativeExpression = "." + tokens[1]; -// } -// } else { -// headerName = valueAttribute; -// } -// Assert.notNull(headerName, "Cannot determine parameter name. Possible reasons: -debug is " -// + "disabled or header name is not explicitly provided via @YarnParameter annotation."); -// String headerRetrievalExpression = "parameters['" + headerName + "']"; -// String fullHeaderExpression = headerRetrievalExpression + relativeExpression; -// String fallbackExpression = (parameterAnnotation.required()) ? "T(org.springframework.util.Assert).isTrue(false, 'required parameter not available: " -// + headerName + "')" -// : "null"; -// return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression; -// } - -// private String determineEnvironmentExpression(YarnEnvironment environmentAnnotation, MethodParameter methodParameter) { -// methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER); -// String headerName = null; -// String relativeExpression = ""; -// String valueAttribute = environmentAnnotation.value(); -// if (!StringUtils.hasText(valueAttribute)) { -// headerName = methodParameter.getParameterName(); -// } else if (valueAttribute.indexOf('.') != -1) { -// String tokens[] = valueAttribute.split("\\.", 2); -// headerName = tokens[0]; -// if (StringUtils.hasText(tokens[1])) { -// relativeExpression = "." + tokens[1]; -// } -// } else { -// headerName = valueAttribute; -// } -// Assert.notNull(headerName, "Cannot determine parameter name. Possible reasons: -debug is " -// + "disabled or header name is not explicitly provided via @YarnEnvironment annotation."); -// String headerRetrievalExpression = "environment['" + headerName + "']"; -// String fullHeaderExpression = headerRetrievalExpression + relativeExpression; -// String fallbackExpression = (environmentAnnotation.required()) ? "T(org.springframework.util.Assert).isTrue(false, 'required parameter not available: " -// + headerName + "')" -// : "null"; -// return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression; -// } - } /** * Wrapping everything we need to work with spel. */ - public class ParametersWrapper { + public class ParametersWrapper { - private final StateContext stateContext; - - public ParametersWrapper(StateContext stateContext) { + private final StateContext stateContext; + + public ParametersWrapper(StateContext stateContext) { this.stateContext = stateContext; } - - public StateContext getStateContext() { + + public StateContext getStateContext() { return stateContext; } - + + public Map getHeaders() { + return stateContext.getMessageHeaders(); + } + + public ExtendedState getExtendedState() { + return stateContext.getExtendedState(); + } + } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java index ed45aa8a..5e701db4 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java @@ -19,15 +19,35 @@ import java.lang.reflect.Method; import org.springframework.statemachine.annotation.OnTransition; -public class StateMachineOnTransitionHandler extends StateMachineHandler { +/** + * Transition specific {@link StateMachineHandler}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class StateMachineOnTransitionHandler extends StateMachineHandler { - private OnTransition annotation; - + private final OnTransition annotation; + + /** + * Instantiates a new state machine on transition handler. + * + * @param target the target + * @param method the method + * @param annotation the annotation + */ public StateMachineOnTransitionHandler(Object target, Method method, OnTransition annotation) { super(target, method); this.annotation = annotation; } - + + /** + * Gets the annotation. + * + * @return the annotation + */ public OnTransition getAnnotation() { return annotation; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java index 3ac4be7f..7096998e 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java @@ -22,9 +22,16 @@ import org.springframework.statemachine.StateContext; * * @author Janne Valkealahti * + * @param the type of state + * @param the type of event */ -public interface StateMachineRuntime { +public interface StateMachineRuntime { - StateContext getStateContext(); + /** + * Gets the state context. + * + * @return the state context + */ + StateContext getStateContext(); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java index 10a42e8f..9a291f19 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java @@ -21,9 +21,11 @@ package org.springframework.statemachine.processor; * * @author Janne Valkealahti * - * @param type + * @param the return type + * @param the type of state + * @param the type of event */ -public interface StateMachineRuntimeProcessor { +public interface StateMachineRuntimeProcessor { /** * Process the container based on information available @@ -32,6 +34,6 @@ public interface StateMachineRuntimeProcessor { * @param stateMachineRuntime the yarn container runtime * @return the result */ - T process(StateMachineRuntime stateMachineRuntime); + T process(StateMachineRuntime stateMachineRuntime); } 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 8659284d..a4069e8b 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 @@ -87,6 +87,10 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport private volatile Runnable task; + private final Map> handlers = new HashMap>(); + + private volatile boolean handlersInitialized; + /** * Instantiates a new abstract state machine. * @@ -109,7 +113,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport */ public AbstractStateMachine(Collection> states, Collection> transitions, State initialState, State endState) { - this(states, transitions, initialState, endState, null, null); + this(states, transitions, initialState, endState, null, new DefaultExtendedState()); } /** @@ -366,6 +370,9 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport } private void callHandlers(State sourceState, State targetState, Message event) { + + + if (sourceState != null && targetState != null) { MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders( new HashMap()); @@ -375,21 +382,21 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport } - private List getStateMachineHandlerResults(List stateMachineHandlers, final StateContext stateContext) { - StateMachineRuntime runtime = new StateMachineRuntime() { + private List getStateMachineHandlerResults(List> stateMachineHandlers, final StateContext stateContext) { + StateMachineRuntime runtime = new StateMachineRuntime() { @Override public StateContext getStateContext() { return stateContext; } }; List results = new ArrayList(); - for (StateMachineHandler handler : stateMachineHandlers) { + for (StateMachineHandler handler : stateMachineHandlers) { results.add(handler.handle(runtime)); } return results; } - private List getStateMachineHandlers(State sourceState, State targetState) { + private synchronized List> getStateMachineHandlers(State sourceState, State targetState) { BeanFactory beanFactory = getBeanFactory(); // TODO think how to handle null bf @@ -397,11 +404,19 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport return Collections.emptyList(); } Assert.state(beanFactory instanceof ListableBeanFactory, "Bean factory must be instance of ListableBeanFactory"); - Map handlers = ((ListableBeanFactory) beanFactory) - .getBeansOfType(StateMachineOnTransitionHandler.class); - List handlersList = new ArrayList(); - for (Entry entry : handlers.entrySet()) { + if (!handlersInitialized) { + Map handlersx = ((ListableBeanFactory) beanFactory) + .getBeansOfType(StateMachineOnTransitionHandler.class); + for (Entry entry : handlersx.entrySet()) { + handlers.put(entry.getKey(), entry.getValue()); + } + handlersInitialized = true; + } + + List> handlersList = new ArrayList>(); + + for (Entry> entry : handlers.entrySet()) { OnTransition annotation = entry.getValue().getAnnotation(); String source = annotation.source(); String target = annotation.target(); 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 e8c0d8d0..61c2f7f4 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 @@ -16,9 +16,11 @@ package org.springframework.statemachine.annotation; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; import java.util.EnumSet; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -29,6 +31,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.MessageBuilder; import org.springframework.statemachine.AbstractStateMachineTests; import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.ExtendedState; import org.springframework.statemachine.StateMachineSystemConstants; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; @@ -59,6 +62,34 @@ public class MethodAnnotationTests extends AbstractStateMachineTests { context.close(); } + @Test + @SuppressWarnings("unchecked") + public void testMethodAnnotations2() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BaseConfig.class, AnnoConfig.class, BeanConfig2.class, Config1.class); + + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(context.containsBean("fooMachine"), is(true)); + machine.start(); + + Bean2 bean2 = context.getBean(Bean2.class); + + // this event should cause 'method1' to get called + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("foo", "jee").build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build()); + + assertThat(bean2.onMethod1Latch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(bean2.headers, notNullValue()); + assertThat((String)bean2.headers.get("foo"), is("jee")); + assertThat(bean2.extendedState, notNullValue()); + + assertThat(bean2.onMethod2Latch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(bean2.variable, notNullValue()); + assertThat((String)bean2.variable, is("jee")); + + context.close(); + } + @WithStateMachine(name = StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE) static class Bean1 { @@ -75,6 +106,30 @@ public class MethodAnnotationTests extends AbstractStateMachineTests { onOnTransitionFromS2ToS3Latch.countDown(); } + } + + @WithStateMachine(name = StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE) + static class Bean2 { + + CountDownLatch onMethod1Latch = new CountDownLatch(1); + CountDownLatch onMethod2Latch = new CountDownLatch(1); + Map headers; + ExtendedState extendedState; + Object variable; + + @OnTransition(source = "S1", target = "S2") + public void method1(@EventHeaders Map headers, ExtendedState extendedState) { + this.headers = headers; + extendedState.getVariables().put("foo", "jee"); + this.extendedState = extendedState; + onMethod1Latch.countDown(); + } + + @OnTransition(source = "S2", target = "S3") + public void method2(@EventHeaders Map headers, ExtendedState extendedState) { + variable = extendedState.getVariables().get("foo"); + onMethod2Latch.countDown(); + } } @@ -88,6 +143,15 @@ public class MethodAnnotationTests extends AbstractStateMachineTests { } + @Configuration + static class BeanConfig2 { + + @Bean + public Bean2 bean2() { + return new Bean2(); + } + + } @Configuration static class AnnoConfig { @@ -124,7 +188,12 @@ public class MethodAnnotationTests extends AbstractStateMachineTests { .withExternal() .source(TestStates.S2) .target(TestStates.S3) - .event(TestEvents.E2); + .event(TestEvents.E2) + .and() + .withExternal() + .source(TestStates.S3) + .target(TestStates.S4) + .event(TestEvents.E3); } @Bean