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 9af1ba19d2..f3ed17078b 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 c44aafb9f8..d00048dc22 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 94a825fcb8..4947f6ab0c 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 @@ -33,6 +33,7 @@ import static org.mockito.Mockito.verify; import java.lang.reflect.Method; 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; @@ -42,6 +43,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; @@ -63,6 +65,9 @@ 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.configuration.EnableIntegrationTests.TestMessagingGateway; +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; @@ -71,6 +76,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; @@ -93,6 +99,8 @@ import org.springframework.util.concurrent.ListenableFutureCallback; @DirtiesContext public class GatewayInterfaceTests { + private static final String IGNORE_HEADER = "ignoreHeader"; + @Autowired private Int2634Gateway int2634Gateway; @@ -116,6 +124,11 @@ public class GatewayInterfaceTests { @Autowired private AutoCreateChannelService autoCreateChannelService; + @Autowired + private MessageChannel errorChannel; + + @Autowired + private IgnoredHeaderGateway ignoredHeaderGateway; @Test public void testWithServiceSuperclassAnnotatedMethod() throws Exception { @@ -408,6 +421,27 @@ public class GatewayInterfaceTests { assertEquals("foo", this.autoCreateChannelService.service("foo")); } + @Test + @SuppressWarnings("unchecked") + public void testIgnoredHeader() { + MessageHandler messageHandler = mock(MessageHandler.class); + + ((SubscribableChannel) this.errorChannel).subscribe(messageHandler); + this.ignoredHeaderGateway.service("foo", "theHeaderValue"); + + @SuppressWarnings("rawtypes") + 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 { @Gateway(requestChannel = "requestChannelFoo") @@ -463,6 +497,13 @@ public class GatewayInterfaceTests { @EnableIntegration public static class TestConfig { + @Bean(name = IntegrationContextUtils.INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME) + public static Properties integrationProperties() { + Properties properties = new Properties(); + properties.setProperty(IntegrationProperties.READ_ONLY_HEADERS, IGNORE_HEADER); + return properties; + } + @Bean @BridgeTo public MessageChannel gatewayChannel() { @@ -550,4 +591,12 @@ public class GatewayInterfaceTests { } + @MessagingGateway(defaultRequestChannel = "errorChannel") + @TestMessagingGateway + public interface IgnoredHeaderGateway { + + void service(String payload, @Header(IGNORE_HEADER) String myHeader); + + } + } diff --git a/src/reference/asciidoc/message.adoc b/src/reference/asciidoc/message.adoc index 122aab232d..ce819929d2 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