/* * 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.aopalliance.aop.Advice; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; import org.springframework.aop.TargetSource; import org.springframework.aop.framework.Advised; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor; import org.springframework.aop.support.NameMatchMethodPointcut; import org.springframework.aop.support.NameMatchMethodPointcutAdvisor; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionValidationException; import org.springframework.context.annotation.Bean; import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.task.TaskExecutor; import org.springframework.integration.annotation.IdempotentReceiver; import org.springframework.integration.annotation.Poller; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.MessagePublishingErrorHandler; import org.springframework.integration.config.IntegrationConfigUtils; import org.springframework.integration.context.Orderable; import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.integration.endpoint.AbstractPollingEndpoint; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.endpoint.PollingConsumer; import org.springframework.integration.endpoint.ReactiveStreamsConsumer; import org.springframework.integration.endpoint.SourcePollingChannelAdapter; import org.springframework.integration.handler.AbstractMessageProducingHandler; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.handler.advice.HandleMessageAdvice; import org.springframework.integration.router.AbstractMessageRouter; import org.springframework.integration.scheduling.PollerMetadata; import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.integration.util.MessagingAnnotationUtils; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.core.DestinationResolutionException; import org.springframework.messaging.core.DestinationResolver; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.PeriodicTrigger; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * Base class for Method-level annotation post-processors. * * @author Mark Fisher * @author Artem Bilan * @author Gary Russell */ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation> implements MethodAnnotationPostProcessor<T> { private static final String INPUT_CHANNEL_ATTRIBUTE = "inputChannel"; private static final String ADVICE_CHAIN_ATTRIBUTE = "adviceChain"; protected static final String SEND_TIMEOUT_ATTRIBUTE = "sendTimeout"; protected final Log logger = LogFactory.getLog(this.getClass()); protected final List<String> messageHandlerAttributes = new ArrayList<String>(); protected final ConfigurableListableBeanFactory beanFactory; protected final ConversionService conversionService; protected final DestinationResolver<MessageChannel> channelResolver; protected final Class<T> annotationType; @SuppressWarnings("unchecked") public AbstractMethodAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { Assert.notNull(beanFactory, "'beanFactory' must not be null"); this.messageHandlerAttributes.add(SEND_TIMEOUT_ATTRIBUTE); this.beanFactory = beanFactory; ConversionService conversionService = this.beanFactory.getConversionService(); if (conversionService != null) { this.conversionService = conversionService; } else { this.conversionService = DefaultConversionService.getSharedInstance(); } this.channelResolver = new BeanFactoryChannelResolver(beanFactory); this.annotationType = (Class<T>) GenericTypeResolver.resolveTypeArgument(this.getClass(), MethodAnnotationPostProcessor.class); } @Override public Object postProcess(Object bean, String beanName, Method method, List<Annotation> annotations) { if (this.beanAnnotationAware() && AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { try { resolveTargetBeanFromMethodWithBeanAnnotation(method); } catch (NoSuchBeanDefinitionException e) { if (this.logger.isDebugEnabled()) { this.logger.debug("Skipping endpoint creation; " + e.getMessage() + "; perhaps due to some '@Conditional' annotation."); } return null; } } List<Advice> adviceChain = extractAdviceChain(beanName, annotations); MessageHandler handler = createHandler(bean, method, annotations); if (!CollectionUtils.isEmpty(adviceChain) && handler instanceof AbstractReplyProducingMessageHandler) { ((AbstractReplyProducingMessageHandler) handler).setAdviceChain(adviceChain); } if (handler instanceof Orderable) { Order orderAnnotation = AnnotationUtils.findAnnotation(method, Order.class); if (orderAnnotation != null) { ((Orderable) handler).setOrder(orderAnnotation.value()); } } if (handler instanceof AbstractMessageProducingHandler || handler instanceof AbstractMessageRouter) { String sendTimeout = MessagingAnnotationUtils.resolveAttribute(annotations, "sendTimeout", String.class); if (sendTimeout != null) { Long value = Long.valueOf(this.beanFactory.resolveEmbeddedValue(sendTimeout)); if (handler instanceof AbstractMessageProducingHandler) { ((AbstractMessageProducingHandler) handler).setSendTimeout(value); } else { ((AbstractMessageRouter) handler).setSendTimeout(value); } } } boolean handlerExists = false; if (this.beanAnnotationAware() && AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { Object handlerBean = this.resolveTargetBeanFromMethodWithBeanAnnotation(method); handlerExists = handlerBean != null && handler == handlerBean; } if (!handlerExists) { String handlerBeanName = generateHandlerBeanName(beanName, method); this.beanFactory.registerSingleton(handlerBeanName, handler); handler = (MessageHandler) this.beanFactory.initializeBean(handler, handlerBeanName); } if (AnnotatedElementUtils.isAnnotated(method, IdempotentReceiver.class.getName()) && !AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { String[] interceptors = AnnotationUtils.getAnnotation(method, IdempotentReceiver.class).value(); for (String interceptor : interceptors) { DefaultBeanFactoryPointcutAdvisor advisor = new DefaultBeanFactoryPointcutAdvisor(); advisor.setAdviceBeanName(interceptor); NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); pointcut.setMappedName("handleMessage"); advisor.setPointcut(pointcut); advisor.setBeanFactory(this.beanFactory); if (handler instanceof Advised) { ((Advised) handler).addAdvisor(advisor); } else { ProxyFactory proxyFactory = new ProxyFactory(handler); proxyFactory.addAdvisor(advisor); handler = (MessageHandler) proxyFactory.getProxy(this.beanFactory.getBeanClassLoader()); } } } if (!CollectionUtils.isEmpty(adviceChain)) { for (Advice advice : adviceChain) { if (advice instanceof HandleMessageAdvice) { NameMatchMethodPointcutAdvisor handlerAdvice = new NameMatchMethodPointcutAdvisor(advice); handlerAdvice.addMethodName("handleMessage"); if (handler instanceof Advised) { ((Advised) handler).addAdvisor(handlerAdvice); } else { ProxyFactory proxyFactory = new ProxyFactory(handler); proxyFactory.addAdvisor(handlerAdvice); handler = (MessageHandler) proxyFactory.getProxy(this.beanFactory.getBeanClassLoader()); } } } } AbstractEndpoint endpoint = createEndpoint(handler, method, annotations); if (endpoint != null) { return endpoint; } return handler; } @Override public boolean shouldCreateEndpoint(Method method, List<Annotation> annotations) { String inputChannel = MessagingAnnotationUtils.resolveAttribute(annotations, getInputChannelAttribute(), String.class); boolean createEndpoint = StringUtils.hasText(inputChannel); if (!createEndpoint && beanAnnotationAware()) { boolean isBean = AnnotatedElementUtils.isAnnotated(method, Bean.class.getName()); Assert.isTrue(!isBean, "A channel name in '" + getInputChannelAttribute() + "' is required when " + this.annotationType + " is used on '@Bean' methods."); } return createEndpoint; } protected String getInputChannelAttribute() { return INPUT_CHANNEL_ATTRIBUTE; } protected boolean beanAnnotationAware() { return true; } protected List<Advice> extractAdviceChain(String beanName, List<Annotation> annotations) { List<Advice> adviceChain = null; String[] adviceChainNames = MessagingAnnotationUtils.resolveAttribute(annotations, ADVICE_CHAIN_ATTRIBUTE, String[].class); /* * Note: we don't merge advice chain contents; if the directAnnotation has a non-empty * attribute, it wins. You cannot "remove" an advice chain from a meta-annotation * by setting an empty array on the custom annotation. */ if (adviceChainNames != null && adviceChainNames.length > 0) { adviceChain = new ArrayList<Advice>(); for (String adviceChainName : adviceChainNames) { Object adviceChainBean = this.beanFactory.getBean(adviceChainName); if (adviceChainBean instanceof Advice) { adviceChain.add((Advice) adviceChainBean); } else if (adviceChainBean instanceof Advice[]) { Collections.addAll(adviceChain, (Advice[]) adviceChainBean); } else if (adviceChainBean instanceof Collection) { @SuppressWarnings("unchecked") Collection<Advice> adviceChainEntries = (Collection<Advice>) adviceChainBean; for (Advice advice : adviceChainEntries) { adviceChain.add(advice); } } else { throw new IllegalArgumentException("Invalid advice chain type:" + adviceChainName.getClass().getName() + " for bean '" + beanName + "'"); } } } return adviceChain; } protected AbstractEndpoint createEndpoint(MessageHandler handler, Method method, List<Annotation> annotations) { AbstractEndpoint endpoint = null; String inputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, getInputChannelAttribute(), String.class); if (StringUtils.hasText(inputChannelName)) { MessageChannel inputChannel; try { inputChannel = this.channelResolver.resolveDestination(inputChannelName); } catch (DestinationResolutionException e) { if (e.getCause() instanceof NoSuchBeanDefinitionException) { inputChannel = new DirectChannel(); this.beanFactory.registerSingleton(inputChannelName, inputChannel); inputChannel = (MessageChannel) this.beanFactory.initializeBean(inputChannel, inputChannelName); } else { throw e; } } Assert.notNull(inputChannel, "failed to resolve inputChannel '" + inputChannelName + "'"); endpoint = doCreateEndpoint(handler, inputChannel, annotations); } return endpoint; } protected AbstractEndpoint doCreateEndpoint(MessageHandler handler, MessageChannel inputChannel, List<Annotation> annotations) { AbstractEndpoint endpoint; if (inputChannel instanceof PollableChannel) { PollingConsumer pollingConsumer = new PollingConsumer((PollableChannel) inputChannel, handler); configurePollingEndpoint(pollingConsumer, annotations); endpoint = pollingConsumer; } else { Poller[] pollers = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller[].class); Assert.state(ObjectUtils.isEmpty(pollers), "A '@Poller' should not be specified for Annotation-based " + "endpoint, since '" + inputChannel + "' is a SubscribableChannel (not pollable)."); if (inputChannel instanceof Publisher) { endpoint = new ReactiveStreamsConsumer(inputChannel, handler); } else { endpoint = new EventDrivenConsumer((SubscribableChannel) inputChannel, handler); } } return endpoint; } protected void configurePollingEndpoint(AbstractPollingEndpoint pollingEndpoint, List<Annotation> annotations) { PollerMetadata pollerMetadata = null; Poller[] pollers = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller[].class); if (!ObjectUtils.isEmpty(pollers)) { Assert.state(pollers.length == 1, "The 'poller' for an Annotation-based endpoint can have only one '@Poller'."); Poller poller = pollers[0]; String ref = poller.value(); String triggerRef = poller.trigger(); String executorRef = poller.taskExecutor(); String fixedDelayValue = this.beanFactory.resolveEmbeddedValue(poller.fixedDelay()); String fixedRateValue = this.beanFactory.resolveEmbeddedValue(poller.fixedRate()); String maxMessagesPerPollValue = this.beanFactory.resolveEmbeddedValue(poller.maxMessagesPerPoll()); String cron = this.beanFactory.resolveEmbeddedValue(poller.cron()); String errorChannel = this.beanFactory.resolveEmbeddedValue(poller.errorChannel()); if (StringUtils.hasText(ref)) { Assert.state(!StringUtils.hasText(triggerRef) && !StringUtils.hasText(executorRef) && !StringUtils.hasText(cron) && !StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue) && !StringUtils.hasText(maxMessagesPerPollValue), "The '@Poller' 'ref' attribute is mutually exclusive with other attributes."); pollerMetadata = this.beanFactory.getBean(ref, PollerMetadata.class); } else { pollerMetadata = new PollerMetadata(); if (StringUtils.hasText(maxMessagesPerPollValue)) { pollerMetadata.setMaxMessagesPerPoll(Long.parseLong(maxMessagesPerPollValue)); } else if (pollingEndpoint instanceof SourcePollingChannelAdapter) { // SPCAs default to 1 message per poll pollerMetadata.setMaxMessagesPerPoll(1); } if (StringUtils.hasText(executorRef)) { pollerMetadata.setTaskExecutor(this.beanFactory.getBean(executorRef, TaskExecutor.class)); } Trigger trigger = null; if (StringUtils.hasText(triggerRef)) { Assert.state(!StringUtils.hasText(cron) && !StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue), "The '@Poller' 'trigger' attribute is mutually exclusive with other attributes."); trigger = this.beanFactory.getBean(triggerRef, Trigger.class); } else if (StringUtils.hasText(cron)) { Assert.state(!StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue), "The '@Poller' 'cron' attribute is mutually exclusive with other attributes."); trigger = new CronTrigger(cron); } else if (StringUtils.hasText(fixedDelayValue)) { Assert.state(!StringUtils.hasText(fixedRateValue), "The '@Poller' 'fixedDelay' attribute is mutually exclusive with other attributes."); trigger = new PeriodicTrigger(Long.parseLong(fixedDelayValue)); } else if (StringUtils.hasText(fixedRateValue)) { trigger = new PeriodicTrigger(Long.parseLong(fixedRateValue)); ((PeriodicTrigger) trigger).setFixedRate(true); } //'Trigger' can be null. 'PollingConsumer' does fallback to the 'new PeriodicTrigger(10)'. pollerMetadata.setTrigger(trigger); if (StringUtils.hasText(errorChannel)) { MessagePublishingErrorHandler errorHandler = new MessagePublishingErrorHandler(); errorHandler.setDefaultErrorChannelName(errorChannel); errorHandler.setBeanFactory(this.beanFactory); pollerMetadata.setErrorHandler(errorHandler); } } } else { pollerMetadata = PollerMetadata.getDefaultPollerMetadata(this.beanFactory); Assert.notNull(pollerMetadata, "No poller has been defined for Annotation-based endpoint, " + "and no default poller is available within the context."); } pollingEndpoint.setTaskExecutor(pollerMetadata.getTaskExecutor()); pollingEndpoint.setTrigger(pollerMetadata.getTrigger()); pollingEndpoint.setAdviceChain(pollerMetadata.getAdviceChain()); pollingEndpoint.setMaxMessagesPerPoll(pollerMetadata.getMaxMessagesPerPoll()); pollingEndpoint.setErrorHandler(pollerMetadata.getErrorHandler()); if (pollingEndpoint instanceof PollingConsumer) { ((PollingConsumer) pollingEndpoint).setReceiveTimeout(pollerMetadata.getReceiveTimeout()); } pollingEndpoint.setTransactionSynchronizationFactory(pollerMetadata.getTransactionSynchronizationFactory()); } protected String generateHandlerBeanName(String originalBeanName, Method method) { String baseName = originalBeanName + "." + method.getName() + "." + ClassUtils.getShortNameAsProperty(this.annotationType); String name = baseName; int count = 1; while (this.beanFactory.containsBean(name)) { name = baseName + "#" + (++count); } return name + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX; } protected void setOutputChannelIfPresent(List<Annotation> annotations, AbstractReplyProducingMessageHandler handler) { String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel", String.class); if (StringUtils.hasText(outputChannelName)) { handler.setOutputChannelName(outputChannelName); } } protected Object resolveTargetBeanFromMethodWithBeanAnnotation(Method method) { String id = resolveTargetBeanName(method); return this.beanFactory.getBean(id); } protected String resolveTargetBeanName(Method method) { String id = null; String[] names = AnnotationUtils.getAnnotation(method, Bean.class).name(); if (!ObjectUtils.isEmpty(names)) { id = names[0]; } if (!StringUtils.hasText(id)) { id = method.getName(); } return id; } @SuppressWarnings("unchecked") protected <H> H extractTypeIfPossible(Object targetObject, Class<H> expectedType) { if (targetObject == null) { return null; } if (expectedType.isAssignableFrom(targetObject.getClass())) { return (H) targetObject; } if (targetObject instanceof Advised) { TargetSource targetSource = ((Advised) targetObject).getTargetSource(); if (targetSource == null) { return null; } try { return extractTypeIfPossible(targetSource.getTarget(), expectedType); } catch (Exception e) { throw new IllegalStateException(e); } } return null; } protected void checkMessageHandlerAttributes(String handlerBeanName, List<Annotation> annotations) { for (String attribute : this.messageHandlerAttributes) { for (Annotation annotation : annotations) { Object value = AnnotationUtils.getValue(annotation, attribute); if (MessagingAnnotationUtils.hasValue(value)) { throw new BeanDefinitionValidationException("The MessageHandler [" + handlerBeanName + "] can not" + " be populated because of ambiguity with annotation attributes " + this.messageHandlerAttributes + " which are not allowed when an integration annotation " + "is used with a @Bean definition for a MessageHandler." + "\nThe attribute causing the ambiguity is: [" + attribute + "]." + "\nUse the appropriate setter on the MessageHandler directly when configuring an " + "endpoint this way."); } } } } /** * Subclasses must implement this method to create the MessageHandler. * * @param bean The bean. * @param method The method. * @param annotations The messaging annotation (or meta-annotation hierarchy) on the method. * @return The MessageHandler. */ protected abstract MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations); }