INT-3376 Support Meta-Annotation Att. Override
JIRA: https://jira.spring.io/browse/INT-3376 Allow user annotations with Messaging Meta-Annotations override/supplement attributes set on the meta-Annotation. PR Comments and More - Do not create a bean definition for the annotation (interface) when `MessagingGateway` is used as a meta-Annotation. - For a `MessagingGateway` restore attributes overridden with empty values - Other PR comments. Fully support meta-Annotation hierarchy. Polishing: * Upgrade JRuby to 1.7.12 * Replace `AnnotationFinder` with moving its methods to `MessagingAnnotationUtils` * Refactor aggregator classes to use `MessagingAnnotationUtils` to determine the method for annotation * Fix `IntegrationComponentScanRegistrar` to skip annotation classes * Fix `MessagingGatewayRegistrar#replaceEmptyOverrides` * Improve `MessagingAnnotationPostProcessor` to use `ReflectionUtils.USER_DECLARED_METHODS` `MethodFilter` * Change the `MethodAnnotationPostProcessor` hierarchy to use just list of annotations to process attributes
This commit is contained in:
committed by
Artem Bilan
parent
97d1d389a9
commit
24f176c897
@@ -80,7 +80,7 @@ subprojects { subproject ->
|
||||
javaxMailVersion = '1.4.7'
|
||||
jmsApiVersion = '1.1-rev-1'
|
||||
jpaApiVersion = '2.0.0'
|
||||
jrubyVersion = '1.7.8'
|
||||
jrubyVersion = '1.7.12'
|
||||
jschVersion = '0.1.51'
|
||||
jsonpathVersion = '0.9.1'
|
||||
junitVersion = '4.11'
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
|
||||
/**
|
||||
* Helper to provide common features for inspecting objects and locating annotated methods.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Gunnar Hillert
|
||||
* @author Soby Chacko
|
||||
*
|
||||
*/
|
||||
abstract class AnnotationFinder {
|
||||
|
||||
public static Method findAnnotatedMethod(Object target, final Class<? extends Annotation> annotationType) {
|
||||
final AtomicReference<Method> reference = new AtomicReference<Method>();
|
||||
ReflectionUtils.doWithMethods(getTargetClass(target), new MethodCallback() {
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
if (AnnotationUtils.findAnnotation(method, annotationType) != null) {
|
||||
reference.set(method);
|
||||
}
|
||||
}
|
||||
});
|
||||
return reference.get();
|
||||
}
|
||||
|
||||
private static Class<?> getTargetClass(Object targetObject) {
|
||||
Class<?> targetClass = targetObject.getClass();
|
||||
if (AopUtils.isAopProxy(targetObject)) {
|
||||
targetClass = AopUtils.getTargetClass(targetObject);
|
||||
}
|
||||
else if (ClassUtils.isCglibProxyClass(targetClass)) {
|
||||
Class<?> superClass = targetObject.getClass().getSuperclass();
|
||||
if (!Object.class.equals(superClass)) {
|
||||
targetClass = superClass;
|
||||
}
|
||||
}
|
||||
return targetClass;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.aggregator.CorrelationStrategy;
|
||||
import org.springframework.integration.aggregator.HeaderAttributeCorrelationStrategy;
|
||||
import org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy;
|
||||
import org.springframework.integration.config.annotation.MessagingAnnotationUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -60,7 +61,8 @@ public class CorrelationStrategyFactoryBean implements FactoryBean<CorrelationSt
|
||||
delegate = new MethodInvokingCorrelationStrategy(target, methodName);
|
||||
}
|
||||
else {
|
||||
Method method = AnnotationFinder.findAnnotatedMethod(target, org.springframework.integration.annotation.CorrelationStrategy.class);
|
||||
Method method = MessagingAnnotationUtils.findAnnotatedMethod(target,
|
||||
org.springframework.integration.annotation.CorrelationStrategy.class);
|
||||
if (method != null) {
|
||||
delegate = new MethodInvokingCorrelationStrategy(target, method);
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
package org.springframework.integration.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
@@ -41,12 +43,15 @@ import org.springframework.util.StringUtils;
|
||||
* @author Artem Bilan
|
||||
* @since 4.0
|
||||
*/
|
||||
public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
|
||||
public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
ResourceLoaderAware, BeanClassLoaderAware {
|
||||
|
||||
private final Map<TypeFilter, ImportBeanDefinitionRegistrar> componentRegistrars = new HashMap<TypeFilter, ImportBeanDefinitionRegistrar>();
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
public IntegrationComponentScanRegistrar() {
|
||||
this.componentRegistrars.put(new AnnotationTypeFilter(MessagingGateway.class, true), new MessagingGatewayRegistrar());
|
||||
}
|
||||
@@ -56,6 +61,11 @@ public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRe
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
Map<String, Object> componentScan = importingClassMetadata
|
||||
@@ -84,7 +94,23 @@ public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRe
|
||||
|
||||
@Override
|
||||
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
|
||||
return beanDefinition.getMetadata().isIndependent();
|
||||
if (beanDefinition.getMetadata().isIndependent()) {
|
||||
// TODO until SPR-11711 will be resolved
|
||||
if (beanDefinition.getMetadata().isInterface() &&
|
||||
beanDefinition.getMetadata().getInterfaceNames().length == 1 &&
|
||||
Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
|
||||
try {
|
||||
Class<?> target = ClassUtils.forName(beanDefinition.getMetadata().getClassName(), classLoader);
|
||||
return !target.isAnnotation();
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Could not load target class: " + beanDefinition.getMetadata().getClassName(), e);
|
||||
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,14 @@
|
||||
package org.springframework.integration.config;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.util.ArrayList;
|
||||
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.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
@@ -31,9 +38,11 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.integration.config.annotation.MessagingAnnotationUtils;
|
||||
import org.springframework.integration.gateway.GatewayMethodMetadata;
|
||||
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -42,16 +51,21 @@ import org.springframework.util.StringUtils;
|
||||
* and to register {@link BeanDefinition} {@link GatewayProxyFactoryBean}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(MessagingGatewayRegistrar.class);
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
if (importingClassMetadata != null && importingClassMetadata.isAnnotated(MessagingGateway.class.getName())) {
|
||||
Assert.isTrue(importingClassMetadata.isInterface(),
|
||||
"@MessagingGateway can only be specified on an interface");
|
||||
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(MessagingGateway.class.getName());
|
||||
Assert.isTrue(importingClassMetadata.isInterface(), "@MessagingGateway can only be specified on an interface");
|
||||
List<MultiValueMap<String, Object>> valuesHierarchy = captureMetaAnnotationValues(importingClassMetadata);
|
||||
Map<String, Object> annotationAttributes =
|
||||
importingClassMetadata.getAnnotationAttributes(MessagingGateway.class.getName());
|
||||
replaceEmptyOverrides(valuesHierarchy, annotationAttributes);
|
||||
annotationAttributes.put("serviceInterface", importingClassMetadata.getClassName());
|
||||
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(this.parse(annotationAttributes), registry);
|
||||
@@ -73,7 +87,8 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar
|
||||
|
||||
boolean hasMapper = StringUtils.hasText(mapper);
|
||||
boolean hasDefaultPayloadExpression = StringUtils.hasText(defaultPayloadExpression);
|
||||
Assert.state(!hasMapper || !hasDefaultPayloadExpression, "'defaultPayloadExpression' is not allowed when a 'mapper' is provided");
|
||||
Assert.state(!hasMapper || !hasDefaultPayloadExpression,
|
||||
"'defaultPayloadExpression' is not allowed when a 'mapper' is provided");
|
||||
|
||||
boolean hasDefaultHeaders = !ObjectUtils.isEmpty(defaultHeaders);
|
||||
Assert.state(!hasMapper || !hasDefaultHeaders, "'defaultHeaders' are not allowed when a 'mapper' is provided");
|
||||
@@ -92,11 +107,14 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar
|
||||
boolean hasValue = StringUtils.hasText(headerValue);
|
||||
|
||||
if (!(hasValue ^ StringUtils.hasText(headerExpression))) {
|
||||
throw new BeanDefinitionStoreException("exactly one of 'value' or 'expression' is required on a gateway's header.");
|
||||
throw new BeanDefinitionStoreException("exactly one of 'value' or 'expression' " +
|
||||
"is required on a gateway's header.");
|
||||
}
|
||||
|
||||
BeanDefinition expressionDef = new RootBeanDefinition(hasValue ? LiteralExpression.class : ExpressionFactoryBean.class);
|
||||
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(hasValue ? headerValue : headerExpression);
|
||||
BeanDefinition expressionDef =
|
||||
new RootBeanDefinition(hasValue ? LiteralExpression.class : ExpressionFactoryBean.class);
|
||||
expressionDef.getConstructorArgumentValues()
|
||||
.addGenericArgumentValue(hasValue ? headerValue : headerExpression);
|
||||
|
||||
headerExpressions.put((String) header.get("name"), expressionDef);
|
||||
}
|
||||
@@ -140,4 +158,49 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar
|
||||
return new BeanDefinitionHolder(gatewayProxyBuilder.getBeanDefinition(), id);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO until SPR-11710 will be resolved.
|
||||
* Captures the meta-annotation attribute values, in order.
|
||||
* @param importingClassMetadata The importing class metadata
|
||||
* @return The captured values.
|
||||
*/
|
||||
private List<MultiValueMap<String, Object>> captureMetaAnnotationValues(AnnotationMetadata importingClassMetadata) {
|
||||
Set<String> directAnnotations = importingClassMetadata.getAnnotationTypes();
|
||||
List<MultiValueMap<String, Object>> valuesHierarchy = new ArrayList<MultiValueMap<String, Object>>();
|
||||
// Need to grab the values now; see SPR-11710
|
||||
for (String ann : directAnnotations) {
|
||||
Set<String> chain = importingClassMetadata.getMetaAnnotationTypes(ann);
|
||||
if (chain.contains(MessagingGateway.class.getName())) {
|
||||
for (String meta : chain) {
|
||||
valuesHierarchy.add(importingClassMetadata.getAllAnnotationAttributes(meta));
|
||||
}
|
||||
}
|
||||
}
|
||||
return valuesHierarchy;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO until SPR-11709 will be resolved.
|
||||
* For any empty values, traverses back up the meta-annotation hierarchy to
|
||||
* see if a value has been overridden to empty, and replaces the first such value found.
|
||||
* @param valuesHierarchy The values hierarchy in order.
|
||||
* @param annotationAttributes The current attribute values.
|
||||
*/
|
||||
private void replaceEmptyOverrides(List<MultiValueMap<String, Object>> valuesHierarchy,
|
||||
Map<String, Object> annotationAttributes) {
|
||||
for (Entry<String, Object> entry : annotationAttributes.entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if (!MessagingAnnotationUtils.hasValue(value)) {
|
||||
// see if we overrode a value that was higher in the annotation chain
|
||||
for (MultiValueMap<String, Object> metaAttributesMap : valuesHierarchy) {
|
||||
Object newValue = metaAttributesMap.getFirst(entry.getKey());
|
||||
if (MessagingAnnotationUtils.hasValue(newValue)) {
|
||||
annotationAttributes.put(entry.getKey(), newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.integration.aggregator.MethodInvokingReleaseStrategy;
|
||||
import org.springframework.integration.aggregator.ReleaseStrategy;
|
||||
import org.springframework.integration.aggregator.SequenceSizeReleaseStrategy;
|
||||
import org.springframework.integration.config.annotation.MessagingAnnotationUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -65,7 +66,8 @@ public class ReleaseStrategyFactoryBean implements FactoryBean<ReleaseStrategy>
|
||||
this.delegate = new MethodInvokingReleaseStrategy(target, methodName);
|
||||
}
|
||||
else {
|
||||
Method method = AnnotationFinder.findAnnotatedMethod(target, org.springframework.integration.annotation.ReleaseStrategy.class);
|
||||
Method method = MessagingAnnotationUtils.findAnnotatedMethod(target,
|
||||
org.springframework.integration.annotation.ReleaseStrategy.class);
|
||||
if (method != null) {
|
||||
this.delegate = new MethodInvokingReleaseStrategy(target, method);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.aopalliance.aop.Advice;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.Environment;
|
||||
@@ -77,19 +78,23 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
|
||||
|
||||
protected final DestinationResolver<MessageChannel> channelResolver;
|
||||
|
||||
protected final Class<T> annotationType;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public AbstractMethodAnnotationPostProcessor(ListableBeanFactory beanFactory, Environment environment) {
|
||||
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
||||
this.beanFactory = beanFactory;
|
||||
this.environment = environment;
|
||||
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, T annotation) {
|
||||
MessageHandler handler = this.createHandler(bean, method, annotation);
|
||||
this.setAdviceChainIfPresent(beanName, annotation, handler);
|
||||
public Object postProcess(Object bean, String beanName, Method method, List<Annotation> annotations) {
|
||||
MessageHandler handler = this.createHandler(bean, method, annotations);
|
||||
this.setAdviceChainIfPresent(beanName, annotations, handler);
|
||||
if (handler instanceof Orderable) {
|
||||
Order orderAnnotation = AnnotationUtils.findAnnotation(method, Order.class);
|
||||
if (orderAnnotation != null) {
|
||||
@@ -97,12 +102,12 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
|
||||
}
|
||||
}
|
||||
if (beanFactory instanceof ConfigurableListableBeanFactory) {
|
||||
String handlerBeanName = this.generateHandlerBeanName(beanName, method, annotation.annotationType());
|
||||
String handlerBeanName = this.generateHandlerBeanName(beanName, method);
|
||||
ConfigurableListableBeanFactory listableBeanFactory = (ConfigurableListableBeanFactory) beanFactory;
|
||||
listableBeanFactory.registerSingleton(handlerBeanName, handler);
|
||||
handler = (MessageHandler) listableBeanFactory.initializeBean(handler, handlerBeanName);
|
||||
}
|
||||
AbstractEndpoint endpoint = this.createEndpoint(handler, annotation);
|
||||
AbstractEndpoint endpoint = this.createEndpoint(handler, annotations);
|
||||
if (endpoint != null) {
|
||||
return endpoint;
|
||||
}
|
||||
@@ -110,8 +115,14 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
|
||||
}
|
||||
|
||||
|
||||
protected final void setAdviceChainIfPresent(String beanName, T annotation, MessageHandler handler) {
|
||||
String[] adviceChainNames = (String[]) AnnotationUtils.getValue(annotation, ADVICE_CHAIN_ATTRIBUTE);
|
||||
protected final void setAdviceChainIfPresent(String beanName, List<Annotation> annotations, MessageHandler handler) {
|
||||
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) {
|
||||
if (!(handler instanceof AbstractReplyProducingMessageHandler)) {
|
||||
throw new IllegalArgumentException("Cannot apply advice chain to " + handler.getClass().getName());
|
||||
@@ -141,9 +152,10 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
|
||||
}
|
||||
}
|
||||
|
||||
private AbstractEndpoint createEndpoint(MessageHandler handler, T annotation) {
|
||||
private AbstractEndpoint createEndpoint(MessageHandler handler, List<Annotation> annotations) {
|
||||
AbstractEndpoint endpoint = null;
|
||||
String inputChannelName = (String) AnnotationUtils.getValue(annotation, INPUT_CHANNEL_ATTRIBUTE);
|
||||
String inputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, INPUT_CHANNEL_ATTRIBUTE,
|
||||
String.class);
|
||||
if (StringUtils.hasText(inputChannelName)) {
|
||||
MessageChannel inputChannel;
|
||||
try {
|
||||
@@ -161,22 +173,22 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
|
||||
|
||||
if (inputChannel instanceof PollableChannel) {
|
||||
PollingConsumer pollingConsumer = new PollingConsumer((PollableChannel) inputChannel, handler);
|
||||
this.configurePollingEndpoint(pollingConsumer, annotation);
|
||||
this.configurePollingEndpoint(pollingConsumer, annotations);
|
||||
endpoint = pollingConsumer;
|
||||
}
|
||||
else {
|
||||
Poller[] pollers = (Poller[]) AnnotationUtils.getValue(annotation, "poller");
|
||||
Assert.state(ObjectUtils.isEmpty(pollers), "A '@Poller' should not be specified for for Annotation-based endpoint, " +
|
||||
"since '" + inputChannel + "' is a SubscribableChannel (not pollable).");
|
||||
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).");
|
||||
endpoint = new EventDrivenConsumer((SubscribableChannel) inputChannel, handler);
|
||||
}
|
||||
}
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
protected void configurePollingEndpoint(AbstractPollingEndpoint pollingEndpoint, T annotation) {
|
||||
protected void configurePollingEndpoint(AbstractPollingEndpoint pollingEndpoint, List<Annotation> annotations) {
|
||||
PollerMetadata pollerMetadata = null;
|
||||
Poller[] pollers = (Poller[]) AnnotationUtils.getValue(annotation, "poller");
|
||||
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];
|
||||
@@ -190,9 +202,9 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
|
||||
String cron = this.environment.resolvePlaceholders(poller.cron());
|
||||
|
||||
if (StringUtils.hasText(ref)) {
|
||||
Assert.state(!StringUtils.hasText(triggerRef) && !StringUtils.hasText(executorRef) && !StringUtils.hasText(cron)
|
||||
&& !StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue)
|
||||
&& !StringUtils.hasText(maxMessagesPerPollValue),
|
||||
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);
|
||||
}
|
||||
@@ -206,7 +218,8 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
|
||||
}
|
||||
Trigger trigger = null;
|
||||
if (StringUtils.hasText(triggerRef)) {
|
||||
Assert.state(!StringUtils.hasText(cron) && !StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue),
|
||||
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);
|
||||
}
|
||||
@@ -246,8 +259,9 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
|
||||
pollingEndpoint.setTransactionSynchronizationFactory(pollerMetadata.getTransactionSynchronizationFactory());
|
||||
}
|
||||
|
||||
protected String generateHandlerBeanName(String originalBeanName, Method method, Class<? extends Annotation> annotationType) {
|
||||
String baseName = originalBeanName + "." + method.getName() + "." + ClassUtils.getShortNameAsProperty(annotationType);
|
||||
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)) {
|
||||
@@ -256,14 +270,20 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
|
||||
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.setOutputChannel(this.channelResolver.resolveDestination(outputChannelName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses must implement this method to create the MessageHandler.
|
||||
*
|
||||
* @param bean The bean.
|
||||
* @param method The method.
|
||||
* @param annotation The annotation.
|
||||
* @return The MessageHandler.
|
||||
*/
|
||||
protected abstract MessageHandler createHandler(Object bean, Method method, T annotation);
|
||||
protected abstract MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations);
|
||||
|
||||
}
|
||||
|
||||
@@ -18,10 +18,9 @@ package org.springframework.integration.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.integration.aggregator.AggregatingMessageHandler;
|
||||
import org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy;
|
||||
@@ -34,7 +33,6 @@ import org.springframework.integration.store.SimpleMessageStore;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -42,6 +40,8 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Aggregator> {
|
||||
|
||||
@@ -51,55 +51,47 @@ public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationP
|
||||
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler(Object bean, Method method, Aggregator annotation) {
|
||||
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
|
||||
MethodInvokingMessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(bean, method);
|
||||
processor.setBeanFactory(this.beanFactory);
|
||||
MethodInvokingReleaseStrategy releaseStrategy = getReleaseStrategy(bean);
|
||||
MethodInvokingCorrelationStrategy correlationStrategy = getCorrelationStrategy(bean);
|
||||
AggregatingMessageHandler handler = new AggregatingMessageHandler(processor, new SimpleMessageStore(), correlationStrategy, releaseStrategy);
|
||||
String discardChannelName = annotation.discardChannel();
|
||||
|
||||
MethodInvokingReleaseStrategy releaseStrategy = null;
|
||||
Method releaseStrategyMethod = MessagingAnnotationUtils.findAnnotatedMethod(bean, ReleaseStrategy.class);
|
||||
if (releaseStrategyMethod != null) {
|
||||
releaseStrategy = new MethodInvokingReleaseStrategy(bean, releaseStrategyMethod);
|
||||
}
|
||||
|
||||
MethodInvokingCorrelationStrategy correlationStrategy = null;
|
||||
Method correlationStrategyMethod = MessagingAnnotationUtils.findAnnotatedMethod(bean, CorrelationStrategy.class);
|
||||
if (correlationStrategyMethod != null) {
|
||||
correlationStrategy = new MethodInvokingCorrelationStrategy(bean, correlationStrategyMethod);
|
||||
}
|
||||
|
||||
AggregatingMessageHandler handler = new AggregatingMessageHandler(processor, new SimpleMessageStore(),
|
||||
correlationStrategy, releaseStrategy);
|
||||
|
||||
String discardChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "discardChannel", String.class);
|
||||
if (StringUtils.hasText(discardChannelName)) {
|
||||
MessageChannel discardChannel = this.channelResolver.resolveDestination(discardChannelName);
|
||||
Assert.notNull(discardChannel, "failed to resolve discardChannel '" + discardChannelName + "'");
|
||||
handler.setDiscardChannel(discardChannel);
|
||||
}
|
||||
String outputChannelName = annotation.outputChannel();
|
||||
String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel", String.class);
|
||||
if (StringUtils.hasText(outputChannelName)) {
|
||||
handler.setOutputChannel(this.channelResolver.resolveDestination(outputChannelName));
|
||||
}
|
||||
handler.setSendTimeout(annotation.sendTimeout());
|
||||
handler.setSendPartialResultOnExpiry(annotation.sendPartialResultsOnExpiry());
|
||||
Long sendTimeout = MessagingAnnotationUtils.resolveAttribute(annotations, "sendTimeout", Long.class);
|
||||
if (sendTimeout != null) {
|
||||
handler.setSendTimeout(sendTimeout);
|
||||
}
|
||||
Boolean sendPartialResultsOnExpiry = MessagingAnnotationUtils.resolveAttribute(annotations,
|
||||
"sendPartialResultsOnExpiry", Boolean.class);
|
||||
if (sendPartialResultsOnExpiry != null) {
|
||||
handler.setSendPartialResultOnExpiry(sendPartialResultsOnExpiry);
|
||||
}
|
||||
handler.setBeanFactory(this.beanFactory);
|
||||
handler.afterPropertiesSet();
|
||||
return handler;
|
||||
}
|
||||
|
||||
private MethodInvokingReleaseStrategy getReleaseStrategy(final Object bean) {
|
||||
final AtomicReference<MethodInvokingReleaseStrategy> reference = new AtomicReference<MethodInvokingReleaseStrategy>();
|
||||
ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.getAnnotation(method, ReleaseStrategy.class);
|
||||
if (annotation != null) {
|
||||
reference.set(new MethodInvokingReleaseStrategy(bean, method));
|
||||
}
|
||||
}
|
||||
});
|
||||
return reference.get();
|
||||
}
|
||||
|
||||
private MethodInvokingCorrelationStrategy getCorrelationStrategy(final Object bean) {
|
||||
final AtomicReference<MethodInvokingCorrelationStrategy> reference = new AtomicReference<MethodInvokingCorrelationStrategy>();
|
||||
ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.getAnnotation(method, CorrelationStrategy.class);
|
||||
if (annotation != null) {
|
||||
reference.set(new MethodInvokingCorrelationStrategy(bean, method));
|
||||
}
|
||||
}
|
||||
});
|
||||
return reference.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,16 +16,17 @@
|
||||
|
||||
package org.springframework.integration.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.integration.annotation.Filter;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.integration.filter.MessageFilter;
|
||||
import org.springframework.integration.filter.MethodInvokingSelector;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Post-processor for Methods annotated with {@link Filter @Filter}.
|
||||
@@ -42,16 +43,17 @@ public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
|
||||
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler(Object bean, Method method, Filter annotation) {
|
||||
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
|
||||
Assert.isTrue(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType()),
|
||||
"The Filter annotation may only be applied to methods with a boolean return type.");
|
||||
MethodInvokingSelector selector = new MethodInvokingSelector(bean, method);
|
||||
MessageFilter filter = new MessageFilter(selector);
|
||||
String outputChannelName = annotation.outputChannel();
|
||||
if (StringUtils.hasText(outputChannelName)) {
|
||||
filter.setOutputChannel(this.channelResolver.resolveDestination(outputChannelName));
|
||||
this.setOutputChannelIfPresent(annotations, filter);
|
||||
Boolean discardWithinAdvice = MessagingAnnotationUtils.resolveAttribute(annotations, "discardWithinAdvice",
|
||||
Boolean.class);
|
||||
if (discardWithinAdvice != null) {
|
||||
filter.setDiscardWithinAdvice(discardWithinAdvice);
|
||||
}
|
||||
filter.setDiscardWithinAdvice(annotation.discardWithinAdvice());
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.integration.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
@@ -35,6 +36,7 @@ import org.springframework.util.Assert;
|
||||
* Post-processor for Methods annotated with {@link InboundChannelAdapter @InboundChannelAdapter}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @since 4.0
|
||||
*/
|
||||
public class InboundChannelAdapterAnnotationPostProcessor extends
|
||||
@@ -45,13 +47,13 @@ public class InboundChannelAdapterAnnotationPostProcessor extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcess(Object bean, String beanName, Method method, InboundChannelAdapter annotation) {
|
||||
public Object postProcess(Object bean, String beanName, Method method, List<Annotation> annotations) {
|
||||
Assert.isTrue(!Void.class.isAssignableFrom(method.getReturnType()), "The method '" + method
|
||||
+ "' for 'SourcePollingChannelAdapter' must not have 'void' return type.");
|
||||
Assert.isTrue(method.getParameterTypes().length == 0, "The method '" + method
|
||||
+ "' for 'SourcePollingChannelAdapter' must not have any parameters.");
|
||||
|
||||
String channelName = (String) AnnotationUtils.getValue(annotation);
|
||||
String channelName = MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class);
|
||||
Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty.");
|
||||
|
||||
MessageChannel channel = this.channelResolver.resolveDestination(channelName);
|
||||
@@ -60,7 +62,7 @@ public class InboundChannelAdapterAnnotationPostProcessor extends
|
||||
messageSource.setObject(bean);
|
||||
messageSource.setMethod(method);
|
||||
if (beanFactory instanceof ConfigurableListableBeanFactory) {
|
||||
String handlerBeanName = this.generateHandlerBeanName(beanName, method, annotation.annotationType());
|
||||
String handlerBeanName = this.generateHandlerBeanName(beanName, method);
|
||||
ConfigurableListableBeanFactory listableBeanFactory = (ConfigurableListableBeanFactory) beanFactory;
|
||||
listableBeanFactory.registerSingleton(handlerBeanName, messageSource);
|
||||
messageSource = (MethodInvokingMessageSource) listableBeanFactory
|
||||
@@ -70,20 +72,19 @@ public class InboundChannelAdapterAnnotationPostProcessor extends
|
||||
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
|
||||
adapter.setOutputChannel(channel);
|
||||
adapter.setSource(messageSource);
|
||||
this.configurePollingEndpoint(adapter, annotation);
|
||||
this.configurePollingEndpoint(adapter, annotations);
|
||||
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String generateHandlerBeanName(String originalBeanName, Method method,
|
||||
Class<? extends Annotation> annotationType) {
|
||||
return super.generateHandlerBeanName(originalBeanName, method, annotationType)
|
||||
protected String generateHandlerBeanName(String originalBeanName, Method method) {
|
||||
return super.generateHandlerBeanName(originalBeanName, method)
|
||||
.replaceFirst(IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX + "$", ".source");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler(Object bean, Method method, InboundChannelAdapter annotation) {
|
||||
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ package org.springframework.integration.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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.Set;
|
||||
@@ -41,6 +41,7 @@ import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
@@ -64,6 +65,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class MessagingAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware,
|
||||
InitializingBean, Lifecycle, ApplicationListener<ApplicationEvent>, EnvironmentAware {
|
||||
@@ -117,7 +119,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
|
||||
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);
|
||||
if (!this.isStereotype(beanClass)) {
|
||||
if (AnnotationUtils.findAnnotation(beanClass, Component.class) == null) {
|
||||
// we only post-process stereotype components
|
||||
return bean;
|
||||
}
|
||||
@@ -125,20 +127,27 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
List<Annotation> annotations = new ArrayList<Annotation>();
|
||||
for (Class<? extends Annotation> annotation : postProcessors.keySet()) {
|
||||
Annotation result = AnnotationUtils.getAnnotation(method, annotation);
|
||||
if (result != null) {
|
||||
annotations.add(result);
|
||||
Map<Class<? extends Annotation>, List<Annotation>> annotationChains =
|
||||
new HashMap<Class<? extends Annotation>, List<Annotation>>();
|
||||
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 (Annotation annotation : annotations) {
|
||||
MethodAnnotationPostProcessor postProcessor = postProcessors.get(annotation.annotationType());
|
||||
if (postProcessor != null && shouldCreateEndpoint(annotation)) {
|
||||
Object result = postProcessor.postProcess(bean, beanName, method, annotation);
|
||||
|
||||
for (Map.Entry<Class<? extends Annotation>, List<Annotation>> entry : annotationChains.entrySet()) {
|
||||
Class<? extends Annotation> annotationType = entry.getKey();
|
||||
List<Annotation> annotations = entry.getValue();
|
||||
MethodAnnotationPostProcessor postProcessor = postProcessors.get(annotationType);
|
||||
if (postProcessor != null && shouldCreateEndpoint(annotations)) {
|
||||
Object result = postProcessor.postProcess(bean, beanName, method, annotations);
|
||||
if (result != null && result instanceof AbstractEndpoint) {
|
||||
AbstractEndpoint endpoint = (AbstractEndpoint) result;
|
||||
String autoStartup = (String) AnnotationUtils.getValue(annotation, "autoStartup");
|
||||
String autoStartup = MessagingAnnotationUtils.resolveAttribute(annotations, "autoStartup",
|
||||
String.class);
|
||||
if (StringUtils.hasText(autoStartup)) {
|
||||
if (environment != null) {
|
||||
autoStartup = environment.resolvePlaceholders(autoStartup);
|
||||
@@ -148,7 +157,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
|
||||
}
|
||||
}
|
||||
|
||||
String phase = (String) AnnotationUtils.getValue(annotation, "phase");
|
||||
String phase = MessagingAnnotationUtils.resolveAttribute(annotations, "phase", String.class);
|
||||
if (StringUtils.hasText(phase)) {
|
||||
if (environment != null) {
|
||||
phase = environment.resolvePlaceholders(phase);
|
||||
@@ -158,7 +167,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
|
||||
}
|
||||
}
|
||||
|
||||
String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType());
|
||||
String endpointBeanName = generateBeanName(beanName, method, annotationType);
|
||||
endpoint.setBeanName(endpointBeanName);
|
||||
beanFactory.registerSingleton(endpointBeanName, endpoint);
|
||||
endpoint.setBeanFactory(beanFactory);
|
||||
@@ -179,17 +188,63 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
private boolean shouldCreateEndpoint(Annotation annotation) {
|
||||
Object inputChannel = AnnotationUtils.getValue(annotation, "inputChannel");
|
||||
if (inputChannel == null && annotation instanceof InboundChannelAdapter) {
|
||||
inputChannel = AnnotationUtils.getValue(annotation);
|
||||
/**
|
||||
* @param method the method.
|
||||
* @param annotationType the annotation type.
|
||||
* @return the hierarchical list of annotations in top-bottom order.
|
||||
*/
|
||||
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 (inputChannel != null && inputChannel instanceof String
|
||||
&& StringUtils.hasText((String) inputChannel));
|
||||
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;
|
||||
}
|
||||
|
||||
private boolean shouldCreateEndpoint(List<Annotation> annotations) {
|
||||
for (Annotation annotation : annotations) {
|
||||
Object inputChannel = AnnotationUtils.getValue(annotation, "inputChannel");
|
||||
if (inputChannel == null &&
|
||||
(annotation instanceof InboundChannelAdapter ||
|
||||
AnnotationUtils.findAnnotation(annotation.annotationType(), InboundChannelAdapter.class) != null)) {
|
||||
inputChannel = AnnotationUtils.getValue(annotation);
|
||||
}
|
||||
if (inputChannel != null && inputChannel instanceof String
|
||||
&& StringUtils.hasText((String) inputChannel)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Class<?> getBeanClass(Object bean) {
|
||||
@@ -197,21 +252,6 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
|
||||
return (targetClass != null) ? targetClass : bean.getClass();
|
||||
}
|
||||
|
||||
private boolean isStereotype(Class<?> beanClass) {
|
||||
List<Annotation> annotations = new ArrayList<Annotation>(Arrays.asList(beanClass.getAnnotations()));
|
||||
Class<?>[] interfaces = beanClass.getInterfaces();
|
||||
for (Class<?> iface : interfaces) {
|
||||
annotations.addAll(Arrays.asList(iface.getAnnotations()));
|
||||
}
|
||||
for (Annotation annotation : annotations) {
|
||||
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||
if (annotationType.equals(Component.class) || annotationType.isAnnotationPresent(Component.class)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String generateBeanName(String originalBeanName, Method method, Class<? extends Annotation> annotationType) {
|
||||
String baseName = originalBeanName + "." + method.getName() + "." + ClassUtils.getShortNameAsProperty(annotationType);
|
||||
String name = baseName;
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2014 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.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility methods to support annotation processing.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Dave Syer
|
||||
* @author Gunnar Hillert
|
||||
* @author Soby Chacko
|
||||
* @author Artem Bilan
|
||||
* @since 4.0
|
||||
*/
|
||||
public final class MessagingAnnotationUtils {
|
||||
|
||||
/**
|
||||
* Get the attribute value from the annotation hierarchy, returning the first non-empty
|
||||
* value closest to the annotated method. While traversing up the hierarchy, for string-valued
|
||||
* attributes, an empty string is ignored. For array-valued attributes, an empty
|
||||
* array is ignored.
|
||||
* The overridden attribute must be the same type.
|
||||
* @param annotations The meta-annotations in order (closest first).
|
||||
* @param name The attribute name.
|
||||
* @param requiredType The expected type.
|
||||
* @return The value.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T resolveAttribute(List<Annotation> annotations, String name, Class<T> requiredType) {
|
||||
for (Annotation annotation : annotations) {
|
||||
if (annotation != null) {
|
||||
Object value = AnnotationUtils.getValue(annotation, name);
|
||||
if (value != null && value.getClass() == requiredType && hasValue(value)) {
|
||||
return (T) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean hasValue(Object value) {
|
||||
return value != null && (!(value instanceof String) || (StringUtils.hasText((String) value)))
|
||||
&& (!value.getClass().isArray() || ((Object[]) value).length > 0);
|
||||
}
|
||||
|
||||
public static Method findAnnotatedMethod(Object target, final Class<? extends Annotation> annotationType) {
|
||||
final AtomicReference<Method> reference = new AtomicReference<Method>();
|
||||
|
||||
ReflectionUtils.doWithMethods(getTargetClass(target), new ReflectionUtils.MethodCallback() {
|
||||
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
reference.compareAndSet(null, method);
|
||||
}
|
||||
}, new ReflectionUtils.MethodFilter() {
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method) {
|
||||
return ReflectionUtils.USER_DECLARED_METHODS.matches(method) &&
|
||||
AnnotatedElementUtils.isAnnotated(method, annotationType.getName());
|
||||
}
|
||||
});
|
||||
|
||||
return reference.get();
|
||||
}
|
||||
|
||||
private static Class<?> getTargetClass(Object targetObject) {
|
||||
Class<?> targetClass = targetObject.getClass();
|
||||
if (AopUtils.isAopProxy(targetObject)) {
|
||||
targetClass = AopUtils.getTargetClass(targetObject);
|
||||
}
|
||||
else if (ClassUtils.isCglibProxyClass(targetClass)) {
|
||||
Class<?> superClass = targetObject.getClass().getSuperclass();
|
||||
if (!Object.class.equals(superClass)) {
|
||||
targetClass = superClass;
|
||||
}
|
||||
}
|
||||
return targetClass;
|
||||
}
|
||||
|
||||
private MessagingAnnotationUtils() {}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -18,14 +18,16 @@ package org.springframework.integration.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Strategy interface for post-processing annotated methods.
|
||||
*
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public interface MethodAnnotationPostProcessor<T extends Annotation> {
|
||||
|
||||
Object postProcess(Object bean, String beanName, Method method, T annotation);
|
||||
Object postProcess(Object bean, String beanName, Method method, List<Annotation> annotations);
|
||||
|
||||
}
|
||||
|
||||
@@ -16,14 +16,16 @@
|
||||
|
||||
package org.springframework.integration.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.integration.annotation.Router;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.integration.router.MethodInvokingRouter;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -31,6 +33,7 @@ import org.springframework.util.StringUtils;
|
||||
* Post-processor for Methods annotated with {@link Router @Router}.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Router> {
|
||||
|
||||
@@ -40,10 +43,12 @@ public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
|
||||
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler(Object bean, Method method, Router annotation) {
|
||||
protected MessageHandler createHandler(Object bean, Method method,
|
||||
List<Annotation> annotations) {
|
||||
MethodInvokingRouter router = new MethodInvokingRouter(bean, method);
|
||||
router.setBeanFactory(this.beanFactory);
|
||||
String defaultOutputChannelName = annotation.defaultOutputChannel();
|
||||
String defaultOutputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "defaultOutputChannel",
|
||||
String.class);
|
||||
if (StringUtils.hasText(defaultOutputChannelName)) {
|
||||
MessageChannel defaultOutputChannel = this.channelResolver.resolveDestination(defaultOutputChannelName);
|
||||
Assert.notNull(defaultOutputChannel, "unable to resolve defaultOutputChannel '" + defaultOutputChannelName + "'");
|
||||
|
||||
@@ -16,19 +16,21 @@
|
||||
|
||||
package org.springframework.integration.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.integration.handler.ServiceActivatingHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
/**
|
||||
* Post-processor for Methods annotated with {@link ServiceActivator @ServiceActivator}.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<ServiceActivator> {
|
||||
|
||||
@@ -38,12 +40,9 @@ public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnot
|
||||
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler(Object bean, Method method, ServiceActivator annotation) {
|
||||
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
|
||||
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(bean, method);
|
||||
String outputChannelName = annotation.outputChannel();
|
||||
if (StringUtils.hasText(outputChannelName)) {
|
||||
serviceActivator.setOutputChannel(this.channelResolver.resolveDestination(outputChannelName));
|
||||
}
|
||||
this.setOutputChannelIfPresent(annotations, serviceActivator);
|
||||
return serviceActivator;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,19 +16,21 @@
|
||||
|
||||
package org.springframework.integration.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.integration.annotation.Splitter;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.integration.splitter.MethodInvokingSplitter;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
/**
|
||||
* Post-processor for Methods annotated with {@link Splitter @Splitter}.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class SplitterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Splitter> {
|
||||
|
||||
@@ -38,12 +40,9 @@ public class SplitterAnnotationPostProcessor extends AbstractMethodAnnotationPos
|
||||
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler(Object bean, Method method, Splitter annotation) {
|
||||
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
|
||||
MethodInvokingSplitter splitter = new MethodInvokingSplitter(bean, method);
|
||||
String outputChannelName = annotation.outputChannel();
|
||||
if (StringUtils.hasText(outputChannelName)) {
|
||||
splitter.setOutputChannel(this.channelResolver.resolveDestination(outputChannelName));
|
||||
}
|
||||
this.setOutputChannelIfPresent(annotations, splitter);
|
||||
return splitter;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,20 +16,22 @@
|
||||
|
||||
package org.springframework.integration.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.integration.annotation.Transformer;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.integration.transformer.MessageTransformingHandler;
|
||||
import org.springframework.integration.transformer.MethodInvokingTransformer;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
/**
|
||||
* Post-processor for Methods annotated with {@link Transformer @Transformer}.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class TransformerAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Transformer> {
|
||||
|
||||
@@ -39,13 +41,10 @@ public class TransformerAnnotationPostProcessor extends AbstractMethodAnnotation
|
||||
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler(Object bean, Method method, Transformer annotation) {
|
||||
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
|
||||
MethodInvokingTransformer transformer = new MethodInvokingTransformer(bean, method);
|
||||
MessageTransformingHandler handler = new MessageTransformingHandler(transformer);
|
||||
String outputChannelName = annotation.outputChannel();
|
||||
if (StringUtils.hasText(outputChannelName)) {
|
||||
handler.setOutputChannel(this.channelResolver.resolveDestination(outputChannelName));
|
||||
}
|
||||
this.setOutputChannelIfPresent(annotations, handler);
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,24 +16,20 @@
|
||||
|
||||
package org.springframework.integration.configuration;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -53,6 +49,8 @@ import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.serializer.support.SerializingConverter;
|
||||
import org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.Gateway;
|
||||
import org.springframework.integration.annotation.GatewayHeader;
|
||||
import org.springframework.integration.annotation.InboundChannelAdapter;
|
||||
@@ -76,6 +74,7 @@ import org.springframework.integration.config.GlobalChannelInterceptor;
|
||||
import org.springframework.integration.config.IntegrationConverter;
|
||||
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
|
||||
import org.springframework.integration.endpoint.PollingConsumer;
|
||||
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
|
||||
import org.springframework.integration.history.MessageHistory;
|
||||
import org.springframework.integration.history.MessageHistoryConfigurer;
|
||||
import org.springframework.integration.scheduling.PollerMetadata;
|
||||
@@ -155,6 +154,9 @@ public class EnableIntegrationTests {
|
||||
@Autowired
|
||||
private TestGateway testGateway;
|
||||
|
||||
@Autowired
|
||||
private TestGateway2 testGateway2;
|
||||
|
||||
@Autowired
|
||||
private TestChannelInterceptor testChannelInterceptor;
|
||||
|
||||
@@ -297,6 +299,7 @@ public class EnableIntegrationTests {
|
||||
public void testMessagingGateway() {
|
||||
String payload = "bar";
|
||||
assertEquals(payload.toUpperCase(), this.testGateway.echo(payload));
|
||||
assertEquals(payload.toUpperCase() + "2", this.testGateway2.echo2(payload));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -336,6 +339,83 @@ public class EnableIntegrationTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMetaAnnotations() {
|
||||
|
||||
assertEquals(2, this.context.getBeanNamesForType(GatewayProxyFactoryBean.class).length);
|
||||
|
||||
PollingConsumer consumer = this.context.getBean(
|
||||
"enableIntegrationTests.AnnotationTestService.annCount.serviceActivator",
|
||||
PollingConsumer.class);
|
||||
assertFalse(TestUtils.getPropertyValue(consumer, "autoStartup", Boolean.class));
|
||||
assertEquals(23, TestUtils.getPropertyValue(consumer, "phase"));
|
||||
assertSame(context.getBean("annInput"), TestUtils.getPropertyValue(consumer, "inputChannel"));
|
||||
assertSame(context.getBean("annOutput"), TestUtils.getPropertyValue(consumer, "handler.outputChannel"));
|
||||
assertSame(context.getBean("annAdvice"), TestUtils.getPropertyValue(consumer,
|
||||
"handler.adviceChain", List.class).get(0));
|
||||
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
|
||||
|
||||
consumer = this.context.getBean(
|
||||
"enableIntegrationTests.AnnotationTestService.annCount1.serviceActivator",
|
||||
PollingConsumer.class);
|
||||
consumer.stop();
|
||||
assertTrue(TestUtils.getPropertyValue(consumer, "autoStartup", Boolean.class));
|
||||
assertEquals(23, TestUtils.getPropertyValue(consumer, "phase"));
|
||||
assertSame(context.getBean("annInput1"), TestUtils.getPropertyValue(consumer, "inputChannel"));
|
||||
assertSame(context.getBean("annOutput"), TestUtils.getPropertyValue(consumer, "handler.outputChannel"));
|
||||
assertSame(context.getBean("annAdvice1"), TestUtils.getPropertyValue(consumer,
|
||||
"handler.adviceChain", List.class).get(0));
|
||||
assertEquals(2000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
|
||||
|
||||
consumer = this.context.getBean(
|
||||
"enableIntegrationTests.AnnotationTestService.annCount2.serviceActivator",
|
||||
PollingConsumer.class);
|
||||
assertFalse(TestUtils.getPropertyValue(consumer, "autoStartup", Boolean.class));
|
||||
assertEquals(23, TestUtils.getPropertyValue(consumer, "phase"));
|
||||
assertSame(context.getBean("annInput"), TestUtils.getPropertyValue(consumer, "inputChannel"));
|
||||
assertSame(context.getBean("annOutput"), TestUtils.getPropertyValue(consumer, "handler.outputChannel"));
|
||||
assertSame(context.getBean("annAdvice"), TestUtils.getPropertyValue(consumer,
|
||||
"handler.adviceChain", List.class).get(0));
|
||||
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
|
||||
|
||||
// Tests when the channel is in a "middle" annotation
|
||||
consumer = this.context.getBean(
|
||||
"enableIntegrationTests.AnnotationTestService.annCount5.serviceActivator",
|
||||
PollingConsumer.class);
|
||||
assertFalse(TestUtils.getPropertyValue(consumer, "autoStartup", Boolean.class));
|
||||
assertEquals(23, TestUtils.getPropertyValue(consumer, "phase"));
|
||||
assertSame(context.getBean("annInput3"), TestUtils.getPropertyValue(consumer, "inputChannel"));
|
||||
assertSame(context.getBean("annOutput"), TestUtils.getPropertyValue(consumer, "handler.outputChannel"));
|
||||
assertSame(context.getBean("annAdvice"), TestUtils.getPropertyValue(consumer,
|
||||
"handler.adviceChain", List.class).get(0));
|
||||
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
|
||||
|
||||
consumer = this.context.getBean(
|
||||
"enableIntegrationTests.AnnotationTestService.annAgg1.aggregator",
|
||||
PollingConsumer.class);
|
||||
assertFalse(TestUtils.getPropertyValue(consumer, "autoStartup", Boolean.class));
|
||||
assertEquals(23, TestUtils.getPropertyValue(consumer, "phase"));
|
||||
assertSame(context.getBean("annInput"), TestUtils.getPropertyValue(consumer, "inputChannel"));
|
||||
assertSame(context.getBean("annOutput"), TestUtils.getPropertyValue(consumer, "handler.outputChannel"));
|
||||
assertSame(context.getBean("annOutput"), TestUtils.getPropertyValue(consumer, "handler.discardChannel"));
|
||||
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
|
||||
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "handler.messagingTemplate.sendTimeout"));
|
||||
assertFalse(TestUtils.getPropertyValue(consumer, "handler.sendPartialResultOnExpiry", Boolean.class));
|
||||
|
||||
consumer = this.context.getBean(
|
||||
"enableIntegrationTests.AnnotationTestService.annAgg2.aggregator",
|
||||
PollingConsumer.class);
|
||||
assertFalse(TestUtils.getPropertyValue(consumer, "autoStartup", Boolean.class));
|
||||
assertEquals(23, TestUtils.getPropertyValue(consumer, "phase"));
|
||||
assertSame(context.getBean("annInput"), TestUtils.getPropertyValue(consumer, "inputChannel"));
|
||||
assertSame(context.getBean("annOutput"), TestUtils.getPropertyValue(consumer, "handler.outputChannel"));
|
||||
assertSame(context.getBean("annOutput"), TestUtils.getPropertyValue(consumer, "handler.discardChannel"));
|
||||
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
|
||||
assertEquals(75L, TestUtils.getPropertyValue(consumer, "handler.messagingTemplate.sendTimeout"));
|
||||
assertTrue(TestUtils.getPropertyValue(consumer, "handler.sendPartialResultOnExpiry", Boolean.class));
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@IntegrationComponentScan
|
||||
@@ -433,6 +513,38 @@ public class EnableIntegrationTests {
|
||||
};
|
||||
}
|
||||
|
||||
// beans for metaAnnotation tests
|
||||
|
||||
@Bean
|
||||
public MethodInterceptor annAdvice() {
|
||||
return mock(MethodInterceptor.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public QueueChannel annInput() {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public QueueChannel annOutput() {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MethodInterceptor annAdvice1() {
|
||||
return mock(MethodInterceptor.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public QueueChannel annInput1() {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public QueueChannel annInput3() {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
@@ -492,6 +604,11 @@ public class EnableIntegrationTests {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PollableChannel gatewayChannel2() {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PollableChannel counterChannel() {
|
||||
return new QueueChannel();
|
||||
@@ -624,8 +741,16 @@ public class EnableIntegrationTests {
|
||||
return this.handle(message.getPayload());
|
||||
}
|
||||
|
||||
@InboundChannelAdapter(value = "counterChannel", autoStartup = "false",
|
||||
phase = "23")
|
||||
@Transformer(inputChannel = "gatewayChannel2")
|
||||
public String transform2(Message<String> message) {
|
||||
assertTrue(message.getHeaders().containsKey("foo"));
|
||||
assertEquals("FOO", message.getHeaders().get("foo"));
|
||||
assertTrue(message.getHeaders().containsKey("calledMethod"));
|
||||
assertEquals("echo2", message.getHeaders().get("calledMethod"));
|
||||
return this.handle(message.getPayload()) + "2";
|
||||
}
|
||||
|
||||
@MyInboundChannelAdapter1
|
||||
public Integer count() {
|
||||
return this.counter.incrementAndGet();
|
||||
}
|
||||
@@ -656,6 +781,44 @@ public class EnableIntegrationTests {
|
||||
public void error2() {
|
||||
}*/
|
||||
|
||||
// metaAnnotation tests
|
||||
|
||||
@MyServiceActivator
|
||||
public Integer annCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@MyServiceActivator1(inputChannel = "annInput1", autoStartup = "true",
|
||||
adviceChain = { "annAdvice1" }, poller = @Poller(fixedRate = "2000") )
|
||||
public Integer annCount1() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@MyServiceActivatorNoLocalAtts()
|
||||
public Integer annCount2() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@MyServiceActivator5
|
||||
public Integer annCount5() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@MyServiceActivator8
|
||||
public Integer annCount8() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@MyAggregator
|
||||
public Integer annAgg1(List<?> messages) {
|
||||
return 42;
|
||||
}
|
||||
|
||||
@MyAggregatorDefaultOverrideDefaults
|
||||
public Integer annAgg2(List<?> messages) {
|
||||
return 42;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TestMessagingGateway
|
||||
@@ -666,13 +829,217 @@ public class EnableIntegrationTests {
|
||||
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@TestMessagingGateway2
|
||||
public static interface TestGateway2 {
|
||||
|
||||
@Gateway(headers = @GatewayHeader(name = "calledMethod", expression = "#gatewayMethod.name"))
|
||||
String echo2(String payload);
|
||||
|
||||
}
|
||||
|
||||
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MessagingGateway(defaultRequestChannel = "gatewayChannel",
|
||||
defaultHeaders = @GatewayHeader(name = "foo", value = "FOO"))
|
||||
public static @interface TestMessagingGateway {
|
||||
|
||||
String defaultRequestChannel() default "";
|
||||
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@TestMessagingGateway(defaultRequestChannel = "gatewayChannel2")
|
||||
public static @interface TestMessagingGateway2 {
|
||||
|
||||
String defaultRequestChannel() default "";
|
||||
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ServiceActivator(autoStartup = "false",
|
||||
phase = "23",
|
||||
inputChannel = "annInput",
|
||||
outputChannel = "annOutput",
|
||||
adviceChain = { "annAdvice" },
|
||||
poller = @Poller(fixedDelay = "1000"))
|
||||
public static @interface MyServiceActivator {
|
||||
|
||||
String inputChannel() default "";
|
||||
|
||||
String outputChannel() default "";
|
||||
|
||||
String[] adviceChain() default {};
|
||||
|
||||
String autoStartup() default "";
|
||||
|
||||
String phase() default "";
|
||||
|
||||
Poller[] poller() default {};
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MyServiceActivator
|
||||
public static @interface MyServiceActivator1 {
|
||||
|
||||
String inputChannel() default "";
|
||||
|
||||
String outputChannel() default "";
|
||||
|
||||
String[] adviceChain() default {};
|
||||
|
||||
String autoStartup() default "";
|
||||
|
||||
String phase() default "";
|
||||
|
||||
Poller[] poller() default {};
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MyServiceActivator1
|
||||
public static @interface MyServiceActivator2 {
|
||||
|
||||
String inputChannel() default "";
|
||||
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MyServiceActivator2
|
||||
public static @interface MyServiceActivator3 {
|
||||
|
||||
String inputChannel() default "";
|
||||
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MyServiceActivator3(inputChannel = "annInput3")
|
||||
public static @interface MyServiceActivator4 {
|
||||
|
||||
String inputChannel() default "";
|
||||
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MyServiceActivator4
|
||||
public static @interface MyServiceActivator5 {
|
||||
|
||||
String inputChannel() default "";
|
||||
|
||||
}
|
||||
|
||||
// Test prevent infinite recursion
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MyServiceActivator5
|
||||
public static @interface MyServiceActivator6 {
|
||||
|
||||
String inputChannel() default "";
|
||||
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MyServiceActivator8
|
||||
public static @interface MyServiceActivator7 {
|
||||
|
||||
String inputChannel() default "";
|
||||
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MyServiceActivator7
|
||||
public static @interface MyServiceActivator8 {
|
||||
|
||||
String inputChannel() default "";
|
||||
|
||||
}
|
||||
// end test infinite recursion
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ServiceActivator(autoStartup = "false",
|
||||
phase = "23",
|
||||
inputChannel = "annInput",
|
||||
outputChannel = "annOutput",
|
||||
adviceChain = { "annAdvice" },
|
||||
poller = @Poller(fixedDelay = "1000"))
|
||||
public static @interface MyServiceActivatorNoLocalAtts {
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Aggregator(autoStartup = "false",
|
||||
phase = "23",
|
||||
inputChannel = "annInput",
|
||||
outputChannel = "annOutput",
|
||||
discardChannel = "annOutput",
|
||||
poller = @Poller(fixedDelay = "1000"))
|
||||
public static @interface MyAggregator {
|
||||
|
||||
String inputChannel() default "";
|
||||
|
||||
String outputChannel() default "";
|
||||
|
||||
String discardChannel() default "";
|
||||
|
||||
long sendTimeout() default AbstractCorrelatingMessageHandler.DEFAULT_SEND_TIMEOUT;
|
||||
|
||||
boolean sendPartialResultsOnExpiry() default false;
|
||||
|
||||
String autoStartup() default "";
|
||||
|
||||
String phase() default "";
|
||||
|
||||
Poller[] poller() default {};
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Aggregator(autoStartup = "false",
|
||||
phase = "23",
|
||||
inputChannel = "annInput",
|
||||
outputChannel = "annOutput",
|
||||
discardChannel = "annOutput",
|
||||
sendPartialResultsOnExpiry = false,
|
||||
sendTimeout = 1000L,
|
||||
poller = @Poller(fixedDelay = "1000"))
|
||||
public static @interface MyAggregatorDefaultOverrideDefaults {
|
||||
|
||||
boolean sendPartialResultsOnExpiry() default true;
|
||||
|
||||
long sendTimeout() default 75;
|
||||
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@InboundChannelAdapter(value = "counterChannel", autoStartup = "false", phase = "23")
|
||||
public static @interface MyInboundChannelAdapter {
|
||||
|
||||
String value() default "";
|
||||
|
||||
String autoStartup() default "";
|
||||
|
||||
String phase() default "";
|
||||
|
||||
Poller[] poller() default {};
|
||||
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MyInboundChannelAdapter
|
||||
public static @interface MyInboundChannelAdapter1 {
|
||||
|
||||
}
|
||||
|
||||
// Error because the annotation is on a class; it must be on an interface
|
||||
// @MessagingGateway(defaultRequestChannel = "gatewayChannel", defaultHeaders = @GatewayHeader(name = "foo", value = "FOO"))
|
||||
|
||||
Reference in New Issue
Block a user