/*
* Copyright 2002-2017 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.integration.config.annotation;
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;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.BridgeFrom;
import org.springframework.integration.annotation.BridgeTo;
import org.springframework.integration.annotation.Filter;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.Role;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.support.SmartLifecycleRoleController;
import org.springframework.integration.util.MessagingAnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* A {@link BeanPostProcessor} implementation that processes method-level
* messaging annotations such as @Transformer, @Splitter, @Router, and @Filter.
*
* @author Mark Fisher
* @author Marius Bogoevici
* @author Artem Bilan
* @author Gary Russell
*/
public class MessagingAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware,
InitializingBean, SmartInitializingSingleton {
protected final Log logger = LogFactory.getLog(this.getClass()); // NOSONAR
private final Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> postProcessors =
new HashMap<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>>();
private final MultiValueMap<String, String> lazyLifecycleRoles = new LinkedMultiValueMap<String, String>();
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isAssignable(ConfigurableListableBeanFactory.class, beanFactory.getClass(),
"a ConfigurableListableBeanFactory is required");
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
protected ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
@Override
public void afterPropertiesSet() {
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
this.postProcessors.put(Filter.class, new FilterAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(Router.class, new RouterAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(InboundChannelAdapter.class,
new InboundChannelAdapterAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(BridgeFrom.class, new BridgeFromAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(BridgeTo.class, new BridgeToAnnotationPostProcessor(this.beanFactory));
Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> customPostProcessors =
setupCustomPostProcessors();
if (!CollectionUtils.isEmpty(customPostProcessors)) {
this.postProcessors.putAll(customPostProcessors);
}
}
protected Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> setupCustomPostProcessors() {
return null;
}
public <A extends Annotation> void addMessagingAnnotationPostProcessor(Class<A> annotation,
MethodAnnotationPostProcessor<A> postProcessor) {
this.postProcessors.put(annotation, postProcessor);
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public void afterSingletonsInstantiated() {
SmartLifecycleRoleController roleController;
try {
roleController = this.beanFactory.getBean(IntegrationContextUtils.INTEGRATION_LIFECYCLE_ROLE_CONTROLLER,
SmartLifecycleRoleController.class);
for (Entry<String, List<String>> entry : this.lazyLifecycleRoles.entrySet()) {
roleController.addLifecyclesToRole(entry.getKey(), entry.getValue());
}
}
catch (NoSuchBeanDefinitionException e) {
this.logger.error("No LifecycleRoleController in the context");
}
}
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
final Class<?> beanClass = this.getBeanClass(bean);
ReflectionUtils.doWithMethods(beanClass, method -> {
Map<Class<? extends Annotation>, List<Annotation>> annotationChains = new HashMap<>();
for (Class<? extends Annotation> annotationType :
MessagingAnnotationPostProcessor.this.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();
processAnnotationTypeOnMethod(bean, beanName, method, annotationType, annotations);
}
}, ReflectionUtils.USER_DECLARED_METHODS);
return bean;
}
protected void processAnnotationTypeOnMethod(Object bean, String beanName, Method method,
Class<? extends Annotation> annotationType, List<Annotation> annotations) {
MethodAnnotationPostProcessor<?> postProcessor =
MessagingAnnotationPostProcessor.this.postProcessors.get(annotationType);
if (postProcessor != null && postProcessor.shouldCreateEndpoint(method, annotations)) {
Method targetMethod = method;
if (AopUtils.isJdkDynamicProxy(bean)) {
try {
targetMethod = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
}
catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Service methods must be extracted to the service "
+ "interface for JdkDynamicProxy. The affected bean is: '" + beanName + "' "
+ "and its method: '" + method + "'", e);
}
}
Object result = postProcessor.postProcess(bean, beanName, targetMethod, annotations);
if (result != null && result instanceof AbstractEndpoint) {
AbstractEndpoint endpoint = (AbstractEndpoint) result;
String autoStartup = MessagingAnnotationUtils.resolveAttribute(annotations, "autoStartup",
String.class);
if (StringUtils.hasText(autoStartup)) {
autoStartup = getBeanFactory().resolveEmbeddedValue(autoStartup);
if (StringUtils.hasText(autoStartup)) {
endpoint.setAutoStartup(Boolean.parseBoolean(autoStartup));
}
}
String phase = MessagingAnnotationUtils.resolveAttribute(annotations, "phase", String.class);
if (StringUtils.hasText(phase)) {
phase = getBeanFactory().resolveEmbeddedValue(phase);
if (StringUtils.hasText(phase)) {
endpoint.setPhase(Integer.parseInt(phase));
}
}
String endpointBeanName = generateBeanName(beanName, method, annotationType);
endpoint.setBeanName(endpointBeanName);
getBeanFactory().registerSingleton(endpointBeanName, endpoint);
getBeanFactory().initializeBean(endpoint, endpointBeanName);
Role role = AnnotationUtils.findAnnotation(method, Role.class);
if (role != null) {
MessagingAnnotationPostProcessor.this.lazyLifecycleRoles.add(role.value(),
endpointBeanName);
}
}
}
}
/**
* @param method the method.
* @param annotationType the annotation type.
* @return the hierarchical list of annotations in top-bottom order.
*/
protected 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;
}
protected 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;
}
protected Class<?> getBeanClass(Object bean) {
Class<?> targetClass = AopUtils.getTargetClass(bean);
return (targetClass != null) ? targetClass : bean.getClass();
}
protected String generateBeanName(String originalBeanName, Method method,
Class<? extends Annotation> annotationType) {
String baseName = originalBeanName + "." + method.getName() + "."
+ ClassUtils.getShortNameAsProperty(annotationType);
String name = baseName;
int count = 1;
while (this.beanFactory.containsBean(name)) {
name = baseName + "#" + (++count);
}
return name;
}
protected Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> getPostProcessors() {
return this.postProcessors;
}
protected MultiValueMap<String, String> getLazyLifecycleRoles() {
return this.lazyLifecycleRoles;
}
}