diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 95cda945..253740fb 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -831,6 +831,14 @@ application context by using annotation `name` field. include::samples/DocsConfigurationSampleTests4.java[tags=snippetAA] ---- +_@WithStateMachine_ can also be used as a meta-annotation as shown +above. In this case you could annotate your bean with _WithMyBean_. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests4.java[tags=snippetAAA] +---- + [NOTE] ==== Return type of these methods doesn't matter and is effectively 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 e46038d7..a316f495 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 @@ -17,11 +17,8 @@ package org.springframework.statemachine.processor; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -52,7 +49,7 @@ import org.springframework.statemachine.annotation.OnStateMachineStop; import org.springframework.statemachine.annotation.OnTransition; import org.springframework.statemachine.annotation.OnTransitionEnd; import org.springframework.statemachine.annotation.OnTransitionStart; -import org.springframework.stereotype.Component; +import org.springframework.statemachine.annotation.WithStateMachine; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -137,11 +134,13 @@ public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, B Assert.notNull(beanFactory, "BeanFactory must not be null"); final Class beanClass = getBeanClass(bean); - if (!isStereotype(beanClass)) { - // we only post-process stereotype components + if (AnnotationUtils.findAnnotation(beanClass, WithStateMachine.class) == null) { + // we only post-process beans having WithStateMachine + // in it or as a meta annotation return bean; } + ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() { @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -247,28 +246,6 @@ public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, B return (targetClass != null) ? targetClass : bean.getClass(); } - /** - * Checks if class is a stereotype meaning if there is - * a Component annotation present. - * - * @param beanClass the bean class - * @return true, if is stereotype - */ - private boolean isStereotype(Class beanClass) { - List annotations = new ArrayList(Arrays.asList(beanClass.getAnnotations())); - Class[] interfaces = beanClass.getInterfaces(); - for (Class iface : interfaces) { - annotations.addAll(Arrays.asList(iface.getAnnotations())); - } - for (Annotation annotation : annotations) { - Class annotationType = annotation.annotationType(); - if (annotationType.equals(Component.class) || annotationType.isAnnotationPresent(Component.class)) { - return true; - } - } - return false; - } - private String generateBeanName(String originalBeanName, Method method, Class annotationType) { String baseName = originalBeanName + "." + method.getName() + "." + ClassUtils.getShortNameAsProperty(annotationType); String name = baseName; diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/ClassAnnotationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/ClassAnnotationTests.java index 083ec6cf..6da84bca 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/ClassAnnotationTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/ClassAnnotationTests.java @@ -18,6 +18,10 @@ package org.springframework.statemachine.annotation; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -41,7 +45,7 @@ public class ClassAnnotationTests extends AbstractStateMachineTests { @SuppressWarnings("unchecked") public void testClassAnnotations() throws Exception { AnnotationConfigApplicationContext context = - new AnnotationConfigApplicationContext(BaseConfig.class, BeanConfig.class, FooConfig.class, BarConfig.class); + new AnnotationConfigApplicationContext(BaseConfig.class, BeanConfig1.class, FooConfig.class, BarConfig.class); ObjectStateMachine fooMachine = context.getBean("fooMachine", ObjectStateMachine.class); @@ -76,6 +80,28 @@ public class ClassAnnotationTests extends AbstractStateMachineTests { context.close(); } + @Test + @SuppressWarnings("unchecked") + public void testClassAnnotationsWithMeta() throws Exception { + AnnotationConfigApplicationContext context = + new AnnotationConfigApplicationContext(BaseConfig.class, BeanConfig2.class, JeeConfig.class, FooConfig.class); + + ObjectStateMachine jeeMachine = + context.getBean("jeeMachine", ObjectStateMachine.class); + + assertThat(context.containsBean("fooMachine"), is(true)); + assertThat(context.containsBean("jeeMachine"), is(true)); + + JeeBean jeeBean = context.getBean(JeeBean.class); + FooBean fooBean = context.getBean(FooBean.class); + fooBean.resetMethodLatch(); + jeeMachine.start(); + jeeMachine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(jeeBean.onJeeMethodLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(fooBean.onFooMethodLatch.await(2, TimeUnit.SECONDS), is(false)); + context.close(); + } + @WithStateMachine(name = "fooMachine") static class FooBean { @@ -104,8 +130,26 @@ public class ClassAnnotationTests extends AbstractStateMachineTests { } + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @WithStateMachine(name = "jeeMachine") + public @interface WithJeeMachine { + } + + @WithJeeMachine + static class JeeBean { + + CountDownLatch onJeeMethodLatch = new CountDownLatch(1); + + @OnTransition(source = "S1", target = "S2") + public void jeeMethod() { + onJeeMethodLatch.countDown(); + } + + } + @Configuration - static class BeanConfig { + static class BeanConfig1 { @Bean public FooBean fooBean() { @@ -119,6 +163,21 @@ public class ClassAnnotationTests extends AbstractStateMachineTests { } + @Configuration + static class BeanConfig2 { + + @Bean + public FooBean fooBean() { + return new FooBean(); + } + + @Bean + public JeeBean jeeBean() { + return new JeeBean(); + } + + } + @Configuration @EnableStateMachine(name = "fooMachine") static class FooConfig extends EnumStateMachineConfigurerAdapter { @@ -189,4 +248,39 @@ public class ClassAnnotationTests extends AbstractStateMachineTests { } + @Configuration + @EnableStateMachine(name = "jeeMachine") + static class JeeConfig extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S2); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .guard(testGuard()) + .action(testAction()); + } + + @Bean + public TestGuard testGuard() { + return new TestGuard(); + } + + @Bean + public TestAction testAction() { + return new TestAction(); + } + + } + } 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 index ff6731f5..e19851a9 100644 --- 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 @@ -60,6 +60,14 @@ public class DocsConfigurationSampleTests4 extends AbstractStateMachineTests { } // end::snippetAA[] +// tag::snippetAAA[] + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @WithStateMachine(name = "myMachineBeanName") + public @interface WithMyBean { + } +// end::snippetAAA[] + // tag::snippetB[] @WithStateMachine public class Bean3 { diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/AnnotatedMethodTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/AnnotatedMethodTests.java index a7e9b14f..e00e469a 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/AnnotatedMethodTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/AnnotatedMethodTests.java @@ -143,6 +143,18 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests { assertThat(bean1.onMethod9Latch.await(2, TimeUnit.SECONDS), is(true)); } + @Test + @SuppressWarnings("unchecked") + public void testMetaAnnotation1() throws Exception { + context.register(Config1.class, BeanConfig2.class); + context.refresh(); + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + Bean2 bean2 = context.getBean(Bean2.class); + machine.start(); + assertThat(bean2.onMethod0Latch.await(2, TimeUnit.SECONDS), is(true)); + } + @WithStateMachine static class Bean1 { @@ -220,6 +232,23 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests { } + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @WithStateMachine + public @interface WithStateMachineMeta1 { + } + + @WithStateMachineMeta1 + static class Bean2 { + + CountDownLatch onMethod0Latch = new CountDownLatch(1); + + @OnTransition(target = "S1") + public void method0() { + onMethod0Latch.countDown(); + } + } + @Configuration static class BeanConfig1 { @@ -230,6 +259,16 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests { } + @Configuration + static class BeanConfig2 { + + @Bean + public Bean2 bean2() { + return new Bean2(); + } + + } + @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @OnTransition