INT-3402: Late Resolution for Channel Names

JIRA: https://jira.spring.io/browse/INT-3402

* Move `channel name to channel` resolution logic to the action, e.g. `sendReplyMessage`
* Remove resolution logic from `MessagingAnnotation` Processors
* Fix `AggregatorAnnotationPostProcessor` to invoke `handler.afterPropertiesSet()` one more time,
because of `AbstractMethodAnnotationPostProcessor` `this.beanFactory.initializeBean(handler, handlerBeanName)`
* `Filter` annotation: allow `property-placeholder` for the `discardWithinAdvice`
* Fix `EnricherParserTests4-context.xml` duration: use just `0` for the `reply-timeout`

INT-3402: Add `synchronized double check`

INT-3402: Polishing according PR comments

INT-3402: Optimization for `AbstractCorrelatingMH`
This commit is contained in:
Artem Bilan
2014-07-28 18:02:16 +03:00
committed by Gary Russell
parent 403c91801d
commit 1458f56102
17 changed files with 202 additions and 169 deletions

View File

@@ -57,7 +57,6 @@ import org.springframework.messaging.core.DestinationResolutionException;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Abstract Message handler that holds a buffer of correlated messages in a
@@ -187,6 +186,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
}
public void setOutputChannelName(String outputChannelName) {
Assert.hasText(outputChannelName, "'outputChannelName' must not be empty");
this.outputChannelName = outputChannelName;
}
@@ -215,27 +215,11 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
BeanFactory beanFactory = this.getBeanFactory();
if (beanFactory != null) {
this.messagingTemplate.setBeanFactory(beanFactory);
if (StringUtils.hasText(this.discardChannelName)) {
Assert.isNull(this.discardChannel, "'outputChannelName' and 'discardChannel' are mutually exclusive.");
try {
this.discardChannel = beanFactory.getBean(this.discardChannelName, MessageChannel.class);
}
catch (BeansException e) {
throw new DestinationResolutionException("Failed to look up MessageChannel with name '"
+ this.discardChannelName + "' in the BeanFactory.");
}
}
Assert.state(!(this.discardChannelName != null && this.discardChannel != null),
"'discardChannelName' and 'discardChannel' are mutually exclusive.");
if (StringUtils.hasText(this.outputChannelName)) {
Assert.isNull(this.outputChannel, "'outputChannelName' and 'outputChannel' are mutually exclusive.");
try {
this.outputChannel = this.getBeanFactory().getBean(this.outputChannelName, MessageChannel.class);
}
catch (BeansException e) {
throw new DestinationResolutionException("Failed to look up MessageChannel with name '"
+ this.outputChannelName + "' in the BeanFactory.");
}
}
Assert.state(!(this.outputChannelName != null && this.outputChannel != null),
"'outputChannelName' and 'outputChannel' are mutually exclusive.");
if (this.outputProcessor instanceof BeanFactoryAware) {
((BeanFactoryAware) this.outputProcessor).setBeanFactory(beanFactory);
@@ -272,6 +256,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
}
public void setDiscardChannelName(String discardChannelName) {
Assert.hasText(discardChannelName, "'discardChannelName' must not be empty");
this.discardChannelName = discardChannelName;
}
@@ -291,7 +276,6 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
* schedule than expiring partial groups, set this property. Empty groups will
* then not be removed from the MessageStore until they have not been modified
* for at least this number of milliseconds.
*
* @param minimumTimeoutForEmptyGroups The minimum timeout.
*/
public void setMinimumTimeoutForEmptyGroups(long minimumTimeoutForEmptyGroups) {
@@ -461,7 +445,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
}
}
else {
discardChannel.send(message);
discardMessage(message);
}
}
finally {
@@ -469,6 +453,24 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
}
}
private void discardMessage(Message<?> message) {
if (this.discardChannelName != null) {
synchronized (this) {
if (this.discardChannelName != null) {
try {
this.discardChannel = getBeanFactory().getBean(this.discardChannelName, MessageChannel.class);
this.discardChannelName = null;
}
catch (BeansException e) {
throw new DestinationResolutionException("Failed to look up MessageChannel with name '"
+ this.discardChannelName + "' in the BeanFactory.");
}
}
}
}
this.discardChannel.send(message);
}
/**
* Allows you to provide additional logic that needs to be performed after the MessageGroup was released.
* @param group The group.
@@ -591,17 +593,19 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
if (sendPartialResultOnExpiry) {
if (logger.isDebugEnabled()) {
logger.debug("Prematurely releasing partially complete group with key ["
+ correlationKey + "] to: " + outputChannel);
+ correlationKey + "] to: "
+ (this.outputChannelName != null ? this.outputChannelName : this.outputChannel));
}
completeGroup(correlationKey, group);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Discarding messages of partially complete group with key ["
+ correlationKey + "] to: " + discardChannel);
+ correlationKey + "] to: "
+ (this.discardChannelName != null ? this.discardChannelName : this.discardChannel));
}
for (Message<?> message : group.getMessages()) {
discardChannel.send(message);
discardMessage(message);
}
}
if (this.applicationEventPublisher != null) {
@@ -645,6 +649,22 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
if (message != null) {
replyChannelHeader = message.getHeaders().getReplyChannel();
}
if (this.outputChannelName != null) {
synchronized (this) {
if (this.outputChannelName != null) {
try {
this.outputChannel = getBeanFactory().getBean(this.outputChannelName, MessageChannel.class);
this.outputChannelName = null;
}
catch (BeansException e) {
throw new DestinationResolutionException("Failed to look up MessageChannel with name '"
+ this.outputChannelName + "' in the BeanFactory.");
}
}
}
}
Object replyChannel = this.outputChannel;
if (this.outputChannel == null) {
replyChannel = replyChannelHeader;
@@ -718,12 +738,8 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
Integer messageSequenceNumber = messageHeaderAccessor.getSequenceNumber();
if (messageSequenceNumber != null && messageSequenceNumber > 0) {
Integer messageSequenceSize = messageHeaderAccessor.getSequenceSize();
if (!messageSequenceSize.equals(this.getSequenceSize())) {
return false;
}
else {
return !this.containsSequenceNumber(this.getMessages(), messageSequenceNumber);
}
return messageSequenceSize.equals(this.getSequenceSize())
&& !this.containsSequenceNumber(this.getMessages(), messageSequenceNumber);
}
return true;
}
@@ -738,4 +754,5 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
return false;
}
}
}

View File

@@ -54,7 +54,7 @@ public @interface Filter {
String[] adviceChain() default {};
boolean discardWithinAdvice() default true;
String discardWithinAdvice() default "true";
/*
{@code SmartLifecycle} options.

View File

@@ -70,6 +70,7 @@ public class PublisherAnnotationBeanPostProcessor extends ProxyConfig
/**
* Set the default channel where Messages should be sent if the annotation
* itself does not provide a channel.
* @param defaultChannelName the publisher interceptor defaultChannel
* @since 4.0.3
*/
public void setDefaultChannelName(String defaultChannelName) {

View File

@@ -319,7 +319,7 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
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));
handler.setOutputChannelName(outputChannelName);
}
}

View File

@@ -30,9 +30,7 @@ import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.CorrelationStrategy;
import org.springframework.integration.annotation.ReleaseStrategy;
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.StringUtils;
/**
@@ -72,13 +70,11 @@ public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationP
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);
handler.setDiscardChannelName(discardChannelName);
}
String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel", String.class);
if (StringUtils.hasText(outputChannelName)) {
handler.setOutputChannel(this.channelResolver.resolveDestination(outputChannelName));
handler.setOutputChannelName(outputChannelName);
}
Long sendTimeout = MessagingAnnotationUtils.resolveAttribute(annotations, "sendTimeout", Long.class);
if (sendTimeout != null) {
@@ -90,7 +86,6 @@ public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationP
handler.setSendPartialResultOnExpiry(sendPartialResultsOnExpiry);
}
handler.setBeanFactory(this.beanFactory);
handler.afterPropertiesSet();
return handler;
}

View File

@@ -31,6 +31,7 @@ import org.springframework.integration.handler.BridgeHandler;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Post-processor for the {@link BridgeTo @BridgeTo} annotation.
@@ -64,7 +65,9 @@ public class BridgeToAnnotationPostProcessor extends AbstractMethodAnnotationPos
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
BridgeHandler handler = new BridgeHandler();
String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "value", String.class);
handler.setOutputChannelName(outputChannelName);
if (StringUtils.hasText(outputChannelName)) {
handler.setOutputChannelName(outputChannelName);
}
return handler;
}

View File

@@ -70,28 +70,29 @@ public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
MessageFilter filter = new MessageFilter(selector);
/* TODO will be revised in the future
String discardWithinAdvice = MessagingAnnotationUtils.resolveAttribute(annotations, "discardWithinAdvice",
String.class);
if (StringUtils.hasText(discardWithinAdvice)) {
String discardWithinAdviceValue = this.environment.resolvePlaceholders(discardWithinAdvice);
if (StringUtils.hasText(discardWithinAdviceValue)) {
filter.setDiscardWithinAdvice(Boolean.parseBoolean(discardWithinAdviceValue));
discardWithinAdvice = this.environment.resolvePlaceholders(discardWithinAdvice);
if (StringUtils.hasText(discardWithinAdvice)) {
filter.setDiscardWithinAdvice(Boolean.parseBoolean(discardWithinAdvice));
}
}*/
}
filter.setDiscardWithinAdvice(MessagingAnnotationUtils.resolveAttribute(annotations, "discardWithinAdvice",
Boolean.class));
String throwExceptionOnRejection = MessagingAnnotationUtils.resolveAttribute(annotations,
"throwExceptionOnRejection", String.class);
if (StringUtils.hasText(throwExceptionOnRejection)) {
String throwExceptionOnRejectionValue = this.environment.resolvePlaceholders(throwExceptionOnRejection);
filter.setThrowExceptionOnRejection(Boolean.parseBoolean(throwExceptionOnRejectionValue));
if (StringUtils.hasText(throwExceptionOnRejectionValue)) {
filter.setThrowExceptionOnRejection(Boolean.parseBoolean(throwExceptionOnRejectionValue));
}
}
String discardChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "discardChannel", String.class);
filter.setDiscardChannelName(discardChannelName);
if (StringUtils.hasText(discardChannelName)) {
filter.setDiscardChannelName(discardChannelName);
}
this.setOutputChannelIfPresent(annotations, filter);
return filter;

View File

@@ -75,7 +75,9 @@ public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
}
String defaultOutputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations,
"defaultOutputChannel", String.class);
router.setDefaultOutputChannelName(defaultOutputChannelName);
if (StringUtils.hasText(defaultOutputChannelName)) {
router.setDefaultOutputChannelName(defaultOutputChannelName);
}
String applySequence = MessagingAnnotationUtils.resolveAttribute(annotations, "applySequence", String.class);
if (StringUtils.hasText(applySequence)) {

View File

@@ -25,7 +25,6 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.core.DestinationResolutionException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Message Handler that delegates to a {@link MessageSelector}. If and only if
@@ -52,9 +51,7 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa
private volatile String discardChannelName;
/**
* Create a MessageFilter that will delegate to the given
* {@link MessageSelector}.
*
* Create a MessageFilter that will delegate to the given {@link MessageSelector}.
* @param selector The message selector.
*/
public MessageFilter(MessageSelector selector) {
@@ -72,9 +69,8 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa
* a discard channel is provided, but if so, it will still apply
* (in such a case, the Message will be sent to the discard channel,
* and <em>then</em> the exception will be thrown).
* @see #setDiscardChannel(MessageChannel)
*
* @param throwExceptionOnRejection true if an exception should be thrown.
* @see #setDiscardChannel(MessageChannel)
*/
public void setThrowExceptionOnRejection(boolean throwExceptionOnRejection) {
this.throwExceptionOnRejection = throwExceptionOnRejection;
@@ -86,9 +82,7 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa
* the 'throwExceptionOnRejection' flag determines whether rejected Messages
* trigger an exception. That value is evaluated regardless of the presence
* of a discard channel.
*
* @param discardChannel The discard channel.
*
* @see #setThrowExceptionOnRejection(boolean)
*/
public void setDiscardChannel(MessageChannel discardChannel) {
@@ -96,6 +90,7 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa
}
public void setDiscardChannelName(String discardChannelName) {
Assert.hasText(discardChannelName, "'discardChannelName' must not be empty");
this.discardChannelName = discardChannelName;
}
@@ -103,7 +98,6 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa
* Set to 'true' if you wish the discard processing to occur within any
* request handler advice applied to this filter. Also applies to
* throwing an exception on rejection. Default: true.
*
* @param discardWithinAdvice true to discard within the advice.
*/
public void setDiscardWithinAdvice(boolean discardWithinAdvice) {
@@ -117,16 +111,8 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa
@Override
protected void doInit() {
if (StringUtils.hasText(this.discardChannelName)) {
Assert.isNull(this.discardChannel, "'outputChannelName' and 'discardChannel' are mutually exclusive.");
try {
this.discardChannel = this.getBeanFactory().getBean(this.discardChannelName, MessageChannel.class);
}
catch (BeansException e) {
throw new DestinationResolutionException("Failed to look up MessageChannel with name '"
+ this.discardChannelName + "' in the BeanFactory.");
}
}
Assert.state(!(this.discardChannelName != null && this.discardChannel != null),
"'discardChannelName' and 'discardChannel' are mutually exclusive.");
if (this.selector instanceof AbstractMessageProcessingSelector) {
((AbstractMessageProcessingSelector) this.selector).setConversionService(this.getConversionService());
}
@@ -148,11 +134,27 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa
@Override
public Object postProcess(Message<?> message, Object result) {
if (result == null) {
if (this.discardChannelName != null) {
synchronized (this) {
if (this.discardChannelName != null) {
try {
this.discardChannel = this.getBeanFactory()
.getBean(this.discardChannelName, MessageChannel.class);
this.discardChannelName = null;
}
catch (BeansException e) {
throw new DestinationResolutionException("Failed to look up MessageChannel with name '"
+ this.discardChannelName + "' in the BeanFactory.");
}
}
}
}
if (this.discardChannel != null) {
this.getMessagingTemplate().send(this.discardChannel, message);
}
if (this.throwExceptionOnRejection) {
throw new MessageRejectedException(message, "MessageFilter '" + this.getComponentName() + "' rejected Message");
throw new MessageRejectedException(message, "MessageFilter '" + this.getComponentName()
+ "' rejected Message");
}
}
return result;

View File

@@ -35,7 +35,6 @@ import org.springframework.messaging.core.DestinationResolver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Base class for MessageHandlers that are capable of producing replies.
@@ -44,6 +43,7 @@ import org.springframework.util.StringUtils;
* @author Iwein Fuld
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
public abstract class AbstractReplyProducingMessageHandler extends AbstractMessageHandler
implements MessageProducer, BeanClassLoaderAware {
@@ -75,12 +75,12 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
}
public void setOutputChannelName(String outputChannelName) {
Assert.hasText(outputChannelName, "'outputChannelName' must not be empty");
this.outputChannelName = outputChannelName;
}
/**
* Set the timeout for sending reply Messages.
*
* @param sendTimeout The send timeout.
*/
public void setSendTimeout(long sendTimeout) {
@@ -89,7 +89,6 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
/**
* Set the DestinationResolver&lt;MessageChannel&gt; to be used when there is no default output channel.
*
* @param channelResolver The channel resolver.
*/
public void setChannelResolver(DestinationResolver<MessageChannel> channelResolver) {
@@ -100,7 +99,6 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
/**
* Flag whether a reply is required. If true an incoming message MUST result in a reply message being sent.
* If false an incoming message MAY result in a reply message being sent. Default is false.
*
* @param requiresReply true if a reply is required.
*/
public void setRequiresReply(boolean requiresReply) {
@@ -109,7 +107,6 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
/**
* Provides access to the {@link MessagingTemplate} for subclasses.
*
* @return The messaging template.
*/
protected MessagingTemplate getMessagingTemplate() {
@@ -134,18 +131,10 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
@Override
protected final void onInit() {
Assert.state(!(this.outputChannelName != null && this.outputChannel != null),
"'outputChannelName' and 'outputChannel' are mutually exclusive.");
if (this.getBeanFactory() != null) {
this.messagingTemplate.setBeanFactory(getBeanFactory());
if (StringUtils.hasText(this.outputChannelName)) {
Assert.isNull(this.outputChannel, "'outputChannelName' and 'outputChannel' are mutually exclusive.");
try {
this.outputChannel = this.getBeanFactory().getBean(this.outputChannelName, MessageChannel.class);
}
catch (BeansException e) {
throw new DestinationResolutionException("Failed to look up MessageChannel with name '"
+ this.outputChannelName + "' in the BeanFactory.");
}
}
}
if (!CollectionUtils.isEmpty(this.adviceChain)) {
ProxyFactory proxyFactory = new ProxyFactory(new AdvisedRequestHandler());
@@ -229,7 +218,6 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
* Send a reply Message. The 'replyChannelHeaderValue' will be considered only if this handler's
* 'outputChannel' is <code>null</code>. In that case, the header value must not also be
* <code>null</code>, and it must be an instance of either String or {@link MessageChannel}.
*
* @param replyMessage the reply Message to send
* @param replyChannelHeaderValue the 'replyChannel' header value from the original request
*/
@@ -237,6 +225,22 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
if (logger.isDebugEnabled()) {
logger.debug("handler '" + this + "' sending reply Message: " + replyMessage);
}
if (this.outputChannelName != null) {
synchronized (this) {
if (this.outputChannelName != null) {
try {
this.outputChannel = this.getBeanFactory().getBean(this.outputChannelName, MessageChannel.class);
this.outputChannelName = null;
}
catch (BeansException e) {
throw new DestinationResolutionException("Failed to look up MessageChannel with name '"
+ this.outputChannelName + "' in the BeanFactory.");
}
}
}
}
if (this.outputChannel != null) {
this.sendMessage(replyMessage, this.outputChannel);
}
@@ -251,7 +255,6 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
/**
* Send the message to the given channel. The channel must be a String or
* {@link MessageChannel} instance, never <code>null</code>.
*
* @param message The message.
* @param channel The channel to which to send the message.
*/
@@ -279,7 +282,6 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
/**
* Subclasses may override this. True by default.
*
* @return true if the request headers should be copied.
*/
protected boolean shouldCopyRequestHeaders() {
@@ -291,7 +293,6 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
* value may be a Message, a MessageBuilder, or any plain Object. The base class
* will handle the final creation of a reply Message from any of those starting
* points. If the return value is null, the Message flow will end here.
*
* @param requestMessage The request message.
* @return The result of handling the message, or {@code null}.
*/
@@ -318,7 +319,6 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
return AbstractReplyProducingMessageHandler.this.toString();
}
}
}

View File

@@ -31,7 +31,6 @@ import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.core.DestinationResolutionException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Base class for all Message Routers.
@@ -62,9 +61,8 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
* fails to return any channels. If no default channel is provided and channel
* resolution fails to return any channels, the router will throw an
* {@link MessageDeliveryException}.
*
* <p>
* If messages shall be ignored (dropped) instead, please provide a {@link NullChannel}.
*
* @param defaultOutputChannel The default output channel.
*/
public void setDefaultOutputChannel(MessageChannel defaultOutputChannel) {
@@ -72,13 +70,13 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
}
public void setDefaultOutputChannelName(String defaultOutputChannelName) {
Assert.hasText(defaultOutputChannelName, "'defaultOutputChannelName' must not be empty");
this.defaultOutputChannelName = defaultOutputChannelName;
}
/**
* Set the timeout for sending a message to the resolved channel. By default, there is no timeout, meaning the send
* will block indefinitely.
*
* @param timeout The timeout.
*/
public void setTimeout(long timeout) {
@@ -89,7 +87,6 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
* Specify whether send failures for one or more of the recipients should be ignored. By default this is
* <code>false</code> meaning that an Exception will be thrown whenever a send fails. To override this and suppress
* Exceptions, set the value to <code>true</code>.
*
* @param ignoreSendFailures true to ignore send failures.
*/
public void setIgnoreSendFailures(boolean ignoreSendFailures) {
@@ -101,7 +98,6 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
* channels. By default, this value is <code>false</code> meaning that sequence headers will <em>not</em> be
* applied. If planning to use an Aggregator downstream with the default correlation and completion strategies, you
* should set this flag to <code>true</code>.
*
* @param applySequence true to apply sequence information.
*/
public void setApplySequence(boolean applySequence) {
@@ -115,7 +111,6 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
/**
* Provides {@link MessagingTemplate} access for subclasses
*
* @return The messaging template.
*/
protected MessagingTemplate getMessagingTemplate() {
@@ -136,25 +131,16 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
@Override
protected void onInit() throws Exception {
super.onInit();
Assert.state(!(this.defaultOutputChannelName != null && this.defaultOutputChannel != null),
"'defaultOutputChannelName' and 'defaultOutputChannel' are mutually exclusive.");
if (this.getBeanFactory() != null) {
this.messagingTemplate.setBeanFactory(this.getBeanFactory());
if (StringUtils.hasText(this.defaultOutputChannelName)) {
Assert.isNull(this.defaultOutputChannel, "'defaultOutputChannelName' and 'defaultOutputChannel' are mutually exclusive.");
try {
this.defaultOutputChannel = this.getBeanFactory().getBean(this.defaultOutputChannelName, MessageChannel.class);
}
catch (BeansException e) {
throw new DestinationResolutionException("Failed to look up MessageChannel with name '"
+ this.defaultOutputChannelName + "' in the BeanFactory.");
}
}
}
}
/**
* Subclasses must implement this method to return a Collection of zero or more
* MessageChannels to which the given Message should be routed.
*
* @param message The message.
* @return The collection of message channels.
*/
@@ -168,8 +154,11 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
int sequenceSize = results.size();
int sequenceNumber = 1;
for (MessageChannel channel : results) {
final Message<?> messageToSend = (!this.applySequence) ? message : this.getMessageBuilderFactory().fromMessage(message)
.pushSequenceDetails(message.getHeaders().getId(), sequenceNumber++, sequenceSize).build();
final Message<?> messageToSend =
!this.applySequence ? message : (this.getMessageBuilderFactory()
.fromMessage(message)
.pushSequenceDetails(message.getHeaders().getId(), sequenceNumber++, sequenceSize)
.build());
if (channel != null) {
try {
this.messagingTemplate.send(channel, messageToSend);
@@ -187,6 +176,21 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
}
}
if (!sent) {
if (this.defaultOutputChannelName != null) {
synchronized (this) {
if (this.defaultOutputChannelName != null) {
try {
this.defaultOutputChannel = getBeanFactory()
.getBean(this.defaultOutputChannelName, MessageChannel.class);
this.defaultOutputChannelName = null;
}
catch (BeansException e) {
throw new DestinationResolutionException("Failed to look up MessageChannel with name '"
+ this.defaultOutputChannelName + "' in the BeanFactory.");
}
}
}
}
if (this.defaultOutputChannel != null) {
this.messagingTemplate.send(this.defaultOutputChannel, message);
}

View File

@@ -198,7 +198,6 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler
* object</b>.
* <p>
* For instance the following SpEL expressions (among others) are possible:
* <p>
* <ul>
* <li>payload.foo</li>
* <li>headers.foobar</li>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -37,11 +37,8 @@ import org.springframework.integration.aggregator.MethodInvokingReleaseStrategy;
import org.springframework.integration.aggregator.SequenceSizeReleaseStrategy;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.core.DestinationResolver;
/**
* @author Marius Bogoevici
@@ -71,10 +68,8 @@ public class AggregatorAnnotationTests {
final String endpointName = "endpointWithCustomizedAnnotation";
MessageHandler aggregator = this.getAggregator(context, endpointName);
assertTrue(getPropertyValue(aggregator, "releaseStrategy") instanceof SequenceSizeReleaseStrategy);
DestinationResolver<MessageChannel> channelResolver = new BeanFactoryChannelResolver(context);
assertEquals(channelResolver.resolveDestination("outputChannel"), getPropertyValue(aggregator, "outputChannel"));
assertEquals(channelResolver.resolveDestination("discardChannel"), getPropertyValue(aggregator,
"discardChannel"));
assertEquals("outputChannel", getPropertyValue(aggregator, "outputChannelName"));
assertEquals("discardChannel", getPropertyValue(aggregator, "discardChannelName"));
assertEquals(98765432l, getPropertyValue(aggregator, "messagingTemplate.sendTimeout"));
assertEquals(true, getPropertyValue(aggregator, "sendPartialResultOnExpiry"));
}

View File

@@ -198,7 +198,7 @@ public class FilterAnnotationPostProcessorTests {
@MessageEndpoint
private static class TestFilterWithBooleanPrimitive {
@Filter(inputChannel="input", outputChannel="output")
@Filter(inputChannel = "input", outputChannel = "output")
public boolean filter(String s) {
return !s.contains("bad");
}
@@ -207,7 +207,7 @@ public class FilterAnnotationPostProcessorTests {
@MessageEndpoint
private static class TestFilterWithAdviceDiscardWithin {
@Filter(inputChannel="input", outputChannel="output", adviceChain="adviceChain")
@Filter(inputChannel = "input", outputChannel = "output", adviceChain = "adviceChain")
public boolean filter(String s) {
return !s.contains("bad");
}
@@ -216,7 +216,7 @@ public class FilterAnnotationPostProcessorTests {
@MessageEndpoint
private static class TestFilterWithAdviceDiscardWithinTwice {
@Filter(inputChannel="input", outputChannel="output", adviceChain={"adviceChain1", "adviceChain2"})
@Filter(inputChannel = "input", outputChannel = "output", adviceChain = {"adviceChain1", "adviceChain2"})
public boolean filter(String s) {
return !s.contains("bad");
}
@@ -225,8 +225,8 @@ public class FilterAnnotationPostProcessorTests {
@MessageEndpoint
private static class TestFilterWithAdviceDiscardWithout {
@Filter(inputChannel="input", outputChannel="output",
adviceChain="adviceChain", discardWithinAdvice=false)
@Filter(inputChannel = "input", outputChannel = "output",
adviceChain = "adviceChain", discardWithinAdvice = "false")
public boolean filter(String s) {
return !s.contains("bad");
}
@@ -235,7 +235,7 @@ public class FilterAnnotationPostProcessorTests {
@MessageEndpoint
private static class TestFilterWithBooleanWrapperClass {
@Filter(inputChannel="input", outputChannel="output")
@Filter(inputChannel = "input", outputChannel = "output")
public Boolean filter(String s) {
return !s.contains("bad");
}
@@ -245,7 +245,7 @@ public class FilterAnnotationPostProcessorTests {
@MessageEndpoint
private static class TestFilterWithStringReturnType {
@Filter(inputChannel="input", outputChannel="output")
@Filter(inputChannel = "input", outputChannel = "output")
public String filter(String s) {
return s;
}
@@ -255,7 +255,7 @@ public class FilterAnnotationPostProcessorTests {
@MessageEndpoint
private static class TestFilterWithVoidReturnType {
@Filter(inputChannel="input", outputChannel="output")
@Filter(inputChannel = "input", outputChannel = "output")
public void filter(String s) {
}
}

View File

@@ -26,7 +26,7 @@
<enricher id="enricher" input-channel="input"
request-channel="requests1" request-timeout="1234"
reply-timeout="9876" reply-channel="replies"
reply-timeout="0" reply-channel="replies"
order="99" should-clone-payload="true" output-channel="output">
<property name="name" expression="payload.sourceName" null-result-expression="'Could not determine the name'"/>
<property name="age" value="42" null-result-expression="'11'"/>

View File

@@ -17,7 +17,15 @@
package org.springframework.integration.configuration;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
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.mockito.Mockito.mock;
import java.lang.annotation.ElementType;
@@ -402,19 +410,19 @@ public class EnableIntegrationTests {
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"));
assertEquals("annOutput", TestUtils.getPropertyValue(consumer, "handler.outputChannelName"));
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);
"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"));
assertEquals("annOutput", TestUtils.getPropertyValue(consumer, "handler.outputChannelName"));
assertSame(context.getBean("annAdvice1"), TestUtils.getPropertyValue(consumer,
"handler.adviceChain", List.class).get(0));
assertEquals(2000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
@@ -425,7 +433,7 @@ public class EnableIntegrationTests {
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"));
assertEquals("annOutput", TestUtils.getPropertyValue(consumer, "handler.outputChannelName"));
assertSame(context.getBean("annAdvice"), TestUtils.getPropertyValue(consumer,
"handler.adviceChain", List.class).get(0));
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
@@ -437,7 +445,7 @@ public class EnableIntegrationTests {
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"));
assertEquals("annOutput", TestUtils.getPropertyValue(consumer, "handler.outputChannelName"));
assertSame(context.getBean("annAdvice"), TestUtils.getPropertyValue(consumer,
"handler.adviceChain", List.class).get(0));
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
@@ -448,8 +456,8 @@ public class EnableIntegrationTests {
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("annOutput", TestUtils.getPropertyValue(consumer, "handler.outputChannelName"));
assertEquals("annOutput", TestUtils.getPropertyValue(consumer, "handler.discardChannelName"));
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "handler.messagingTemplate.sendTimeout"));
assertFalse(TestUtils.getPropertyValue(consumer, "handler.sendPartialResultOnExpiry", Boolean.class));
@@ -460,8 +468,8 @@ public class EnableIntegrationTests {
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("annOutput", TestUtils.getPropertyValue(consumer, "handler.outputChannelName"));
assertEquals("annOutput", TestUtils.getPropertyValue(consumer, "handler.discardChannelName"));
assertEquals(1000L, TestUtils.getPropertyValue(consumer, "trigger.period"));
assertEquals(75L, TestUtils.getPropertyValue(consumer, "handler.messagingTemplate.sendTimeout"));
assertTrue(TestUtils.getPropertyValue(consumer, "handler.sendPartialResultOnExpiry", Boolean.class));
@@ -795,8 +803,8 @@ public class EnableIntegrationTests {
return new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
asyncAnnotationProcessLatch().countDown();
asyncAnnotationProcessThread().set(Thread.currentThread());
asyncAnnotationProcessLatch().countDown();
asyncAnnotationProcessThread().set(Thread.currentThread());
}
};
}
@@ -996,7 +1004,7 @@ public class EnableIntegrationTests {
}
@MyServiceActivator1(inputChannel = "annInput1", autoStartup = "true",
adviceChain = { "annAdvice1" }, poller = @Poller(fixedRate = "2000") )
adviceChain = {"annAdvice1"}, poller = @Poller(fixedRate = "2000"))
public Integer annCount1() {
return 0;
}
@@ -1074,11 +1082,11 @@ public class EnableIntegrationTests {
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ServiceActivator(autoStartup = "false",
phase = "23",
inputChannel = "annInput",
outputChannel = "annOutput",
adviceChain = { "annAdvice" },
poller = @Poller(fixedDelay = "1000"))
phase = "23",
inputChannel = "annInput",
outputChannel = "annOutput",
adviceChain = {"annAdvice"},
poller = @Poller(fixedDelay = "1000"))
public static @interface MyServiceActivator {
String inputChannel() default "";
@@ -1181,22 +1189,22 @@ public class EnableIntegrationTests {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ServiceActivator(autoStartup = "false",
phase = "23",
inputChannel = "annInput",
outputChannel = "annOutput",
adviceChain = { "annAdvice" },
poller = @Poller(fixedDelay = "1000"))
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"))
phase = "23",
inputChannel = "annInput",
outputChannel = "annOutput",
discardChannel = "annOutput",
poller = @Poller(fixedDelay = "1000"))
public static @interface MyAggregator {
String inputChannel() default "";
@@ -1219,13 +1227,13 @@ public class EnableIntegrationTests {
@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"))
phase = "23",
inputChannel = "annInput",
outputChannel = "annOutput",
discardChannel = "annOutput",
sendPartialResultsOnExpiry = false,
sendTimeout = 1000L,
poller = @Poller(fixedDelay = "1000"))
public static @interface MyAggregatorDefaultOverrideDefaults {
boolean sendPartialResultsOnExpiry() default true;

View File

@@ -16,8 +16,12 @@
package org.springframework.integration.router;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections;
@@ -333,7 +337,7 @@ public class RecipientListRouterTests {
assertNotNull(result1a);
assertNotNull(result1b);
assertEquals("test", result1a.getPayload());
assertEquals(1,new IntegrationMessageHeaderAccessor(result1a).getSequenceNumber().intValue());
assertEquals(1, new IntegrationMessageHeaderAccessor(result1a).getSequenceNumber().intValue());
assertEquals(2, new IntegrationMessageHeaderAccessor(result1a).getSequenceSize().intValue());
assertEquals(message.getHeaders().getId(), new IntegrationMessageHeaderAccessor(result1a).getCorrelationId());
assertEquals("test", result1b.getPayload());
@@ -426,6 +430,8 @@ public class RecipientListRouterTests {
router.setBeanFactory(beanFactory);
router.afterPropertiesSet();
router.handleMessage(new GenericMessage<String>("foo"));
assertSame(defaultChannel, TestUtils.getPropertyValue(router, "defaultOutputChannel"));
Mockito.verify(beanFactory).getBean(Mockito.eq("defaultChannel"), Mockito.eq(MessageChannel.class));
}