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:
Gary Russell
2014-04-24 21:55:52 +03:00
committed by Artem Bilan
parent 97d1d389a9
commit 24f176c897
18 changed files with 793 additions and 231 deletions

View File

@@ -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'

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
};

View File

@@ -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;
}
}
}
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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() {}
}

View File

@@ -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);
}

View File

@@ -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 + "'");

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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"))