INT-3965: AMQP Header Mapping Changes

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

Rework According to AMQP-589
This commit is contained in:
Gary Russell
2016-03-11 22:59:13 -05:00
committed by Artem Bilan
parent 518df4f74b
commit 78aaa6dac6
18 changed files with 198 additions and 57 deletions

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.
@@ -105,7 +105,10 @@ abstract class AbstractAmqpInboundAdapterParser extends AbstractSingleBeanDefini
}
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter");
IntegrationNamespaceUtils.configureHeaderMapper(element, builder, parserContext, DefaultAmqpHeaderMapper.class, null);
BeanDefinitionBuilder mapperBuilder = BeanDefinitionBuilder
.genericBeanDefinition(DefaultAmqpHeaderMapper.class);
mapperBuilder.setFactoryMethod("inboundMapper");
IntegrationNamespaceUtils.configureHeaderMapper(element, builder, parserContext, mapperBuilder, null);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 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.
@@ -67,8 +67,11 @@ public class AmqpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-delivery-mode");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "lazy-connect");
BeanDefinitionBuilder mapperBuilder = BeanDefinitionBuilder
.genericBeanDefinition(DefaultAmqpHeaderMapper.class);
mapperBuilder.setFactoryMethod("outboundMapper");
IntegrationNamespaceUtils.configureHeaderMapper(element, builder, parserContext,
DefaultAmqpHeaderMapper.class, null);
mapperBuilder, null);
BeanDefinition confirmCorrelationExpression =
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("confirm-correlation-expression", element);

View File

@@ -82,7 +82,10 @@ public class AmqpOutboundGatewayParser extends AbstractConsumerEndpointParser {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "confirm-ack-channel");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "confirm-nack-channel");
IntegrationNamespaceUtils.configureHeaderMapper(element, builder, parserContext, DefaultAmqpHeaderMapper.class,
BeanDefinitionBuilder mapperBuilder = BeanDefinitionBuilder
.genericBeanDefinition(DefaultAmqpHeaderMapper.class);
mapperBuilder.setFactoryMethod("outboundMapper");
IntegrationNamespaceUtils.configureHeaderMapper(element, builder, parserContext, mapperBuilder,
null);
return builder;

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.
@@ -18,8 +18,6 @@ package org.springframework.integration.amqp.inbound;
import java.util.Map;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
@@ -33,6 +31,8 @@ import org.springframework.integration.context.OrderlyShutdownCapable;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.util.Assert;
import com.rabbitmq.client.Channel;
/**
* Adapter that receives Messages from an AMQP Queue, converts them into
* Spring Integration Messages, and sends the results to a Message Channel.
@@ -49,7 +49,7 @@ public class AmqpInboundChannelAdapter extends MessageProducerSupport implements
private volatile MessageConverter messageConverter = new SimpleMessageConverter();
private volatile AmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
private volatile AmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
public AmqpInboundChannelAdapter(AbstractMessageListenerContainer listenerContainer) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 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.
@@ -59,7 +59,7 @@ public class AmqpInboundGateway extends MessagingGatewaySupport {
private volatile MessageConverter amqpMessageConverter = new SimpleMessageConverter();
private volatile AmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
private volatile AmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
private Address defaultReplyTo;

View File

@@ -77,7 +77,7 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler
private volatile ExpressionEvaluatingMessageProcessor<String> exchangeNameGenerator;
private volatile AmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
private volatile AmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper();
private volatile Expression confirmCorrelationExpression;

View File

@@ -77,6 +77,7 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
STANDARD_HEADER_NAMES.add(AmqpHeaders.MESSAGE_COUNT);
STANDARD_HEADER_NAMES.add(AmqpHeaders.MESSAGE_ID);
STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_DELAY);
STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_DELIVERY_MODE);
STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_EXCHANGE);
STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_ROUTING_KEY);
STANDARD_HEADER_NAMES.add(AmqpHeaders.REDELIVERED);
@@ -112,6 +113,11 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
CONSUMER_METADATA_PRESENT = consumerTagHeader.get();
}
/**
* @deprecated - use {@link #inboundMapper()} and {@link #outboundMapper()} depending
* on the context in which the mapper is to be used.
*/
@Deprecated
public DefaultAmqpHeaderMapper() {
super(AmqpHeaders.PREFIX, STANDARD_HEADER_NAMES, STANDARD_HEADER_NAMES);
}
@@ -147,9 +153,9 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
if (correlationId != null && correlationId.length > 0) {
headers.put(AmqpHeaders.CORRELATION_ID, correlationId);
}
MessageDeliveryMode deliveryMode = amqpMessageProperties.getDeliveryMode();
if (deliveryMode != null) {
headers.put(AmqpHeaders.DELIVERY_MODE, deliveryMode);
MessageDeliveryMode receivedDeliveryMode = amqpMessageProperties.getReceivedDeliveryMode();
if (receivedDeliveryMode != null) {
headers.put(AmqpHeaders.RECEIVED_DELIVERY_MODE, receivedDeliveryMode);
}
long deliveryTag = amqpMessageProperties.getDeliveryTag();
if (deliveryTag > 0) {
@@ -405,4 +411,50 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
}
}
public static DefaultAmqpHeaderMapper inboundMapper() {
DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
mapper.setRequestHeaderNames(inboundRequestHeaders());
mapper.setReplyHeaderNames(inboundReplyHeaders());
return mapper;
}
public static DefaultAmqpHeaderMapper outboundMapper() {
DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
mapper.setRequestHeaderNames(outboundRequestHeaders());
mapper.setReplyHeaderNames(outboundReplyHeaders());
return mapper;
}
/**
* @return the default request headers for an inbound mapper.
*/
public static String[] inboundRequestHeaders() {
return safeInboundHeaders();
}
/**
* @return the default reply headers for an inbound mapper.
*/
public static String[] inboundReplyHeaders() {
return new String[] { "*" };
}
/**
* @return the default request headers for an outbound mapper.
*/
public static String[] outboundRequestHeaders() {
return new String[] { "*" };
}
/**
* @return the default reply headers for an outbound mapper.
*/
public static String[] outboundReplyHeaders() {
return safeInboundHeaders();
}
private static String[] safeInboundHeaders() {
return new String[] { "!x-*", "*" };
}
}

View File

@@ -102,6 +102,12 @@ public class StubRabbitConnectionFactory implements ConnectionFactory {
public boolean isOpen() {
return false;
}
@Override
public int getLocalPort() {
return 0;
}
}
private static class StubChannel implements Channel {

View File

@@ -23,22 +23,26 @@
<amqp:inbound-channel-adapter id="autoStartFalse" queue-names="inboundchanneladapter.test.2"
auto-startup="false" phase="123" acknowledge-mode="${ackMode}" missing-queues-fatal="false" />
<amqp:inbound-channel-adapter id="withHeaderMapperStandardAndCustomHeaders" channel="requestChannel" queue-names="inboundchanneladapter.test.2"
<amqp:inbound-channel-adapter id="withHeaderMapperStandardAndCustomHeaders"
channel="requestChannel" queue-names="inboundchanneladapter.test.2"
auto-startup="false" phase="123"
mapped-request-headers="foo*, STANDARD_REQUEST_HEADERS"/>
<amqp:inbound-channel-adapter id="withHeaderMapperOnlyCustomHeaders" channel="requestChannel" queue-names="inboundchanneladapter.test.2"
<amqp:inbound-channel-adapter id="withHeaderMapperOnlyCustomHeaders"
channel="requestChannel" queue-names="inboundchanneladapter.test.2"
auto-startup="false" phase="123"
mapped-request-headers="foo*"/>
<amqp:inbound-channel-adapter id="withHeaderMapperNothingToMap" channel="requestChannel" queue-names="inboundchanneladapter.test.2"
<amqp:inbound-channel-adapter id="withHeaderMapperNothingToMap"
channel="requestChannel" queue-names="inboundchanneladapter.test.2"
auto-startup="false" phase="123"
mapped-request-headers=""/>
<amqp:inbound-channel-adapter id="withHeaderMapperDefaultMapping" channel="requestChannel" queue-names="inboundchanneladapter.test.2"
<amqp:inbound-channel-adapter id="withHeaderMapperDefaultMapping"
channel="requestChannel" queue-names="inboundchanneladapter.test.2"
auto-startup="false" phase="123"/>
<int:channel id="requestChannel">
<int:queue/>
</int:channel>
@@ -46,5 +50,5 @@
<bean id="rabbitConnectionFactory" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.amqp.rabbit.connection.ConnectionFactory"/>
</bean>
</beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 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.
@@ -187,8 +187,8 @@ public class AmqpInboundChannelAdapterParserTests {
listener.onMessage(amqpMessage, null);
QueueChannel requestChannel = context.getBean("requestChannel", QueueChannel.class);
org.springframework.messaging.Message<?> siMessage = requestChannel.receive(0);
assertNull(siMessage.getHeaders().get("bar"));
assertNull(siMessage.getHeaders().get("foo"));
assertNotNull(siMessage.getHeaders().get("bar"));
assertNotNull(siMessage.getHeaders().get("foo"));
assertNotNull(siMessage.getHeaders().get(AmqpHeaders.CONTENT_ENCODING));
assertNotNull(siMessage.getHeaders().get(AmqpHeaders.CLUSTER_ID));
assertNotNull(siMessage.getHeaders().get(AmqpHeaders.APP_ID));
@@ -199,7 +199,7 @@ public class AmqpInboundChannelAdapterParserTests {
public void testInt2971HeaderMapperAndMappedHeadersExclusivity() {
try {
new ClassPathXmlApplicationContext("AmqpInboundChannelAdapterParserTests-headerMapper-fail-context.xml",
this.getClass());
this.getClass()).close();
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: The 'header-mapper' attribute " +

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 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.
@@ -186,6 +186,7 @@ public class AmqpOutboundGatewayParserTests {
amqpProperties.setHeader("foobar", "foobar");
amqpProperties.setHeader("bar", "bar");
assertEquals(MessageDeliveryMode.PERSISTENT, properties.getDeliveryMode());
amqpProperties.setReceivedDeliveryMode(properties.getDeliveryMode());
return new org.springframework.amqp.core.Message("hello".getBytes(), amqpProperties);
}
})
@@ -207,7 +208,7 @@ public class AmqpOutboundGatewayParserTests {
assertEquals("bar", replyMessage.getHeaders().get("bar"));
assertEquals("foo", replyMessage.getHeaders().get("foo")); // copied from request Message
assertNull(replyMessage.getHeaders().get("foobar"));
assertNotNull(replyMessage.getHeaders().get(AmqpHeaders.DELIVERY_MODE));
assertNotNull(replyMessage.getHeaders().get(AmqpHeaders.RECEIVED_DELIVERY_MODE));
assertNotNull(replyMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE));
assertNotNull(replyMessage.getHeaders().get(AmqpHeaders.APP_ID));
context.close();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-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.
@@ -100,7 +100,7 @@ public class InboundEndpointTests {
amqpMessageProperties.setDeliveryTag(123L);
org.springframework.amqp.core.Message amqpMessage =
new SimpleMessageConverter().toMessage(jsonMessage.getPayload(), amqpMessageProperties);
new DefaultAmqpHeaderMapper().fromHeadersToRequest(jsonMessage.getHeaders(), amqpMessageProperties);
DefaultAmqpHeaderMapper.inboundMapper().fromHeadersToRequest(jsonMessage.getHeaders(), amqpMessageProperties);
ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener();
Channel rabbitChannel = mock(Channel.class);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 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.
@@ -77,7 +77,7 @@ public class OutboundEndpointTests {
TestRabbitTemplate amqpTemplate = spy(new TestRabbitTemplate(connectionFactory));
AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(amqpTemplate);
endpoint.setExpectReply(true);
DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
DefaultAmqpHeaderMapper mapper = DefaultAmqpHeaderMapper.inboundMapper();
mapper.setRequestHeaderNames("*");
endpoint.setHeaderMapper(mapper);
final AtomicReference<Message> amqpMessage =

View File

@@ -51,7 +51,7 @@ public class DefaultAmqpHeaderMapperTests {
@Test
public void fromHeaders() {
DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper();
Map<String, Object> headerMap = new HashMap<String, Object>();
headerMap.put(AmqpHeaders.APP_ID, "test.appId");
headerMap.put(AmqpHeaders.CLUSTER_ID, "test.clusterId");
@@ -113,7 +113,7 @@ public class DefaultAmqpHeaderMapperTests {
@Test
public void fromHeadersWithContentTypeAsMediaType() {
DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
Map<String, Object> headerMap = new HashMap<String, Object>();
MediaType contentType = MediaType.parseMediaType("text/html");
@@ -135,7 +135,7 @@ public class DefaultAmqpHeaderMapperTests {
@Test
public void fromHeadersWithContentTypeAsMimeType() {
DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
Map<String, Object> headerMap = new HashMap<String, Object>();
MimeType contentType = MimeType.valueOf("text/html");
@@ -151,7 +151,7 @@ public class DefaultAmqpHeaderMapperTests {
@Test
public void toHeaders() {
DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
MessageProperties amqpProperties = new MessageProperties();
amqpProperties.setAppId("test.appId");
amqpProperties.setClusterId("test.clusterId");
@@ -160,7 +160,7 @@ public class DefaultAmqpHeaderMapperTests {
amqpProperties.setContentType("test.contentType");
byte[] testCorrelationId = new byte[] {1, 2, 3};
amqpProperties.setCorrelationId(testCorrelationId);
amqpProperties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
amqpProperties.setReceivedDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
amqpProperties.setDeliveryTag(1234L);
amqpProperties.setExpiration("test.expiration");
amqpProperties.setMessageCount(42);
@@ -184,7 +184,7 @@ public class DefaultAmqpHeaderMapperTests {
assertEquals(99L, headerMap.get(AmqpHeaders.CONTENT_LENGTH));
assertEquals("test.contentType", headerMap.get(AmqpHeaders.CONTENT_TYPE));
assertEquals(testCorrelationId, headerMap.get(AmqpHeaders.CORRELATION_ID));
assertEquals(MessageDeliveryMode.NON_PERSISTENT, headerMap.get(AmqpHeaders.DELIVERY_MODE));
assertEquals(MessageDeliveryMode.NON_PERSISTENT, headerMap.get(AmqpHeaders.RECEIVED_DELIVERY_MODE));
assertEquals(1234L, headerMap.get(AmqpHeaders.DELIVERY_TAG));
assertEquals("test.expiration", headerMap.get(AmqpHeaders.EXPIRATION));
assertEquals(42, headerMap.get(AmqpHeaders.MESSAGE_COUNT));
@@ -203,7 +203,7 @@ public class DefaultAmqpHeaderMapperTests {
@Test // INT-3586 requires Spring AMQP 1.4.2
public void testToHeadersConsumerMetadata() {
try {
DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
MessageProperties amqpProperties = new MessageProperties();
amqpProperties.setConsumerTag("consumerTag");
amqpProperties.setConsumerQueue("consumerQueue");
@@ -218,7 +218,7 @@ public class DefaultAmqpHeaderMapperTests {
@Test
public void messageIdNotMappedToAmqpProperties() {
DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
Map<String, Object> headerMap = new HashMap<String, Object>();
headerMap.put(MessageHeaders.ID, "msg-id");
MessageHeaders integrationHeaders = new MessageHeaders(headerMap);
@@ -229,7 +229,7 @@ public class DefaultAmqpHeaderMapperTests {
@Test
public void messageTimestampNotMappedToAmqpProperties() {
DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
Map<String, Object> headerMap = new HashMap<String, Object>();
headerMap.put(MessageHeaders.TIMESTAMP, 1234L);
MessageHeaders integrationHeaders = new MessageHeaders(headerMap);
@@ -240,7 +240,7 @@ public class DefaultAmqpHeaderMapperTests {
@Test // INT-2090
public void jsonTypeIdNotOverwritten() {
DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
MessageConverter converter = new Jackson2JsonMessageConverter();
MessageProperties amqpProperties = new MessageProperties();
converter.toMessage("123", amqpProperties);
@@ -253,4 +253,35 @@ public class DefaultAmqpHeaderMapperTests {
assertEquals(String.class, result.getClass());
}
@Test
public void inboundOutbound() {
DefaultAmqpHeaderMapper mapper = DefaultAmqpHeaderMapper.inboundMapper();
MessageProperties amqpProperties = new MessageProperties();
amqpProperties.setReceivedDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
amqpProperties.getHeaders().put("foo", "bar");
amqpProperties.getHeaders().put("x-foo", "bar");
Map<String, Object> headers = mapper.toHeadersFromRequest(amqpProperties);
assertNull(headers.get(AmqpHeaders.DELIVERY_MODE));
assertEquals(MessageDeliveryMode.NON_PERSISTENT, headers.get(AmqpHeaders.RECEIVED_DELIVERY_MODE));
assertEquals("bar", headers.get("foo"));
assertNull(headers.get("x-foo"));
headers.put(AmqpHeaders.DELIVERY_MODE, MessageDeliveryMode.NON_PERSISTENT);
mapper.fromHeadersToReply(new MessageHeaders(headers), amqpProperties);
assertEquals(MessageDeliveryMode.NON_PERSISTENT, amqpProperties.getDeliveryMode());
assertEquals("bar", amqpProperties.getHeaders().get("foo"));
mapper = DefaultAmqpHeaderMapper.outboundMapper();
mapper.fromHeadersToRequest(new MessageHeaders(headers), amqpProperties);
assertEquals(MessageDeliveryMode.NON_PERSISTENT, amqpProperties.getDeliveryMode());
assertEquals("bar", amqpProperties.getHeaders().get("foo"));
amqpProperties.setReceivedDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
headers = mapper.toHeadersFromReply(amqpProperties);
assertEquals(MessageDeliveryMode.NON_PERSISTENT, headers.get(AmqpHeaders.RECEIVED_DELIVERY_MODE));
assertNull(headers.get(AmqpHeaders.DELIVERY_MODE));
assertEquals("bar", headers.get("foo"));
}
}