INT-4284: INFO about overriding readOnly headers
JIRA: https://jira.spring.io/browse/INT-4284 Add `INFO` into the `MessageBuilder#copyHeadersIfAbsent()` when end-user tries to populate headers which are `readOnly` We can't throw exception on the matter since can modify `readOnlyHeaders` and that would force end-user to add `header-filter` logic to the application. * Document `readOnly` headers in the `message.adoc` **Cherry-pick 4.3.x**
This commit is contained in:
committed by
Gary Russell
parent
2c7bf9271c
commit
33e9ce912f
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<T> extends AbstractIntegrationMessageBuilder<T> {
|
||||
|
||||
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<T> extends AbstractIntegrationMessageBuilder<T
|
||||
*/
|
||||
@Override
|
||||
public MessageBuilder<T> copyHeadersIfAbsent(Map<String, ?> headersToCopy) {
|
||||
this.headerAccessor.copyHeadersIfAbsent(headersToCopy);
|
||||
if (headersToCopy != null) {
|
||||
for (Map.Entry<String, ?> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Message<?>> 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}",
|
||||
|
||||
@@ -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 <<global-properties>>) 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user