StateMachineAnnotationPostProcessor should extract correct annotated method
- Fix processing so that only expected annotations are processed and handlers created. - Fixes #510
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-2018 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.
|
||||
@@ -17,9 +17,13 @@ package org.springframework.statemachine.processor;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -37,6 +41,7 @@ import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.statemachine.annotation.OnEventNotAccepted;
|
||||
import org.springframework.statemachine.annotation.OnExtendedStateChanged;
|
||||
@@ -140,53 +145,69 @@ public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, B
|
||||
return bean;
|
||||
}
|
||||
|
||||
|
||||
ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
for (Class<? extends Annotation> ppa : postProcessors.keySet()) {
|
||||
Annotation metaAnnotation = AnnotationUtils.findAnnotation(method, ppa);
|
||||
if (metaAnnotation == null) {
|
||||
continue;
|
||||
}
|
||||
for (Annotation a : AnnotationUtils.getAnnotations(method)) {
|
||||
MethodAnnotationPostProcessor postProcessor = metaAnnotation != null ? postProcessors
|
||||
.get(metaAnnotation.annotationType()) : null;
|
||||
if (postProcessor != null && shouldCreateHandler(a)) {
|
||||
Object result = postProcessor.postProcess(beanClass, bean, beanName, method, metaAnnotation, a);
|
||||
if (result != null && result instanceof StateMachineHandler) {
|
||||
String endpointBeanName = generateBeanName(beanName, method, a.annotationType());
|
||||
|
||||
if (result instanceof BeanNameAware) {
|
||||
((BeanNameAware) result).setBeanName(endpointBeanName);
|
||||
Map<Class<? extends Annotation>, List<Annotation>> annotationChains = new HashMap<>();
|
||||
for (Class<? extends Annotation> annotationType : postProcessors.keySet()) {
|
||||
if (AnnotatedElementUtils.isAnnotated(method, annotationType.getName())) {
|
||||
List<Annotation> annotationChain = getAnnotationChain(method, annotationType);
|
||||
if (annotationChain.size() > 0) {
|
||||
annotationChains.put(annotationType, annotationChain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<Class<? extends Annotation>, List<Annotation>> entry : annotationChains.entrySet()) {
|
||||
Class<? extends Annotation> annotationType = entry.getKey();
|
||||
List<Annotation> annotations = entry.getValue();
|
||||
Annotation metaAnnotation = null;
|
||||
Annotation annotation = null;
|
||||
if (annotations.size() == 2) {
|
||||
annotation = annotations.get(0);
|
||||
metaAnnotation = annotations.get(1);
|
||||
} else if (annotations.size() == 1) {
|
||||
annotation = annotations.get(0);
|
||||
metaAnnotation = annotations.get(0);
|
||||
}
|
||||
|
||||
MethodAnnotationPostProcessor postProcessor = metaAnnotation != null ? postProcessors.get(annotationType) : null;
|
||||
if (postProcessor != null) {
|
||||
// TODO: should change post processor to handle annotation list
|
||||
Object result = postProcessor.postProcess(beanClass, bean, beanName, method, metaAnnotation, annotation);
|
||||
if (result != null && result instanceof StateMachineHandler) {
|
||||
String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType());
|
||||
|
||||
if (result instanceof BeanNameAware) {
|
||||
((BeanNameAware) result).setBeanName(endpointBeanName);
|
||||
}
|
||||
beanFactory.registerSingleton(endpointBeanName, result);
|
||||
if (result instanceof BeanFactoryAware) {
|
||||
((BeanFactoryAware) result).setBeanFactory(beanFactory);
|
||||
}
|
||||
if (result instanceof InitializingBean) {
|
||||
try {
|
||||
((InitializingBean) result).afterPropertiesSet();
|
||||
} catch (Exception e) {
|
||||
throw new BeanInitializationException("failed to initialize annotated component", e);
|
||||
}
|
||||
beanFactory.registerSingleton(endpointBeanName, result);
|
||||
if (result instanceof BeanFactoryAware) {
|
||||
((BeanFactoryAware) result).setBeanFactory(beanFactory);
|
||||
}
|
||||
if (result instanceof InitializingBean) {
|
||||
try {
|
||||
((InitializingBean) result).afterPropertiesSet();
|
||||
} catch (Exception e) {
|
||||
throw new BeanInitializationException("failed to initialize annotated component", e);
|
||||
}
|
||||
}
|
||||
if (result instanceof Lifecycle) {
|
||||
lifecycles.add((Lifecycle) result);
|
||||
if (result instanceof SmartLifecycle && ((SmartLifecycle) result).isAutoStartup()) {
|
||||
((SmartLifecycle) result).start();
|
||||
}
|
||||
}
|
||||
if (result instanceof ApplicationListener) {
|
||||
listeners.add((ApplicationListener) result);
|
||||
}
|
||||
if (result instanceof Lifecycle) {
|
||||
lifecycles.add((Lifecycle) result);
|
||||
if (result instanceof SmartLifecycle && ((SmartLifecycle) result).isAutoStartup()) {
|
||||
((SmartLifecycle) result).start();
|
||||
}
|
||||
}
|
||||
if (result instanceof ApplicationListener) {
|
||||
listeners.add((ApplicationListener) result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
return bean;
|
||||
}
|
||||
|
||||
@@ -229,10 +250,6 @@ public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, B
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
private boolean shouldCreateHandler(Annotation annotation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bean class. Will check if bean is a proxy and
|
||||
* find a class from there as target class, otherwise
|
||||
@@ -256,4 +273,38 @@ public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, B
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
private List<Annotation> getAnnotationChain(Method method, Class<? extends Annotation> annotationType) {
|
||||
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
|
||||
List<Annotation> annotationChain = new LinkedList<Annotation>();
|
||||
Set<Annotation> visited = new HashSet<Annotation>();
|
||||
for (Annotation ann : annotations) {
|
||||
this.recursiveFindAnnotation(annotationType, ann, annotationChain, visited);
|
||||
if (annotationChain.size() > 0) {
|
||||
Collections.reverse(annotationChain);
|
||||
return annotationChain;
|
||||
}
|
||||
}
|
||||
return annotationChain;
|
||||
}
|
||||
|
||||
private boolean recursiveFindAnnotation(Class<? extends Annotation> annotationType, Annotation ann,
|
||||
List<Annotation> annotationChain, Set<Annotation> visited) {
|
||||
if (ann.annotationType().equals(annotationType)) {
|
||||
annotationChain.add(ann);
|
||||
return true;
|
||||
}
|
||||
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
|
||||
if (!ann.equals(metaAnn) && !visited.contains(metaAnn)
|
||||
&& !(metaAnn.annotationType().getPackage().getName().startsWith("java.lang"))) {
|
||||
visited.add(metaAnn); // prevent infinite recursion if the same
|
||||
// annotation is found again
|
||||
if (this.recursiveFindAnnotation(annotationType, metaAnn, annotationChain, visited)) {
|
||||
annotationChain.add(ann);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2018 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.
|
||||
@@ -41,6 +41,7 @@ import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
public class AnnotatedMethodTests extends AbstractStateMachineTests {
|
||||
|
||||
@@ -83,6 +84,8 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
|
||||
assertThat(bean1.onMethod5Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod7Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod9Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod10Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod10Count, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -105,6 +108,8 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
|
||||
assertThat(bean1.onMethod5Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod7Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod9Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod10Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod10Count, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,6 +132,8 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
|
||||
assertThat(bean1.onMethod5Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod7Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod9Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod10Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod10Count, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,6 +150,8 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
|
||||
assertThat(bean1.onMethod8Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
machine.sendEvent(TestEvents.E3);
|
||||
assertThat(bean1.onMethod9Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod10Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(bean1.onMethod10Count, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -193,7 +202,7 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
|
||||
machine.sendEvent(TestEvents.E1);
|
||||
assertThat(bean1.onMethod1Latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
}
|
||||
|
||||
|
||||
@WithStateMachine
|
||||
static class Bean1 {
|
||||
|
||||
@@ -207,6 +216,8 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
|
||||
CountDownLatch onMethod7Latch = new CountDownLatch(1);
|
||||
CountDownLatch onMethod8Latch = new CountDownLatch(1);
|
||||
CountDownLatch onMethod9Latch = new CountDownLatch(1);
|
||||
CountDownLatch onMethod10Latch = new CountDownLatch(1);
|
||||
volatile int onMethod10Count;
|
||||
CountDownLatch onOnTransitionFromS2ToS3Latch = new CountDownLatch(1);
|
||||
|
||||
@OnTransition(target = "S1")
|
||||
@@ -259,6 +270,13 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
|
||||
onMethod9Latch.countDown();
|
||||
}
|
||||
|
||||
@StatesOnTransition(target = TestStates.S30)
|
||||
@Transactional
|
||||
public void method10() {
|
||||
onMethod10Count++;
|
||||
onMethod10Latch.countDown();
|
||||
}
|
||||
|
||||
@OnTransition
|
||||
public void onTransitionFromS2ToS3() {
|
||||
onOnTransitionFromS2ToS3Latch.countDown();
|
||||
@@ -615,7 +633,7 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
|
||||
.withConfiguration()
|
||||
.autoStartup(true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-2018 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.
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package org.springframework.statemachine.processor;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@@ -30,6 +33,7 @@ import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
public class StateMachineAnnotationPostProcessorTests extends AbstractStateMachineTests {
|
||||
|
||||
@@ -39,9 +43,17 @@ public class StateMachineAnnotationPostProcessorTests extends AbstractStateMachi
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithOtherAnnotations() {
|
||||
public void testWithNormalAnnotation() {
|
||||
context.register(Config1.class, BeanConfig1.class);
|
||||
context.refresh();
|
||||
assertThat(context.getBeansOfType(StateMachineHandler.class).size(), is(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNormalAnnotationWithTransactional() {
|
||||
context.register(Config1.class, BeanConfig2.class);
|
||||
context.refresh();
|
||||
assertThat(context.getBeansOfType(StateMachineHandler.class).size(), is(1));
|
||||
}
|
||||
|
||||
@WithStateMachine
|
||||
@@ -67,6 +79,25 @@ public class StateMachineAnnotationPostProcessorTests extends AbstractStateMachi
|
||||
|
||||
}
|
||||
|
||||
@WithStateMachine
|
||||
static class Bean2 {
|
||||
|
||||
CountDownLatch onMethod1Latch = new CountDownLatch(1);
|
||||
CountDownLatch onOnTransitionFromS2ToS3Latch = new CountDownLatch(1);
|
||||
|
||||
@OnTransition(source = "S1", target = "S2")
|
||||
@Transactional
|
||||
public void method1() {
|
||||
onMethod1Latch.countDown();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public String dummy() {
|
||||
return "dummy";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class BeanConfig1 {
|
||||
|
||||
@@ -77,6 +108,16 @@ public class StateMachineAnnotationPostProcessorTests extends AbstractStateMachi
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class BeanConfig2 {
|
||||
|
||||
@Bean
|
||||
public Bean2 bean2() {
|
||||
return new Bean2();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine(name = {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, "fooMachine"})
|
||||
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
@@ -99,5 +140,4 @@ public class StateMachineAnnotationPostProcessorTests extends AbstractStateMachi
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user