Added support for the @Transactional annotation within the @Poller annotation (INT-399). Simplified AbstractMessageBarrierConsumer somewhat (to reuse base class methods for sending replies and discarding incomplete messages). Added TestUtils with convenient property-path accessor methods. Refactored annotation post-processors such that the BeanFactory is passed to the individual method post-processors rather than passing the MessageBus. The base class still detects the MessageBus bean and provides a protected 'channelRegistry' reference.

This commit is contained in:
Mark Fisher
2008-10-08 00:19:20 +00:00
parent ea220cac6c
commit 6c4245f3ca
21 changed files with 331 additions and 177 deletions

View File

@@ -29,7 +29,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.channel.BlockingChannel;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.endpoint.AbstractReplyProducingMessageConsumer;
import org.springframework.integration.message.Message;
@@ -67,8 +66,6 @@ import org.springframework.util.ObjectUtils;
public abstract class AbstractMessageBarrierConsumer extends AbstractReplyProducingMessageConsumer
implements TaskSchedulerAware, InitializingBean {
public final static long DEFAULT_SEND_TIMEOUT = 1000;
public final static long DEFAULT_TIMEOUT = 60000;
public final static long DEFAULT_REAPER_INTERVAL = 1000;
@@ -79,8 +76,6 @@ public abstract class AbstractMessageBarrierConsumer extends AbstractReplyProduc
private volatile MessageChannel discardChannel;
protected volatile long sendTimeout = DEFAULT_SEND_TIMEOUT;
protected final ConcurrentMap<Object, MessageBarrier> barriers =
new ConcurrentHashMap<Object, MessageBarrier>();
@@ -109,13 +104,6 @@ public abstract class AbstractMessageBarrierConsumer extends AbstractReplyProduc
this.discardChannel = discardChannel;
}
/**
* Set the timeout for sending aggregation results and discarded Messages.
*/
public void setSendTimeout(long sendTimeout) {
this.sendTimeout = sendTimeout;
}
/**
* Specify whether to aggregate and send the resulting Message when the
* timeout elapses prior to the CompletionStrategy returning true.
@@ -225,12 +213,7 @@ public abstract class AbstractMessageBarrierConsumer extends AbstractReplyProduc
}
}
if (replyChannel != null) {
if (replyChannel instanceof BlockingChannel && this.sendTimeout >= 0) {
((BlockingChannel) replyChannel).send(result, this.sendTimeout);
}
else {
replyChannel.send(result);
}
this.sendReplyMessage(result, replyChannel);
}
else if (logger.isWarnEnabled()) {
logger.warn("unable to determine reply target for aggregation result: " + result);
@@ -240,9 +223,7 @@ public abstract class AbstractMessageBarrierConsumer extends AbstractReplyProduc
private void sendToDiscardChannelIfAvailable(Message<?> message) {
if (this.discardChannel != null) {
boolean sent = (this.discardChannel instanceof BlockingChannel && this.sendTimeout >= 0)
? ((BlockingChannel) this.discardChannel).send(message, this.sendTimeout)
: this.discardChannel.send(message);
boolean sent = this.sendReplyMessage(message, this.discardChannel);
if (!sent) {
if (logger.isWarnEnabled()) {
logger.warn("unable to send to 'discardChannel', message: " + message);

View File

@@ -18,16 +18,18 @@ package org.springframework.integration.config.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.channel.SubscribableChannel;
import org.springframework.integration.config.MessageBusParser;
import org.springframework.integration.endpoint.AbstractMessageConsumer;
import org.springframework.integration.endpoint.AbstractReplyProducingMessageConsumer;
import org.springframework.integration.endpoint.MessageEndpoint;
@@ -35,6 +37,12 @@ import org.springframework.integration.endpoint.PollingConsumerEndpoint;
import org.springframework.integration.endpoint.SubscribingConsumerEndpoint;
import org.springframework.integration.message.MessageConsumer;
import org.springframework.integration.scheduling.IntervalTrigger;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -50,23 +58,23 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
private static final String OUTPUT_CHANNEL_ATTRIBUTE = "outputChannel";
private final MessageBus messageBus;
private final BeanFactory beanFactory;
protected final ChannelRegistry channelRegistry;
public AbstractMethodAnnotationPostProcessor(MessageBus messageBus) {
Assert.notNull(messageBus, "MessageBus must not be null");
this.messageBus = messageBus;
public AbstractMethodAnnotationPostProcessor(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
this.channelRegistry = (ChannelRegistry) this.beanFactory.getBean(
MessageBusParser.MESSAGE_BUS_BEAN_NAME);
}
protected ChannelRegistry getChannelRegistry() {
return this.messageBus;
}
public Object postProcess(Object bean, String beanName, Method method, T annotation) {
MessageConsumer consumer = this.createConsumer(bean, method, annotation);
if (consumer instanceof ChannelRegistryAware) {
((ChannelRegistryAware) consumer).setChannelRegistry(this.getChannelRegistry());
((ChannelRegistryAware) consumer).setChannelRegistry(this.channelRegistry);
}
Poller pollerAnnotation = AnnotationUtils.findAnnotation(method, Poller.class);
MessageEndpoint endpoint = this.createEndpoint(consumer, annotation, pollerAnnotation);
@@ -93,7 +101,7 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
MessageEndpoint endpoint = null;
String inputChannelName = (String) AnnotationUtils.getValue(annotation, INPUT_CHANNEL_ATTRIBUTE);
if (StringUtils.hasText(inputChannelName)) {
MessageChannel inputChannel = this.messageBus.lookupChannel(inputChannelName);
MessageChannel inputChannel = this.channelRegistry.lookupChannel(inputChannelName);
Assert.notNull(inputChannel, "unable to resolve inputChannel '" + inputChannelName + "'");
if (consumer instanceof AbstractMessageConsumer) {
if (inputChannel instanceof PollableChannel) {
@@ -106,6 +114,15 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
trigger.setFixedRate(pollerAnnotation.fixedRate());
pollingEndpoint.setTrigger(trigger);
pollingEndpoint.setMaxMessagesPerPoll(pollerAnnotation.maxMessagesPerPoll());
if (StringUtils.hasText(pollerAnnotation.transactionManager())) {
String txManagerRef = pollerAnnotation.transactionManager();
Assert.isTrue(this.beanFactory.containsBean(txManagerRef), "no such bean '" + txManagerRef + "'");
PlatformTransactionManager txManager = (PlatformTransactionManager)
this.beanFactory.getBean(txManagerRef, PlatformTransactionManager.class);
pollingEndpoint.setTransactionManager(txManager);
Transactional txAnnotation = pollerAnnotation.transactionAttributes();
pollingEndpoint.setTransactionDefinition(this.parseTransactionAnnotation(txAnnotation));
}
}
endpoint = pollingEndpoint;
}
@@ -122,7 +139,7 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
if (consumer instanceof AbstractReplyProducingMessageConsumer) {
String outputChannelName = (String) AnnotationUtils.getValue(annotation, OUTPUT_CHANNEL_ATTRIBUTE);
if (StringUtils.hasText(outputChannelName)) {
MessageChannel outputChannel = this.messageBus.lookupChannel(outputChannelName);
MessageChannel outputChannel = this.channelRegistry.lookupChannel(outputChannelName);
Assert.notNull(outputChannel, "unable to resolve outputChannel '" + outputChannelName + "'");
((AbstractReplyProducingMessageConsumer) consumer).setOutputChannel(outputChannel);
}
@@ -131,6 +148,44 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
return endpoint;
}
@SuppressWarnings("unchecked")
private TransactionAttribute parseTransactionAnnotation(Transactional annotation) {
if (annotation == null) {
return null;
}
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
rbta.setPropagationBehavior(annotation.propagation().value());
rbta.setIsolationLevel(annotation.isolation().value());
rbta.setTimeout(annotation.timeout());
rbta.setReadOnly(annotation.readOnly());
ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<RollbackRuleAttribute>();
Class<?>[] rbf = annotation.rollbackFor();
for (int i = 0; i < rbf.length; ++i) {
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbf[i]);
rollBackRules.add(rule);
}
String[] rbfc = annotation.rollbackForClassName();
for (int i = 0; i < rbfc.length; ++i) {
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbfc[i]);
rollBackRules.add(rule);
}
Class<?>[] nrbf = annotation.noRollbackFor();
for (int i = 0; i < nrbf.length; ++i) {
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(nrbf[i]);
rollBackRules.add(rule);
}
String[] nrbfc = annotation.noRollbackForClassName();
for (int i = 0; i < nrbfc.length; ++i) {
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(nrbfc[i]);
rollBackRules.add(rule);
}
rbta.getRollbackRules().addAll(rollBackRules);
return rbta;
}
/**
* Subclasses must implement this method to create the MessageConsumer.
*/
protected abstract MessageConsumer createConsumer(Object bean, Method method, T annotation);
}

View File

@@ -19,13 +19,13 @@ package org.springframework.integration.config.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.aggregator.AbstractMessageAggregator;
import org.springframework.integration.aggregator.CompletionStrategyAdapter;
import org.springframework.integration.aggregator.MethodInvokingAggregator;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.CompletionStrategy;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.MessageConsumer;
import org.springframework.util.Assert;
@@ -39,8 +39,8 @@ import org.springframework.util.StringUtils;
*/
public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Aggregator> {
public AggregatorAnnotationPostProcessor(MessageBus messageBus) {
super(messageBus);
public AggregatorAnnotationPostProcessor(BeanFactory beanFactory) {
super(beanFactory);
}
@@ -50,7 +50,7 @@ public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationP
this.configureCompletionStrategy(bean, aggregator);
String discardChannelName = annotation.discardChannel();
if (StringUtils.hasText(discardChannelName)) {
MessageChannel discardChannel = this.getChannelRegistry().lookupChannel(discardChannelName);
MessageChannel discardChannel = this.channelRegistry.lookupChannel(discardChannelName);
Assert.notNull(discardChannel, "unable to resolve discardChannel '" + discardChannelName + "'");
aggregator.setDiscardChannel(discardChannel);
}

View File

@@ -58,8 +58,6 @@ public class AnnotationDrivenParser implements BeanDefinitionParser {
private void registerMessagingAnnotationPostProcessor(ParserContext parserContext) {
BeanDefinition bd = new RootBeanDefinition(MessagingAnnotationPostProcessor.class);
bd.getConstructorArgumentValues().addGenericArgumentValue(
new RuntimeBeanReference(MessageBusParser.MESSAGE_BUS_BEAN_NAME));
BeanComponentDefinition bcd = new BeanComponentDefinition(
bd, MESSAGING_ANNOTATION_POST_PROCESSOR_BEAN_NAME);
parserContext.registerBeanComponent(bcd);

View File

@@ -22,11 +22,12 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.annotation.ChannelAdapter;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.channel.SubscribableChannel;
import org.springframework.integration.config.MessageBusParser;
import org.springframework.integration.endpoint.MessageEndpoint;
import org.springframework.integration.endpoint.PollingConsumerEndpoint;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
@@ -45,16 +46,16 @@ import org.springframework.util.ClassUtils;
*/
public class ChannelAdapterAnnotationPostProcessor implements MethodAnnotationPostProcessor<ChannelAdapter> {
private final MessageBus messageBus;
private final ConfigurableBeanFactory beanFactory;
private final ChannelRegistry channelRegistry;
public ChannelAdapterAnnotationPostProcessor(MessageBus messageBus, ConfigurableBeanFactory beanFactory) {
Assert.notNull(messageBus, "MessageBus must not be null");
public ChannelAdapterAnnotationPostProcessor(ConfigurableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.messageBus = messageBus;
this.beanFactory = beanFactory;
this.channelRegistry = (ChannelRegistry)
this.beanFactory.getBean(MessageBusParser.MESSAGE_BUS_BEAN_NAME);
}
@@ -62,7 +63,7 @@ public class ChannelAdapterAnnotationPostProcessor implements MethodAnnotationPo
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
MessageEndpoint endpoint = null;
String channelName = annotation.value();
MessageChannel channel = this.messageBus.lookupChannel(channelName);
MessageChannel channel = this.channelRegistry.lookupChannel(channelName);
if (channel == null) {
DirectChannel directChannel = new DirectChannel();
directChannel.setBeanName(channelName);

View File

@@ -41,6 +41,7 @@ import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.config.MessageBusParser;
import org.springframework.integration.endpoint.MessageEndpoint;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@@ -57,7 +58,7 @@ import org.springframework.util.StringUtils;
*/
public class MessagingAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean {
private final MessageBus messageBus;
private volatile MessageBus messageBus;
private volatile ConfigurableBeanFactory beanFactory;
@@ -66,12 +67,6 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
new HashMap<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>>();
public MessagingAnnotationPostProcessor(MessageBus messageBus) {
Assert.notNull(messageBus, "MessageBus must not be null.");
this.messageBus = messageBus;
}
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isAssignable(ConfigurableBeanFactory.class, beanFactory.getClass(),
"a ConfigurableBeanFactory is required");
@@ -80,12 +75,13 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
public void afterPropertiesSet() {
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor(this.messageBus));
postProcessors.put(ChannelAdapter.class, new ChannelAdapterAnnotationPostProcessor(this.messageBus, this.beanFactory));
postProcessors.put(Router.class, new RouterAnnotationPostProcessor(this.messageBus));
postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor(this.messageBus));
postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor(this.messageBus));
postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor(this.messageBus));
this.messageBus = (MessageBus) this.beanFactory.getBean(MessageBusParser.MESSAGE_BUS_BEAN_NAME);
postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor(this.beanFactory));
postProcessors.put(ChannelAdapter.class, new ChannelAdapterAnnotationPostProcessor(this.beanFactory));
postProcessors.put(Router.class, new RouterAnnotationPostProcessor(this.beanFactory));
postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor(this.beanFactory));
postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor(this.beanFactory));
postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor(this.beanFactory));
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

View File

@@ -18,8 +18,8 @@ package org.springframework.integration.config.annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.MessageConsumer;
import org.springframework.integration.router.MethodInvokingChannelResolver;
@@ -34,8 +34,8 @@ import org.springframework.util.StringUtils;
*/
public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Router> {
public RouterAnnotationPostProcessor(MessageBus messageBus) {
super(messageBus);
public RouterAnnotationPostProcessor(BeanFactory beanFactory) {
super(beanFactory);
}
@@ -45,7 +45,7 @@ public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
RouterEndpoint router = new RouterEndpoint(resolver);
String defaultOutputChannelName = annotation.defaultOutputChannel();
if (StringUtils.hasText(defaultOutputChannelName)) {
MessageChannel defaultOutputChannel = this.getChannelRegistry().lookupChannel(defaultOutputChannelName);
MessageChannel defaultOutputChannel = this.channelRegistry.lookupChannel(defaultOutputChannelName);
Assert.notNull(defaultOutputChannel, "unable to resolve defaultOutputChannel '" + defaultOutputChannelName + "'");
router.setDefaultOutputChannel(defaultOutputChannel);
}

View File

@@ -18,8 +18,8 @@ package org.springframework.integration.config.annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.endpoint.ServiceActivatorEndpoint;
import org.springframework.integration.message.MessageConsumer;
import org.springframework.integration.message.MessageMappingMethodInvoker;
@@ -31,8 +31,8 @@ import org.springframework.integration.message.MessageMappingMethodInvoker;
*/
public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<ServiceActivator> {
public ServiceActivatorAnnotationPostProcessor(MessageBus messageBus) {
super(messageBus);
public ServiceActivatorAnnotationPostProcessor(BeanFactory beanFactory) {
super(beanFactory);
}

View File

@@ -18,8 +18,8 @@ package org.springframework.integration.config.annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.message.MessageConsumer;
import org.springframework.integration.splitter.MethodInvokingSplitter;
@@ -30,8 +30,8 @@ import org.springframework.integration.splitter.MethodInvokingSplitter;
*/
public class SplitterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Splitter> {
public SplitterAnnotationPostProcessor(MessageBus messageBus) {
super(messageBus);
public SplitterAnnotationPostProcessor(BeanFactory beanFactory) {
super(beanFactory);
}

View File

@@ -18,8 +18,8 @@ package org.springframework.integration.config.annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.message.MessageConsumer;
import org.springframework.integration.transformer.MethodInvokingTransformer;
import org.springframework.integration.transformer.TransformerEndpoint;
@@ -31,8 +31,8 @@ import org.springframework.integration.transformer.TransformerEndpoint;
*/
public class TransformerAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Transformer> {
public TransformerAnnotationPostProcessor(MessageBus messageBus) {
super(messageBus);
public TransformerAnnotationPostProcessor(BeanFactory beanFactory) {
super(beanFactory);
}

View File

@@ -40,6 +40,9 @@ import org.springframework.util.Assert;
*/
public abstract class AbstractReplyProducingMessageConsumer extends AbstractMessageConsumer implements ChannelRegistryAware {
public static final long DEFAULT_SEND_TIMEOUT = 1000;
private MessageChannel outputChannel;
private volatile ChannelRegistry channelRegistry;
@@ -48,7 +51,13 @@ public abstract class AbstractReplyProducingMessageConsumer extends AbstractMess
private volatile boolean requiresReply = false;
private final MessageChannelTemplate channelTemplate = new MessageChannelTemplate();
private final MessageChannelTemplate channelTemplate;
public AbstractReplyProducingMessageConsumer() {
this.channelTemplate = new MessageChannelTemplate();
this.channelTemplate.setSendTimeout(DEFAULT_SEND_TIMEOUT);
}
public void setOutputChannel(MessageChannel outputChannel) {
@@ -59,6 +68,13 @@ public abstract class AbstractReplyProducingMessageConsumer extends AbstractMess
return this.outputChannel;
}
/**
* Set the timeout for sending reply Messages.
*/
public void setSendTimeout(long sendTimeout) {
this.channelTemplate.setSendTimeout(sendTimeout);
}
public void setChannelRegistry(ChannelRegistry channelRegistry) {
this.channelRegistry = channelRegistry;
}
@@ -122,7 +138,7 @@ public abstract class AbstractReplyProducingMessageConsumer extends AbstractMess
return false;
}
private boolean sendReplyMessage(Message<?> replyMessage, MessageChannel replyChannel) {
protected boolean sendReplyMessage(Message<?> replyMessage, MessageChannel replyChannel) {
return this.channelTemplate.send(replyMessage, replyChannel);
}