diff --git a/spring-integration-core/src/main/java/org/springframework/integration/IntegrationMessageHeaderAccessor.java b/spring-integration-core/src/main/java/org/springframework/integration/IntegrationMessageHeaderAccessor.java index e11459eac4..ae23d06826 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/IntegrationMessageHeaderAccessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/IntegrationMessageHeaderAccessor.java @@ -155,7 +155,7 @@ public class IntegrationMessageHeaderAccessor extends MessageHeaderAccessor { } @Override - protected boolean isReadOnly(String headerName) { + public boolean isReadOnly(String headerName) { return super.isReadOnly(headerName) || this.readOnlyHeaders.contains(headerName); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/MessageBuilder.java b/spring-integration-core/src/main/java/org/springframework/integration/support/MessageBuilder.java index cc72bb9d71..360186682d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/MessageBuilder.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/MessageBuilder.java @@ -20,6 +20,9 @@ import java.util.Date; import java.util.List; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.integration.IntegrationMessageHeaderAccessor; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @@ -43,6 +46,8 @@ import org.springframework.util.ObjectUtils; */ public final class MessageBuilder extends AbstractIntegrationMessageBuilder { + private static final Log logger = LogFactory.getLog(MessageBuilder.class); + private final T payload; private final IntegrationMessageHeaderAccessor headerAccessor; @@ -176,7 +181,16 @@ public final class MessageBuilder extends AbstractIntegrationMessageBuilder copyHeadersIfAbsent(Map headersToCopy) { - this.headerAccessor.copyHeadersIfAbsent(headersToCopy); + if (headersToCopy != null) { + for (Map.Entry entry : headersToCopy.entrySet()) { + if (!this.headerAccessor.isReadOnly(entry.getKey())) { + this.headerAccessor.setHeaderIfAbsent(entry.getKey(), entry.getValue()); + } + else if (logger.isInfoEnabled()) { + logger.info("The header [" + entry + "] is ignored for population because it is is readOnly."); + } + } + } return this; } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java index bfb39fef6e..b2ad9c03da 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java @@ -39,6 +39,7 @@ import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -48,6 +49,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; @@ -71,6 +73,8 @@ import org.springframework.integration.annotation.MessagingGateway; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.context.IntegrationContextUtils; +import org.springframework.integration.context.IntegrationProperties; import org.springframework.integration.handler.BridgeHandler; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; @@ -79,6 +83,7 @@ import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.support.ChannelInterceptorAdapter; @@ -102,6 +107,8 @@ import org.springframework.util.concurrent.ListenableFutureCallback; @DirtiesContext public class GatewayInterfaceTests { + private static final String IGNORE_HEADER = "ignoreHeader"; + @Autowired private Int2634Gateway int2634Gateway; @@ -141,6 +148,9 @@ public class GatewayInterfaceTests { @Autowired private MessageChannel errorChannel; + @Autowired + private IgnoredHeaderGateway ignoredHeaderGateway; + @Test public void testWithServiceSuperclassAnnotatedMethod() throws Exception { ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); @@ -453,6 +463,25 @@ public class GatewayInterfaceTests { assertEquals("baz", ((LiteralExpression) barHeaderExpression).getValue()); } + @Test + @SuppressWarnings("unchecked") + public void testIgnoredHeader() { + MessageHandler messageHandler = mock(MessageHandler.class); + + ((SubscribableChannel) this.errorChannel).subscribe(messageHandler); + this.ignoredHeaderGateway.service("foo", "theHeaderValue"); + + ArgumentCaptor> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); + + verify(messageHandler).handleMessage(messageArgumentCaptor.capture()); + + Message message = messageArgumentCaptor.getValue(); + + assertFalse(message.getHeaders().containsKey(IGNORE_HEADER)); + + ((SubscribableChannel) this.errorChannel).unsubscribe(messageHandler); + } + public interface Foo { @@ -513,6 +542,13 @@ public class GatewayInterfaceTests { @EnableIntegration public static class TestConfig { + @Bean(name = IntegrationContextUtils.INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME) + public Properties integrationProperties() { + Properties properties = new Properties(); + properties.setProperty(IntegrationProperties.READ_ONLY_HEADERS, IGNORE_HEADER); + return properties; + } + @Bean @BridgeTo public MessageChannel gatewayChannel() { @@ -616,6 +652,14 @@ public class GatewayInterfaceTests { } + @MessagingGateway(defaultRequestChannel = "errorChannel") + @TestMessagingGateway + public interface IgnoredHeaderGateway { + + void service(String payload, @Header(IGNORE_HEADER) String myHeader); + + } + @MessagingGateway( defaultRequestChannel = "${gateway.channel:gatewayChannel}", defaultReplyChannel = "${gateway.channel:gatewayChannel}", diff --git a/src/reference/asciidoc/message.adoc b/src/reference/asciidoc/message.adoc index 8bf8898f64..a4b83fd8c4 100644 --- a/src/reference/asciidoc/message.adoc +++ b/src/reference/asciidoc/message.adoc @@ -370,6 +370,13 @@ If the strategy is the same, but parameterized, the strategy in the first contex In addition to the default strategy, two additional `IdGenerators` are provided; `org.springframework.util.JdkIdGenerator` uses the previous `UUID.randomUUID()` mechanism; `o.s.i.support.IdGenerators.SimpleIncrementingIdGenerator` can be used in cases where a UUID is not really needed and a simple incrementing value is sufficient. +[[read-only-headers]] +===== Read-only Headers + +The `MessageHeaders.ID` and `MessageHeaders.TIMESTAMP` are read-only headers and the cannot be overridden. +With the `spring.integration.readOnly.headers` global property (see <>) you can specify any header names which become read-only. +When you try to build a new message using `MessageBuilder`, this kind of headers are ignored and particular `INFO` message is emitted to logs. + [[message-implementations]] ==== Message Implementations