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

@@ -86,6 +86,7 @@ public class JmsOutboundGatewayParserTests {
accessor = new DirectFieldAccessor(gateway);
int deliveryMode = (Integer)accessor.getPropertyValue("deliveryMode");
assertEquals(DeliveryMode.PERSISTENT, deliveryMode);
assertTrue(TestUtils.getPropertyValue(gateway, "async", Boolean.class));
DefaultMessageListenerContainer container = TestUtils.getPropertyValue(gateway, "replyContainer",
DefaultMessageListenerContainer.class);
assertEquals(4, TestUtils.getPropertyValue(container, "concurrentConsumers"));
@@ -116,6 +117,7 @@ public class JmsOutboundGatewayParserTests {
"jmsOutboundGatewayWithDeliveryPersistent.xml", this.getClass());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("advised");
JmsOutboundGateway gateway = TestUtils.getPropertyValue(endpoint, "handler", JmsOutboundGateway.class);
assertFalse(TestUtils.getPropertyValue(gateway, "async", Boolean.class));
gateway.handleMessage(new GenericMessage<String>("foo"));
assertEquals(1, adviceCalled);
assertEquals(3, TestUtils.getPropertyValue(gateway, "replyContainer.sessionAcknowledgeMode"));
@@ -287,13 +289,17 @@ public class JmsOutboundGatewayParserTests {
public interface SampleGateway{
String echo(String value);
}
public static class SampleService{
public String echo(String value){
return value.toUpperCase();
}
}
public static class FooAdvice extends AbstractRequestHandlerAdvice {
@@ -305,4 +311,5 @@ public class JmsOutboundGatewayParserTests {
}
}
}

View File

@@ -16,14 +16,14 @@
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/integration/jms
http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd">
<context:property-placeholder properties-ref="props"/>
<util:properties id="props">
<prop key="jmsAcknowledgeModeTransacted">transacted</prop>
<prop key="jmsAcknowledgeModeDupsOk">dups-ok</prop>
</util:properties>
<si:channel id="requestChannel"/>
<jms:outbound-gateway id="jmsGateway"
@@ -31,6 +31,8 @@
request-channel="requestChannel"
delivery-persistent="true"
idle-reply-listener-timeout="1234"
async="true"
correlation-key="JMSCorrelationID"
auto-startup="false">
<jms:reply-listener
acknowledge="${jmsAcknowledgeModeTransacted}"

View File

@@ -0,0 +1,174 @@
/*
* Copyright 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.
* 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.jms.request_reply;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.jms.JmsOutboundGateway;
import org.springframework.integration.jms.JmsTimeoutException;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.support.JmsHeaders;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Gary Russell
* @since 4.3
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class AsyncGatewayTests {
@Autowired
private CachingConnectionFactory ccf;
@Autowired
private JmsOutboundGateway gateway1;
@Autowired
private JmsOutboundGateway gateway2;
@Test
public void testWithReply() throws Exception {
QueueChannel replies = new QueueChannel();
this.gateway1.setOutputChannel(replies);
this.gateway1.start();
this.gateway1.handleMessage(MessageBuilder.withPayload("foo")
.setHeader(JmsHeaders.CORRELATION_ID, "baz")// make sure it's restored in case we're from an upstream gw
.build());
JmsTemplate template = new JmsTemplate(this.ccf);
template.setReceiveTimeout(10000);
final Message received = template.receive("asyncTest1");
assertNotNull(received);
template.send(received.getJMSReplyTo(), new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage textMessage = session.createTextMessage("bar");
textMessage.setJMSCorrelationID(received.getJMSCorrelationID());
return textMessage;
}
});
org.springframework.messaging.Message<?> reply = replies.receive(10000);
assertNotNull(reply);
assertEquals("bar", reply.getPayload());
assertEquals("baz", reply.getHeaders().get(JmsHeaders.CORRELATION_ID));
this.gateway1.stop();
}
@Test
public void testWithTimeout() throws Exception {
QueueChannel errors = new QueueChannel();
this.gateway2.setOutputChannel(errors);
this.gateway2.start();
this.gateway2.handleMessage(MessageBuilder.withPayload("foo").setErrorChannel(errors).build());
JmsTemplate template = new JmsTemplate(this.ccf);
final Message received = template.receive("asyncTest3");
assertNotNull(received);
org.springframework.messaging.Message<?> error = errors.receive(10000);
assertNotNull(error);
assertThat(error, instanceOf(ErrorMessage.class));
assertThat(error.getPayload(), instanceOf(MessagingException.class));
assertThat(((MessagingException) error.getPayload()).getCause(), instanceOf(JmsTimeoutException.class));
assertEquals("foo", ((MessagingException) error.getPayload()).getFailedMessage().getPayload());
this.gateway1.stop();
}
@Test
@DirtiesContext
public void testWithTimeoutNoReplyRequired() throws Exception {
QueueChannel errors = new QueueChannel();
this.gateway2.setOutputChannel(errors);
this.gateway2.setRequiresReply(false);
this.gateway2.start();
this.gateway2.handleMessage(MessageBuilder.withPayload("foo").setErrorChannel(errors).build());
JmsTemplate template = new JmsTemplate(this.ccf);
final Message received = template.receive("asyncTest3");
assertNotNull(received);
org.springframework.messaging.Message<?> error = errors.receive(1000);
assertNull(error);
this.gateway1.stop();
}
@Configuration
@EnableIntegration
public static class Config {
@Bean
public CachingConnectionFactory ccf() {
return new CachingConnectionFactory(
new ActiveMQConnectionFactory("vm://localhosti?broker.persistent=false"));
}
@Bean
public JmsOutboundGateway gateway1() {
JmsOutboundGateway gateway = new JmsOutboundGateway();
gateway.setUseReplyContainer(true);
gateway.setConnectionFactory(ccf());
gateway.setRequestDestinationName("asyncTest1");
gateway.setReplyDestinationName("asyncTest2");
gateway.setRequiresReply(true);
gateway.setReceiveTimeout(10000);
gateway.setAsync(true);
gateway.setCorrelationKey("JMSCorrelationID");
return gateway;
}
@Bean
public JmsOutboundGateway gateway2() {
JmsOutboundGateway gateway = new JmsOutboundGateway();
gateway.setUseReplyContainer(true);
gateway.setConnectionFactory(ccf());
gateway.setRequestDestinationName("asyncTest3");
gateway.setReplyDestinationName("asyncTest4");
gateway.setRequiresReply(true);
gateway.setReceiveTimeout(10);
gateway.setAsync(true);
gateway.setCorrelationKey("JMSCorrelationID");
return gateway;
}
}
}