INT-3944: Async JMS Outbound Gateway

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

Polishing (PR comments) and Doc Polish

Polishing - PR Comments

Remove custom `async` boolean in favor of the now public setter.

Add exception to `JmsException` hierarchy.

INT-3944: Polishing

* Rename `asyncReplySupported` just to `async`. As well as its getter and setter. Plus fix docs on the matter
* Polishing for the `JmsOutboundGateway` according PR comments
* Rework `JmsOutboundGateway` to return `AbstractIntegrationMessageBuilder` instead of `Message`
since `AbstractMessageProducingHandler` rebuilds `Message` for a new one to copy headers from request
* Add `getPayload()` and `getHeaders()` to the `AbstractIntegrationMessageBuilder` to make the `Routing Slip`
users happy, when the `AbstractIntegrationMessageBuilder` is pushed to the `Routing Slip path` function
* Fix race condition in the `ChatMessageListeningEndpointTests`: the `stanza` parsing is done in the different Thread.

Doc Polishing (Routing Slip)

Fix timing issue in the `LastModifiedFileListFilterTests`:
`Thread.sleep()` not always reflects the reality.
Switch to the "past simulation" via explicit `File.setLastModified()`
This commit is contained in:
Gary Russell
2016-03-23 10:12:13 -04:00
committed by Artem Bilan
parent 0fd72d2d18
commit 4bfcdb9dfa
24 changed files with 539 additions and 325 deletions

View File

@@ -84,7 +84,7 @@ public abstract class AbstractSimpleMessageHandlerFactoryBean<H extends MessageH
private DestinationResolver<MessageChannel> channelResolver;
private Boolean asyncReplySupported;
private Boolean async;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
@@ -150,11 +150,11 @@ public abstract class AbstractSimpleMessageHandlerFactoryBean<H extends MessageH
* namespace. It's not clear that other endpoints would benefit from async support,
* but any subclass of {@link AbstractReplyProducingMessageHandler} can potentially
* return a {@code ListenableFuture<?>}.
* @param asyncReplySupported the asyncReplySupported to set.
* @param async the async to set.
* @since 4.3
*/
public void setAsyncReplySupported(Boolean asyncReplySupported) {
this.asyncReplySupported = asyncReplySupported;
public void setAsync(Boolean async) {
this.async = async;
}
/**
@@ -223,10 +223,10 @@ public abstract class AbstractSimpleMessageHandlerFactoryBean<H extends MessageH
+ (name == null ? "" : (", " + name)) + ".");
}
}
if (this.asyncReplySupported != null) {
if (this.async != null) {
if (actualHandler instanceof AbstractReplyProducingMessageHandler) {
((AbstractReplyProducingMessageHandler) actualHandler)
.setAsyncReplySupported(this.asyncReplySupported);
.setAsync(this.async);
}
}
if (this.handler instanceof Orderable && this.order != null) {

View File

@@ -43,7 +43,7 @@ public class ServiceActivatorParser extends AbstractDelegatingConsumerEndpointPa
@Override
void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "async", "asyncReplySupported");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "async");
}
}

View File

@@ -55,7 +55,7 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
private volatile String outputChannelName;
private volatile boolean asyncReplySupported;
private volatile boolean async;
/**
* Set the timeout for sending reply Messages.
@@ -79,21 +79,20 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
* Allow async replies. If the handler reply is a {@link ListenableFuture} send
* the output when it is satisfied rather than sending the future as the result.
* Only subclasses that support this feature should set it.
* @param asyncReplySupported true to allow.
*
* @param async true to allow.
* @since 4.3
*/
public final void setAsyncReplySupported(boolean asyncReplySupported) {
this.asyncReplySupported = asyncReplySupported;
public final void setAsync(boolean async) {
this.async = async;
}
/**
* @see #setAsyncReplySupported(boolean)
* @see #setAsync(boolean)
* @return true if this handler supports async replies.
* @since 4.3
*/
protected boolean getAsyncReplySupported() {
return this.asyncReplySupported;
protected boolean isAsync() {
return this.async;
}
@Override
@@ -177,7 +176,7 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
}
}
if (this.asyncReplySupported && reply instanceof ListenableFuture<?>) {
if (this.async && reply instanceof ListenableFuture<?>) {
ListenableFuture<?> future = (ListenableFuture<?>) reply;
final Object theReplyChannel = replyChannel;
future.addCallback(new ListenableFutureCallback<Object>() {
@@ -230,7 +229,7 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
}
else {
throw new IllegalArgumentException("The RoutingSlip 'path' can be of " +
"String or RoutingSlipRouteStrategy type, but gotten: " + path);
"String or RoutingSlipRouteStrategy type, but got: " + path.getClass());
}
if (routingSlipPathValue instanceof MessageChannel) {

View File

@@ -107,11 +107,11 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
if (result != null) {
sendOutputs(result, message);
}
else if (this.requiresReply && !getAsyncReplySupported()) {
else if (this.requiresReply && !isAsync()) {
throw new ReplyRequiredException(message, "No reply produced by handler '" +
getComponentName() + "', and its 'requiresReply' property is set to true.");
}
else if (!getAsyncReplySupported() && logger.isDebugEnabled()) {
else if (!isAsync() && logger.isDebugEnabled()) {
logger.debug("handler '" + this + "' produced no reply for request Message: " + message);
}
}

View File

@@ -29,6 +29,13 @@ import org.springframework.messaging.Message;
*/
public interface RoutingSlipRouteStrategy {
/**
* Get the next path for this routing slip.
* @param requestMessage the request message.
* @param reply the reply - depending on context, this may be a user-level domain
* object, a {@link Message} or a {@code AbstractIntegrationMessageBuilder}.
* @return a channel name or another {@link RoutingSlipRouteStrategy}.
*/
Object getNextPath(Message<?> requestMessage, Object reply);
}

View File

@@ -36,6 +36,10 @@ import org.springframework.util.Assert;
*/
public abstract class AbstractIntegrationMessageBuilder<T> {
public abstract T getPayload();
public abstract Map<String, Object> getHeaders();
/**
* Set the value for the given header name. If the provided value is <code>null</code>, the header will be removed.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2016 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.
@@ -38,6 +38,7 @@ import org.springframework.util.Assert;
* @author Oleg Zhurakousky
* @author Dave Syer
* @author Gary Russell
* @author Artem Bilan
*/
public final class MessageBuilder<T> extends AbstractIntegrationMessageBuilder<T> {
@@ -62,6 +63,16 @@ public final class MessageBuilder<T> extends AbstractIntegrationMessageBuilder<T
}
}
@Override
public T getPayload() {
return this.payload;
}
@Override
public Map<String, Object> getHeaders() {
return this.headerAccessor.toMap();
}
/**
* Create a builder for a new {@link Message} instance pre-populated with all of the headers copied from the
* provided message. The payload of the provided Message will also be used as the payload for the new message.
@@ -72,8 +83,7 @@ public final class MessageBuilder<T> extends AbstractIntegrationMessageBuilder<T
*/
public static <T> MessageBuilder<T> fromMessage(Message<T> message) {
Assert.notNull(message, "message must not be null");
MessageBuilder<T> builder = new MessageBuilder<T>(message.getPayload(), message);
return builder;
return new MessageBuilder<T>(message.getPayload(), message);
}
/**
@@ -84,8 +94,7 @@ public final class MessageBuilder<T> extends AbstractIntegrationMessageBuilder<T
* @return A MessageBuilder.
*/
public static <T> MessageBuilder<T> withPayload(T payload) {
MessageBuilder<T> builder = new MessageBuilder<T>(payload, null);
return builder;
return new MessageBuilder<T>(payload, null);
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -30,6 +30,7 @@ import org.springframework.util.StringUtils;
/**
* @author Gary Russell
* @author Artem Bilan
* @since 4.0
*
*/
@@ -53,6 +54,16 @@ public class MutableMessageBuilder<T> extends AbstractIntegrationMessageBuilder<
this.headers = this.mutableMessage.getRawHeaders();
}
@Override
public T getPayload() {
return this.mutableMessage.getPayload();
}
@Override
public Map<String, Object> getHeaders() {
return this.headers;
}
/**
* Create a builder for a new {@link Message} instance pre-populated with all of the headers copied from the
* provided message. The payload of the provided Message will also be used as the payload for the new message.

View File

@@ -111,7 +111,7 @@ public class AsyncHandlerTests {
}
};
this.handler.setAsyncReplySupported(true);
this.handler.setAsync(true);
this.handler.setOutputChannel(this.output);
this.handler.setBeanFactory(mock(BeanFactory.class));
this.latch = new CountDownLatch(1);