From dff42c95acdc811fd28ae95c5527da700828789b Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sat, 21 Mar 2015 09:06:36 +0000 Subject: [PATCH] Better support for @OnTransition - We can't use generic enum types in annotations so adding a feature to support @OnTransition as meta annotation where user can define their own states as proper enums still falling back to @OnTransition. --- .../statemachine/annotation/OnTransition.java | 8 +-- .../MethodAnnotationPostProcessor.java | 5 +- ...chineActivatorAnnotationPostProcessor.java | 5 +- .../StateMachineAnnotationPostProcessor.java | 16 ++++- .../StateMachineOnTransitionHandler.java | 19 +++++- .../support/AbstractStateMachine.java | 68 ++++++++++++------- .../support/StateMachineUtils.java | 12 ++++ .../statemachine/annotation/AnnoStates.java | 22 ++++++ .../annotation/AnnoStatesOnTransition.java | 32 +++++++++ 9 files changed, 149 insertions(+), 38 deletions(-) create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/AnnoStates.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/AnnoStatesOnTransition.java 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 52c2facf..e9badec1 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 @@ -22,14 +22,14 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.METHOD) +@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface OnTransition { - - String source() default ""; - String target() default ""; + String[] source() default {}; + + String[] target() default {}; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java index 1412c072..a07adb9c 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java @@ -37,9 +37,10 @@ public interface MethodAnnotationPostProcessor { * @param bean the bean * @param beanName the bean name * @param method the method + * @param annotation the meta annotation * @param annotation the annotation - * @return the object + * @return the post processed object */ - Object postProcess(Object bean, String beanName, Method method, T annotation); + Object postProcess(Object bean, String beanName, Method method, T metaAnnotation, Annotation annotation); } 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 8cf009c7..4c080d03 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 @@ -15,6 +15,7 @@ */ package org.springframework.statemachine.processor; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import org.springframework.beans.factory.BeanFactory; @@ -39,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); + public Object postProcess(Object bean, String beanName, Method method, OnTransition metaAnnotation, Annotation annotation) { + StateMachineHandler handler = new StateMachineOnTransitionHandler(bean, method, metaAnnotation, annotation); Integer order = findOrder(bean, method); if (order != null) { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java index ea717034..3fec231b 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java @@ -118,10 +118,22 @@ public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, B Annotation[] annotations = AnnotationUtils.getAnnotations(method); for (Annotation annotation : annotations) { - MethodAnnotationPostProcessor postProcessor = postProcessors.get(annotation.annotationType()); + + Annotation metaAnnotation = null; + for (Class ppa : postProcessors.keySet()) { + Annotation a = AnnotationUtils.getAnnotation(annotation,ppa); + if (annotation.getClass().equals(a.getClass())) { + metaAnnotation = a; + } else { + metaAnnotation = a; + } + } + + MethodAnnotationPostProcessor postProcessor = metaAnnotation != null ? postProcessors + .get(metaAnnotation.annotationType()) : null; if (postProcessor != null && shouldCreateHandler(annotation)) { - Object result = postProcessor.postProcess(bean, beanName, method, annotation); + Object result = postProcessor.postProcess(bean, beanName, method, metaAnnotation, annotation); if (result != null && result instanceof StateMachineHandler) { String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType()); 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 5e701db4..265bde3c 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 @@ -15,6 +15,7 @@ */ package org.springframework.statemachine.processor; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import org.springframework.statemachine.annotation.OnTransition; @@ -29,26 +30,38 @@ import org.springframework.statemachine.annotation.OnTransition; */ public class StateMachineOnTransitionHandler extends StateMachineHandler { - private final OnTransition annotation; + private final OnTransition metaAnnotation; + private final Annotation annotation; /** * Instantiates a new state machine on transition handler. * * @param target the target * @param method the method + * @param metaAnnotation the meta annotation * @param annotation the annotation */ - public StateMachineOnTransitionHandler(Object target, Method method, OnTransition annotation) { + public StateMachineOnTransitionHandler(Object target, Method method, OnTransition metaAnnotation, Annotation annotation) { super(target, method); + this.metaAnnotation = metaAnnotation; this.annotation = annotation; } + /** + * Gets the meta annotation. + * + * @return the meta annotation + */ + public OnTransition getMetaAnnotation() { + return metaAnnotation; + } + /** * Gets the annotation. * * @return the annotation */ - public OnTransition getAnnotation() { + public Annotation getAnnotation() { return annotation; } 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 bb4325bd..bfb1b1de 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 @@ -15,7 +15,9 @@ */ package org.springframework.statemachine.support; +import java.lang.annotation.Annotation; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -33,6 +35,7 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.context.Lifecycle; import org.springframework.core.OrderComparator; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; @@ -56,7 +59,6 @@ import org.springframework.statemachine.trigger.TimerTrigger; import org.springframework.statemachine.trigger.Trigger; import org.springframework.statemachine.trigger.TriggerListener; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Base implementation of a {@link StateMachine} loosely modelled from UML state @@ -466,31 +468,10 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport List> handlersList = new ArrayList>(); for (Entry> entry : handlers.entrySet()) { - OnTransition annotation = entry.getValue().getAnnotation(); - String source = annotation.source(); - String target = annotation.target(); - // TODO: need major fixes - boolean handle = false; - if (StringUtils.hasText(source) && StringUtils.hasText(target)) { - if (StateMachineUtils.containsAtleastOneEqualString( - StateMachineUtils.toStringCollection(sourceState.getIds()), source) - && StateMachineUtils.containsAtleastOneEqualString( - StateMachineUtils.toStringCollection(targetState.getIds()), target)) { - handle = true; - } - } else if (StringUtils.hasText(source)) { - if (StateMachineUtils.containsAtleastOneEqualString( - StateMachineUtils.toStringCollection(sourceState.getIds()), source)) { - handle = true; - } - } else if (StringUtils.hasText(target)) { - if (StateMachineUtils.containsAtleastOneEqualString( - StateMachineUtils.toStringCollection(targetState.getIds()), target)) { - handle = true; - } + OnTransition metaAnnotation = entry.getValue().getMetaAnnotation(); + Annotation annotation = entry.getValue().getAnnotation(); - } - if (handle) { + if (transitionHandlerMatch(metaAnnotation, annotation, sourceState, targetState)) { handlersList.add(entry.getValue()); } } @@ -500,6 +481,43 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport return handlersList; } + private boolean transitionHandlerMatch(OnTransition metaAnnotation, Annotation annotation, State sourceState, State targetState) { + String[] msources = metaAnnotation.source(); + String[] mtargets = metaAnnotation.target(); + + Map annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation); + Object source = annotationAttributes.get("source"); + Object target = annotationAttributes.get("target"); + + Collection scoll = StateMachineUtils.toStringCollection(source); + if (scoll.isEmpty()) { + scoll = Arrays.asList(msources); + } + Collection tcoll = StateMachineUtils.toStringCollection(target); + if (tcoll.isEmpty()) { + tcoll = Arrays.asList(mtargets); + } + + boolean handle = false; + if (!scoll.isEmpty() && !tcoll.isEmpty()) { + if (StateMachineUtils.containsAtleastOne(scoll, StateMachineUtils.toStringCollection(sourceState.getIds())) + && StateMachineUtils.containsAtleastOne(tcoll, + StateMachineUtils.toStringCollection(targetState.getIds()))) { + handle = true; + } + } else if (!scoll.isEmpty()) { + if (StateMachineUtils.containsAtleastOne(scoll, StateMachineUtils.toStringCollection(sourceState.getIds()))) { + handle = true; + } + } else if (!tcoll.isEmpty()) { + if (StateMachineUtils.containsAtleastOne(tcoll, StateMachineUtils.toStringCollection(targetState.getIds()))) { + handle = true; + } + } + + return handle; + } + private void registerTriggerListener() { for (final Trigger trigger : triggerToTransitionMap.keySet()) { if (trigger instanceof TimerTrigger) { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineUtils.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineUtils.java index 372e648c..4a8b0eda 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineUtils.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineUtils.java @@ -18,6 +18,8 @@ package org.springframework.statemachine.support; import java.util.ArrayList; import java.util.Collection; +import org.springframework.util.ObjectUtils; + /** * Various utility methods for state machine. * @@ -55,6 +57,16 @@ public abstract class StateMachineUtils { return c; } + public static Collection toStringCollection(Object object) { + Collection c = new ArrayList(); + if (ObjectUtils.isArray(object)) { + for (Object o : ObjectUtils.toObjectArray(object)) { + c.add(o.toString()); + } + } + return c; + } + public static boolean containsAtleastOneEqualString(Collection left, String right) { Collection r = new ArrayList(1); r.add(right); diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/AnnoStates.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/AnnoStates.java new file mode 100644 index 00000000..4150fb96 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/AnnoStates.java @@ -0,0 +1,22 @@ +/* + * 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; + +public enum AnnoStates { + + S1, S2 + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/AnnoStatesOnTransition.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/AnnoStatesOnTransition.java new file mode 100644 index 00000000..396e46cb --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/AnnoStatesOnTransition.java @@ -0,0 +1,32 @@ +/* + * 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.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@OnTransition +public @interface AnnoStatesOnTransition { + + AnnoStates[] source() default {}; + + AnnoStates[] target() default {}; + +}