Merge pull request #776 from artembilan/INT-2971

This commit is contained in:
Gary Russell
2013-04-09 10:02:31 -04:00
11 changed files with 192 additions and 27 deletions

View File

@@ -193,6 +193,9 @@
which itself is a Map.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
A special token 'STANDARD_REPLY_HEADERS' represents all the standard AMQP headers (replyTo, correlationId etc);
it is included by default. If you wish to add your own headers, you must also include this token if you wish the
standard headers to also be mapped.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -243,6 +246,9 @@
which itself is a Map.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
A special token 'STANDARD_REPLY_HEADERS' represents all the standard AMQP headers (replyTo, correlationId etc);
it is included by default. If you wish to add your own headers, you must also include this token if you wish the
standard headers to also be mapped.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -418,9 +424,15 @@ The order for this consumer when multiple consumers are registered thereby enabl
</xsd:attribute>
<xsd:attribute name="header-mapper" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a bean implementing 'AmqpHeaderMapper' that maps Spring Integration MessageHeaders to/from
AMQP Message properties.
This is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'.
Default: DefaultAmqpHeaderMapper.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.mapping.HeaderMapper" />
<tool:expected-type type="org.springframework.integration.amqp.support.AmqpHeaderMapper" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
@@ -431,6 +443,9 @@ The order for this consumer when multiple consumers are registered thereby enabl
Comma-separated list of names of AMQP Headers to be mapped from the AMQP request into the MessageHeaders.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
A special token 'STANDARD_REQUEST_HEADERS' represents all the standard AMQP headers (replyTo, correlationId etc);
it is included by default. If you wish to add your own headers, you must also include this token if you wish the
standard headers to also be mapped.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -478,9 +493,15 @@ property set to TRUE.
</xsd:attribute>
<xsd:attribute name="header-mapper" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a bean implementing 'AmqpHeaderMapper' that maps Spring Integration MessageHeaders to/from
AMQP Message properties.
This is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'.
Default: DefaultAmqpHeaderMapper.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.mapping.HeaderMapper" />
<tool:expected-type type="org.springframework.integration.amqp.support.AmqpHeaderMapper" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
@@ -491,6 +512,9 @@ property set to TRUE.
Comma-separated list of names of AMQP Headers to be mapped from the AMQP request into the MessageHeaders.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
A special token 'STANDARD_REQUEST_HEADERS' represents all the standard AMQP headers (replyTo, correlationId etc);
it is included by default. If you wish to add your own headers, you must also include this token if you wish the
standard headers to also be mapped.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:amqp="http://www.springframework.org/schema/integration/amqp"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="amqpHeaderMapper" class="org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper"/>
<amqp:inbound-channel-adapter id="rabbitInbound" queue-names="test.queue"
mapped-request-headers="*"
header-mapper="amqpHeaderMapper"/>
</beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2013 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.
@@ -22,8 +22,11 @@ import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.amqp.AmqpHeaders;
import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter;
import org.springframework.integration.channel.DirectChannel;
@@ -35,9 +38,11 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* @author Mark Fisher
* @author Artem Bilan
* @since 2.1
*/
@ContextConfiguration
@@ -63,12 +68,12 @@ public class AmqpInboundChannelAdapterParserTests {
assertEquals(Boolean.FALSE, TestUtils.getPropertyValue(adapter, "autoStartup"));
assertEquals(123, TestUtils.getPropertyValue(adapter, "phase"));
}
@Test
public void withHeaderMapperStandardAndCustomHeaders() {
AmqpInboundChannelAdapter adapter = context.getBean("withHeaderMapperStandardAndCustomHeaders", AmqpInboundChannelAdapter.class);
AbstractMessageListenerContainer mlc =
AbstractMessageListenerContainer mlc =
TestUtils.getPropertyValue(adapter, "messageListenerContainer", AbstractMessageListenerContainer.class);
MessageListener listener = TestUtils.getPropertyValue(mlc, "messageListener", MessageListener.class);
MessageProperties amqpProperties = new MessageProperties();
@@ -90,12 +95,12 @@ public class AmqpInboundChannelAdapterParserTests {
assertNotNull(siMessage.getHeaders().get(AmqpHeaders.APP_ID));
assertNotNull(siMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE));
}
@Test
public void withHeaderMapperOnlyCustomHeaders() {
AmqpInboundChannelAdapter adapter = context.getBean("withHeaderMapperOnlyCustomHeaders", AmqpInboundChannelAdapter.class);
AbstractMessageListenerContainer mlc =
AbstractMessageListenerContainer mlc =
TestUtils.getPropertyValue(adapter, "messageListenerContainer", AbstractMessageListenerContainer.class);
MessageListener listener = TestUtils.getPropertyValue(mlc, "messageListener", MessageListener.class);
MessageProperties amqpProperties = new MessageProperties();
@@ -117,12 +122,12 @@ public class AmqpInboundChannelAdapterParserTests {
assertNull(siMessage.getHeaders().get(AmqpHeaders.APP_ID));
assertNull(siMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE));
}
@Test
public void withHeaderMapperNothingToMap() {
AmqpInboundChannelAdapter adapter = context.getBean("withHeaderMapperNothingToMap", AmqpInboundChannelAdapter.class);
AbstractMessageListenerContainer mlc =
AbstractMessageListenerContainer mlc =
TestUtils.getPropertyValue(adapter, "messageListenerContainer", AbstractMessageListenerContainer.class);
MessageListener listener = TestUtils.getPropertyValue(mlc, "messageListener", MessageListener.class);
MessageProperties amqpProperties = new MessageProperties();
@@ -135,7 +140,7 @@ public class AmqpInboundChannelAdapterParserTests {
amqpProperties.setHeader("bar", "bar");
Message amqpMessage = new Message("hello".getBytes(), amqpProperties);
listener.onMessage(amqpMessage);
QueueChannel requestChannel = context.getBean("requestChannel", QueueChannel.class);
org.springframework.integration.Message<?> siMessage = requestChannel.receive(0);
assertNull(siMessage.getHeaders().get("foo"));
@@ -145,12 +150,12 @@ public class AmqpInboundChannelAdapterParserTests {
assertNull(siMessage.getHeaders().get(AmqpHeaders.APP_ID));
assertNull(siMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE));
}
@Test
public void withHeaderMapperDefaultMapping() {
AmqpInboundChannelAdapter adapter = context.getBean("withHeaderMapperDefaultMapping", AmqpInboundChannelAdapter.class);
AbstractMessageListenerContainer mlc =
AbstractMessageListenerContainer mlc =
TestUtils.getPropertyValue(adapter, "messageListenerContainer", AbstractMessageListenerContainer.class);
MessageListener listener = TestUtils.getPropertyValue(mlc, "messageListener", MessageListener.class);
MessageProperties amqpProperties = new MessageProperties();
@@ -172,4 +177,16 @@ public class AmqpInboundChannelAdapterParserTests {
assertNotNull(siMessage.getHeaders().get(AmqpHeaders.APP_ID));
assertNotNull(siMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE));
}
@Test
public void testInt2971HeaderMapperAndMappedHeadersExclusivity() {
try {
new ClassPathXmlApplicationContext("AmqpInboundChannelAdapterParserTests-headerMapper-fail-context.xml", this.getClass());
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: The 'header-mapper' attribute " +
"is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'"));
}
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:amqp="http://www.springframework.org/schema/integration/amqp"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="amqpHeaderMapper" class="org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper"/>
<amqp:inbound-gateway request-channel="requests" queue-names="test"
mapped-request-headers="foo*, STANDARD_REQUEST_HEADERS"
mapped-reply-headers="bar*"
header-mapper="amqpHeaderMapper"/>
</beans>

View File

@@ -16,6 +16,10 @@
package org.springframework.integration.amqp.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Field;
import org.junit.Test;
@@ -32,7 +36,9 @@ import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessagingException;
import org.springframework.integration.amqp.inbound.AmqpInboundGateway;
@@ -44,13 +50,10 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
/**
* @author Mark Fisher
* @author Gunnar Hillert
*
* @author Artem Bilan
* @since 2.1
*/
@ContextConfiguration
@@ -130,6 +133,17 @@ public class AmqpInboundGatewayParserTests {
Mockito.any(Message.class), Mockito.any(CorrelationData.class));
}
@Test
public void testInt2971HeaderMapperAndMappedHeadersExclusivity() {
try {
new ClassPathXmlApplicationContext("AmqpInboundGatewayParserTests-headerMapper-fail-context.xml", this.getClass());
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: The 'header-mapper' attribute " +
"is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'"));
}
}
private static class TestConverter extends SimpleMessageConverter {}
}

View File

@@ -25,6 +25,14 @@
exchange-name="outboundchanneladapter.test.1"
mapped-request-headers="foo*"/>
<bean id="customHeaderMapper" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.amqp.support.AmqpHeaderMapper"/>
</bean>
<amqp:outbound-channel-adapter id="withCustomHeaderMapper"
exchange-name="test.exchange"
header-mapper="customHeaderMapper"/>
<int:channel id="requestChannel"/>
<int:chain id="chainWithRabbitOutbound" input-channel="amqpOutboundChannelAdapterWithinChain">

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:amqp="http://www.springframework.org/schema/integration/amqp"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="amqpHeaderMapper" class="org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper"/>
<amqp:outbound-channel-adapter id="rabbitOutbound" exchange-name="test.queue"
mapped-request-headers="foo*"
header-mapper="amqpHeaderMapper"/>
</beans>

View File

@@ -45,6 +45,7 @@ import org.springframework.amqp.rabbit.support.PublisherCallbackChannel;
import org.springframework.amqp.rabbit.support.PublisherCallbackChannelImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -52,6 +53,7 @@ import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.amqp.AmqpHeaders;
import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
import org.springframework.integration.amqp.support.AmqpHeaderMapper;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.context.NamedComponent;
@@ -86,6 +88,10 @@ public class AmqpOutboundChannelAdapterParserTests {
@Autowired
private ApplicationContext context;
@Autowired
@Qualifier("withCustomHeaderMapper.handler")
private MessageHandler amqpMessageHandlerWithCustomHeaderMapper;
@Test
public void verifyIdAsChannel() {
Object channel = context.getBean("rabbitOutbound");
@@ -289,6 +295,24 @@ public class AmqpOutboundChannelAdapterParserTests {
Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.any(BasicProperties.class), Mockito.any(byte[].class));
}
@Test
public void testInt2971HeaderMapperAndMappedHeadersExclusivity() {
try {
new ClassPathXmlApplicationContext("AmqpOutboundChannelAdapterParserTests-headerMapper-fail-context.xml", this.getClass());
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: The 'header-mapper' attribute " +
"is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'"));
}
}
@Test
public void testInt2971AmqpOutboundChannelAdapterWithCustomHeaderMapper() {
AmqpHeaderMapper headerMapper = TestUtils.getPropertyValue(this.amqpMessageHandlerWithCustomHeaderMapper, "headerMapper", AmqpHeaderMapper.class);
assertSame(this.context.getBean("customHeaderMapper"), headerMapper);
}
public static class FooAdvice extends AbstractRequestHandlerAdvice {
@Override

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:amqp="http://www.springframework.org/schema/integration/amqp"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="amqpHeaderMapper" class="org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper"/>
<amqp:outbound-gateway request-channel="toRabbit0"
mapped-request-headers="foo*, STANDARD_REQUEST_HEADERS"
mapped-reply-headers="bar*"
header-mapper="amqpHeaderMapper"/>
</beans>

View File

@@ -30,6 +30,7 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
@@ -264,6 +265,18 @@ public class AmqpOutboundGatewayParserTests {
}
@Test
public void testInt2971HeaderMapperAndMappedHeadersExclusivity() {
try {
new ClassPathXmlApplicationContext("AmqpOutboundGatewayParserTests-headerMapper-fail-context.xml", this.getClass());
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: The 'header-mapper' attribute " +
"is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'"));
}
}
public static class FooAdvice extends AbstractRequestHandlerAdvice {
@Override

View File

@@ -27,7 +27,7 @@
(<ulink url="http://www.springsource.org/spring-amqp">http://www.springsource.org/spring-amqp</ulink>)
which "applies core Spring concepts to the development of AMQP-based
messaging solutions". Spring AMQP provides similar semantics as Spring JMS
(<ulink url="http://static.springsource.org/spring/docs/current/spring-framework-reference/htmlsingle/spring-framework-reference.html#jms">http://.../spring-framework-reference.html#jms</ulink>).
(<ulink url="http://static.springsource.org/spring/docs/current/spring-framework-reference/html/jms.html">http://static.springsource.org/spring/docs/current/spring-framework-reference/html/jms.html</ulink>).
</para>
<para>
Whereas the provided AMQP Channel Adapters are intended for unidirectional
@@ -133,12 +133,12 @@
<emphasis>Optional (Defaults to true)</emphasis>.</para>
</callout>
<callout arearefs="amqp-inbound-channel-adapter-xml-11-co" id="amqp-inbound-channel-adapter-xml-11">
<para><classname>HeaderMapper</classname> to use when receiving AMQP Messages.
<para><interfacename>AmqpHeaderMapper</interfacename> to use when receiving AMQP Messages.
<emphasis>Optional</emphasis>.
By default only standard AMQP properties (e.g. contentType) will be copied to and from
Spring Integration MessageHeaders. Any user-defined headers within the AMQP
MessageProperties will NOT be copied to or from an AMQP Message unless
explicitly identified via 'requestHeaderNames' and/or 'replyHeaderNames' properties of this <classname>HeaderMapper</classname>.
explicitly identified via 'requestHeaderNames' and/or 'replyHeaderNames' properties of this <classname>DefaultAmqpHeaderMapper</classname>.
If you need to copy all user-defined headers simply use wild-card character '*'.
</para>
</callout>
@@ -155,7 +155,7 @@ this list can also be simple patterns to be matched against the header names (e.
this list can also be simple patterns to be matched against the header names (e.g. "*" or "foo*, bar" or "*foo").</para>
</callout>
<callout arearefs="amqp-inbound-channel-adapter-xml-14-co" id="amqp-inbound-channel-adapter-xml-14">
<para>Reference to the <interface>SimpleMessageListenerContainer</interface>
<para>Reference to the <interfacename>SimpleMessageListenerContainer</interfacename>
to use for receiving AMQP Messages. If this attribute is provided,
then no other attribute related to the listener container
configuration should be provided. In other words, by
@@ -538,9 +538,9 @@ this list can also be simple patterns to be matched against the header names (e.
The Spring Integration AMPQ Adapters will map standard AMQP properties
automatically. These properties will be copied by default to and from
Spring Integration
<classname><ulink url="http://static.springsource.org/spring-integration/docs/latest-ga/api/org/springframework/integration/MessageHeaders.html">MessageHeaders</ulink></classname>
<classname><ulink url="http://static.springsource.org/spring-integration/api/org/springframework/integration/MessageHeaders.html">MessageHeaders</ulink></classname>
using the
<classname><ulink url="http://static.springsource.org/spring-integration/docs/latest-ga/api/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.html">DefaultAmqpHeaderMapper</ulink></classname>.
<classname><ulink url="http://static.springsource.org/spring-integration/api/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.html">DefaultAmqpHeaderMapper</ulink></classname>.
</para>
<para>
Of course, you can pass in your own implementation of AMQP specific header
@@ -552,7 +552,7 @@ this list can also be simple patterns to be matched against the header names (e.
will NOT be copied to or from an AMQP Message, unless explicitly specified
by the <emphasis>requestHeaderNames</emphasis> and/or
<emphasis>replyHeaderNames</emphasis> properties of the
<classname>HeaderMapper</classname>.
<classname>DefaultAmqpHeaderMapper</classname>.
</para>
<tip>
When mapping user-defined headers, the values can also contain simple