From dbcebebf380dd6b52e0cc29ea5b77ae2d493269f Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 12 Sep 2016 20:48:34 -0400 Subject: [PATCH] INT-4096: Allow Configuration of ReadOnly Headers JIRA: https://jira.spring.io/browse/INT-4096 * Introduce `spring.integration.readOnly.headers` Integration property * Introduce `IntegrationMessageHeaderAccessor.setReadOnlyHeaders()` and use it from the overridden `IntegrationMessageHeaderAccessor.isReadOnly()` * Add `DefaultMessageBuilderFactory.setReadOnlyHeaders()` and delegate the value to the new `MessageBuilder.readOnlyHeaders()` * Modify `spring.integration.properties` for the `contentType` as a `readOnly` header for testing * Ensure that provided logic works via an appropriate modification to the `ObjectToJsonTransformerParserTests` Increase receive timeouts in the `OutboundGatewayFunctionTests` Fix `FileInboundTransactionTests` for slow `WatchService` issue on Linux/OS X Also redo the `tmp` dir logic to the `TemporaryFolder` `@ClassRule` Fix unused `import` in the `FileInboundTransactionTests` Polishing Conflicts: spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationProperties.java spring-integration-core/src/main/resources/META-INF/spring.integration.default.properties Some polishing and documentation Localize `readOnly.headers` customization only in the `ObjectToJsonTransformerParserTests-context.xml` Otherwise ti might affect many other tests and the search for the reason of failure would be enough complicated Doc Polish --- .../IntegrationMessageHeaderAccessor.java | 27 ++++++++++++ .../config/IntegrationRegistrar.java | 4 +- .../context/IntegrationProperties.java | 6 +++ .../support/DefaultMessageBuilderFactory.java | 21 +++++++++- .../integration/support/MessageBuilder.java | 15 +++++++ .../spring.integration.default.properties | 2 + ...ctToJsonTransformerParserTests-context.xml | 18 ++++---- .../ObjectToJsonTransformerParserTests.java | 41 +++++++++++++------ .../FileInboundTransactionTests-context.xml | 5 ++- .../file/FileInboundTransactionTests.java | 30 +++++++------- .../jms/OutboundGatewayFunctionTests.java | 14 +++---- src/reference/asciidoc/configuration.adoc | 6 +++ src/reference/asciidoc/message.adoc | 5 +++ src/reference/asciidoc/transformer.adoc | 1 + src/reference/asciidoc/whats-new.adoc | 5 +++ 15 files changed, 154 insertions(+), 46 deletions(-) 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 c7d803ef21..1f2a91abeb 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 @@ -17,12 +17,17 @@ package org.springframework.integration; import java.io.Closeable; +import java.util.Arrays; import java.util.Date; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; /** * @@ -53,10 +58,27 @@ public class IntegrationMessageHeaderAccessor extends MessageHeaderAccessor { public static final String CLOSEABLE_RESOURCE = "closeableResource"; + private Set readOnlyHeaders = new HashSet(); + public IntegrationMessageHeaderAccessor(Message message) { super(message); } + /** + * Specify a list of headers which should be considered as read only + * and prohibited from being populated in the message. + * @param readOnlyHeaders the list of headers for {@code readOnly} mode. + * Defaults to {@link MessageHeaders#ID} and {@link MessageHeaders#TIMESTAMP}. + * @since 4.3.2 + * @see #isReadOnly(String) + */ + public void setReadOnlyHeaders(String... readOnlyHeaders) { + Assert.noNullElements(readOnlyHeaders, "'readOnlyHeaders' must not be contain null items."); + if (!ObjectUtils.isEmpty(readOnlyHeaders)) { + this.readOnlyHeaders = new HashSet(Arrays.asList(readOnlyHeaders)); + } + } + public Long getExpirationDate() { return this.getHeader(EXPIRATION_DATE, Long.class); } @@ -130,4 +152,9 @@ public class IntegrationMessageHeaderAccessor extends MessageHeaderAccessor { } } + @Override + protected boolean isReadOnly(String headerName) { + return super.isReadOnly(headerName) || this.readOnlyHeaders.contains(headerName); + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java index 943152a205..679b579a3f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java @@ -417,7 +417,9 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean } if (!alreadyRegistered) { BeanDefinitionBuilder mbfBuilder = BeanDefinitionBuilder - .genericBeanDefinition(DefaultMessageBuilderFactory.class); + .genericBeanDefinition(DefaultMessageBuilderFactory.class) + .addPropertyValue("readOnlyHeaders", + IntegrationProperties.getExpressionFor(IntegrationProperties.READ_ONLY_HEADERS)); registry.registerBeanDefinition( IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME, mbfBuilder.getBeanDefinition()); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationProperties.java b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationProperties.java index 11c209e424..db06916cff 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationProperties.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationProperties.java @@ -66,6 +66,12 @@ public final class IntegrationProperties { */ public static final String THROW_EXCEPTION_ON_LATE_REPLY = INTEGRATION_PROPERTIES_PREFIX + "messagingTemplate.throwExceptionOnLateReply"; + /** + * Specifies the value of {@link org.springframework.integration.support.DefaultMessageBuilderFactory#readOnlyHeaders}. + */ + public static final String READ_ONLY_HEADERS = INTEGRATION_PROPERTIES_PREFIX + "readOnly.headers"; + + private static Properties defaults; static { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/DefaultMessageBuilderFactory.java b/spring-integration-core/src/main/java/org/springframework/integration/support/DefaultMessageBuilderFactory.java index 7da59af95a..854841cfab 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/DefaultMessageBuilderFactory.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/DefaultMessageBuilderFactory.java @@ -17,22 +17,39 @@ package org.springframework.integration.support; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; /** * @author Gary Russell + * @author Artem Bilan * @since 4.0 * */ public class DefaultMessageBuilderFactory implements MessageBuilderFactory { + private String[] readOnlyHeaders; + + /** + * Specify a list of headers which should be considered as a read only + * and prohibited from the population to the message. + * @param readOnlyHeaders the list of headers for {@code readOnly} mode. + * Defaults to {@link MessageHeaders#ID} and {@link MessageHeaders#TIMESTAMP}. + * @since 4.3.2 + */ + public void setReadOnlyHeaders(String... readOnlyHeaders) { + this.readOnlyHeaders = readOnlyHeaders; + } + @Override public MessageBuilder fromMessage(Message message) { - return MessageBuilder.fromMessage(message); + return MessageBuilder.fromMessage(message) + .readOnlyHeaders(this.readOnlyHeaders); } @Override public MessageBuilder withPayload(T payload) { - return MessageBuilder.withPayload(payload); + return MessageBuilder.withPayload(payload) + .readOnlyHeaders(this.readOnlyHeaders); } } 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 933d289e92..ed723211ce 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 @@ -274,6 +274,21 @@ public final class MessageBuilder extends AbstractIntegrationMessageBuilder readOnlyHeaders(String... readOnlyHeaders) { + this.headerAccessor.setReadOnlyHeaders(readOnlyHeaders); + return this; + } + + + @Override @SuppressWarnings("unchecked") public Message build() { diff --git a/spring-integration-core/src/main/resources/META-INF/spring.integration.default.properties b/spring-integration-core/src/main/resources/META-INF/spring.integration.default.properties index 5c6bfd8b16..650beed270 100644 --- a/spring-integration-core/src/main/resources/META-INF/spring.integration.default.properties +++ b/spring-integration-core/src/main/resources/META-INF/spring.integration.default.properties @@ -3,3 +3,5 @@ spring.integration.channels.maxUnicastSubscribers=0x7fffffff spring.integration.channels.maxBroadcastSubscribers=0x7fffffff spring.integration.taskScheduler.poolSize=10 spring.integration.messagingTemplate.throwExceptionOnLateReply=false +# Defaults to MessageHeaders.ID and MessageHeaders.TIMESTAMP +spring.integration.readOnly.headers= \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests-context.xml index 8a67f2bbf0..9a3a714e51 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests-context.xml @@ -1,17 +1,21 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:beans="http://www.springframework.org/schema/beans" + xmlns:util="http://www.springframework.org/schema/util" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> + + + contentType,foo + - + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java index 881c39b948..37f3c37d52 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java @@ -37,6 +37,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.mapping.support.JsonHeaders; +import org.springframework.integration.support.DefaultMessageBuilderFactory; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.json.Jackson2JsonObjectMapper; import org.springframework.integration.support.json.JsonObjectMapperAdapter; @@ -61,45 +62,57 @@ import com.fasterxml.jackson.databind.JsonNode; public class ObjectToJsonTransformerParserTests { @Autowired - private volatile ApplicationContext context; + private ApplicationContext context; @Autowired - private volatile MessageChannel defaultObjectMapperInput; + private MessageChannel defaultObjectMapperInput; @Autowired - private volatile MessageChannel customJsonObjectMapperInput; + private MessageChannel customJsonObjectMapperInput; @Autowired - private volatile MessageChannel jsonNodeInput; + private MessageChannel jsonNodeInput; @Autowired - private volatile MessageChannel boonJsonNodeInput; + private MessageChannel boonJsonNodeInput; + + @Autowired + private DefaultMessageBuilderFactory defaultMessageBuilderFactory; @Test public void testContentType() { ObjectToJsonTransformer transformer = - TestUtils.getPropertyValue(context.getBean("defaultTransformer"), "handler.transformer", ObjectToJsonTransformer.class); + TestUtils.getPropertyValue(context.getBean("defaultTransformer"), "handler.transformer", + ObjectToJsonTransformer.class); assertEquals("application/json", TestUtils.getPropertyValue(transformer, "contentType")); - assertEquals(Jackson2JsonObjectMapper.class, TestUtils.getPropertyValue(transformer, "jsonObjectMapper").getClass()); + assertEquals(Jackson2JsonObjectMapper.class, + TestUtils.getPropertyValue(transformer, "jsonObjectMapper").getClass()); Message transformed = transformer.transform(MessageBuilder.withPayload("foo").build()); - assertTrue(transformed.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE)); - assertEquals("application/json", transformed.getHeaders().get(MessageHeaders.CONTENT_TYPE)); + + // spring.integration.readOnly.headers=contentType, so no 'contentType' + assertFalse(transformed.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE)); + + // Reset readOnlyHeaders to defaults. Therefore the 'contentType' should be presented in subsequent tests + this.defaultMessageBuilderFactory.setReadOnlyHeaders(); transformer = - TestUtils.getPropertyValue(context.getBean("emptyContentTypeTransformer"), "handler.transformer", ObjectToJsonTransformer.class); + TestUtils.getPropertyValue(context.getBean("emptyContentTypeTransformer"), "handler.transformer", + ObjectToJsonTransformer.class); assertEquals("", TestUtils.getPropertyValue(transformer, "contentType")); transformed = transformer.transform(MessageBuilder.withPayload("foo").build()); assertFalse(transformed.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE)); - transformed = transformer.transform(MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE, "foo").build()); + transformed = transformer.transform(MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE, + "foo").build()); assertNotNull(transformed.getHeaders().get(MessageHeaders.CONTENT_TYPE)); assertEquals("foo", transformed.getHeaders().get(MessageHeaders.CONTENT_TYPE)); transformer = - TestUtils.getPropertyValue(context.getBean("overridenContentTypeTransformer"), "handler.transformer", ObjectToJsonTransformer.class); + TestUtils.getPropertyValue(context.getBean("overriddenContentTypeTransformer"), "handler.transformer", + ObjectToJsonTransformer.class); assertEquals("text/xml", TestUtils.getPropertyValue(transformer, "contentType")); } @@ -165,7 +178,8 @@ public class ObjectToJsonTransformerParserTests { assertThat(payload, Matchers.instanceOf(JsonNode.class)); StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); evaluationContext.addPropertyAccessor(new JsonPropertyAccessor()); - Expression expression = new SpelExpressionParser().parseExpression("firstName.toString() == 'John' and age.toString() == '42'"); + Expression expression = new SpelExpressionParser() + .parseExpression("firstName.toString() == 'John' and age.toString() == '42'"); assertTrue(expression.getValue(evaluationContext, payload, Boolean.class)); } @@ -195,6 +209,7 @@ public class ObjectToJsonTransformerParserTests { public String toJson(Object value) throws Exception { return "{" + value.toString() + "}"; } + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileInboundTransactionTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/FileInboundTransactionTests-context.xml index 83959e9dfd..ad143397a1 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileInboundTransactionTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileInboundTransactionTests-context.xml @@ -12,7 +12,8 @@ @@ -32,7 +33,7 @@ + directory="#{T (org.springframework.integration.file.FileInboundTransactionTests).tmpDir.root}/si-test2"> diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileInboundTransactionTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/FileInboundTransactionTests.java index a61900ad65..c5c2311f16 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileInboundTransactionTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileInboundTransactionTests.java @@ -27,11 +27,12 @@ import java.io.File; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.integration.endpoint.SourcePollingChannelAdapter; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; @@ -39,6 +40,7 @@ import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.SubscribableChannel; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.TransactionDefinition; @@ -54,8 +56,12 @@ import org.springframework.transaction.support.DefaultTransactionStatus; */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext public class FileInboundTransactionTests { + @ClassRule + public static TemporaryFolder tmpDir = new TemporaryFolder(); + @Autowired private SourcePollingChannelAdapter pseudoTx; @@ -77,9 +83,6 @@ public class FileInboundTransactionTests { @Autowired private DummyTxManager transactionManager; - @Value("${java.io.tmpdir}") - private String tmpDir; - @Test public void testNoTx() throws Exception { final CountDownLatch latch = new CountDownLatch(1); @@ -95,17 +98,16 @@ public class FileInboundTransactionTests { } }); pseudoTx.start(); - new File(tmpDir + "/si-test1").mkdir(); - File file = new File(tmpDir + "/si-test1/foo"); + File file = new File(tmpDir.getRoot(), "si-test1/foo"); file.createNewFile(); - Message result = successChannel.receive(40000); + Message result = successChannel.receive(60000); assertNotNull(result); assertEquals(Boolean.TRUE, result.getPayload()); assertFalse(file.delete()); crash.set(true); - file = new File(tmpDir + "/si-test1/bar"); + file = new File(tmpDir.getRoot(), "si-test1/bar"); file.createNewFile(); - result = failureChannel.receive(10000); + result = failureChannel.receive(60000); assertNotNull(result); assertTrue(file.delete()); assertEquals("foo", result.getPayload()); @@ -132,18 +134,17 @@ public class FileInboundTransactionTests { } }); realTx.start(); - new File(tmpDir + "/si-test2").mkdir(); - File file = new File(tmpDir + "/si-test2/baz"); + File file = new File(tmpDir.getRoot(), "si-test2/baz"); file.createNewFile(); - Message result = successChannel.receive(40000); + Message result = successChannel.receive(60000); assertNotNull(result); assertEquals(Boolean.TRUE, result.getPayload()); assertTrue(file.delete()); assertTrue(transactionManager.getCommitted()); crash.set(true); - file = new File(tmpDir + "/si-test2/qux"); + file = new File(tmpDir.getRoot(), "si-test2/qux"); file.createNewFile(); - result = failureChannel.receive(10000); + result = failureChannel.receive(60000); assertNotNull(result); assertTrue(file.delete()); assertEquals(Boolean.TRUE, result.getPayload()); @@ -193,6 +194,7 @@ public class FileInboundTransactionTests { public boolean getRolledBack() { return rolledBack; } + } } diff --git a/spring-integration-jms/src/test/java/org/springframework/integration/jms/OutboundGatewayFunctionTests.java b/spring-integration-jms/src/test/java/org/springframework/integration/jms/OutboundGatewayFunctionTests.java index 1ec1de32ed..802533b43d 100644 --- a/spring-integration-jms/src/test/java/org/springframework/integration/jms/OutboundGatewayFunctionTests.java +++ b/spring-integration-jms/src/test/java/org/springframework/integration/jms/OutboundGatewayFunctionTests.java @@ -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. @@ -114,7 +114,7 @@ public class OutboundGatewayFunctionTests extends LogAdjustingTestSupport { assertTrue(latch1.await(10, TimeUnit.SECONDS)); JmsTemplate template = new JmsTemplate(); template.setConnectionFactory(connectionFactory); - template.setReceiveTimeout(5000); + template.setReceiveTimeout(10000); javax.jms.Message request = template.receive(requestQueue1); assertNotNull(request); final javax.jms.Message jmsReply = request; @@ -166,7 +166,7 @@ public class OutboundGatewayFunctionTests extends LogAdjustingTestSupport { assertTrue(latch1.await(10, TimeUnit.SECONDS)); JmsTemplate template = new JmsTemplate(); template.setConnectionFactory(getConnectionFactory()); - template.setReceiveTimeout(5000); + template.setReceiveTimeout(10000); javax.jms.Message request = template.receive(requestQueue2); assertNotNull(request); final javax.jms.Message jmsReply = request; @@ -220,7 +220,7 @@ public class OutboundGatewayFunctionTests extends LogAdjustingTestSupport { assertTrue(latch1.await(10, TimeUnit.SECONDS)); JmsTemplate template = new JmsTemplate(); template.setConnectionFactory(getConnectionFactory()); - template.setReceiveTimeout(5000); + template.setReceiveTimeout(10000); javax.jms.Message request = template.receive(requestQueue3); assertNotNull(request); final javax.jms.Message jmsReply = request; @@ -272,7 +272,7 @@ public class OutboundGatewayFunctionTests extends LogAdjustingTestSupport { assertTrue(latch1.await(10, TimeUnit.SECONDS)); JmsTemplate template = new JmsTemplate(); template.setConnectionFactory(getConnectionFactory()); - template.setReceiveTimeout(5000); + template.setReceiveTimeout(10000); javax.jms.Message request = template.receive(requestQueue4); assertNotNull(request); final javax.jms.Message jmsReply = request; @@ -326,7 +326,7 @@ public class OutboundGatewayFunctionTests extends LogAdjustingTestSupport { assertTrue(latch1.await(10, TimeUnit.SECONDS)); JmsTemplate template = new JmsTemplate(); template.setConnectionFactory(getConnectionFactory()); - template.setReceiveTimeout(5000); + template.setReceiveTimeout(10000); javax.jms.Message request = template.receive(requestQueue5); assertNotNull(request); final javax.jms.Message jmsReply = request; @@ -378,7 +378,7 @@ public class OutboundGatewayFunctionTests extends LogAdjustingTestSupport { assertTrue(latch1.await(10, TimeUnit.SECONDS)); JmsTemplate template = new JmsTemplate(); template.setConnectionFactory(getConnectionFactory()); - template.setReceiveTimeout(5000); + template.setReceiveTimeout(10000); javax.jms.Message request = template.receive(requestQueue6); assertNotNull(request); final javax.jms.Message jmsReply = request; diff --git a/src/reference/asciidoc/configuration.adoc b/src/reference/asciidoc/configuration.adoc index 3fe2a85f7c..bd5ac04a5a 100644 --- a/src/reference/asciidoc/configuration.adoc +++ b/src/reference/asciidoc/configuration.adoc @@ -205,6 +205,7 @@ spring.integration.channels.maxUnicastSubscribers=0x7fffffff <2> spring.integration.channels.maxBroadcastSubscribers=0x7fffffff <3> spring.integration.taskScheduler.poolSize=10 <4> spring.integration.messagingTemplate.throwExceptionOnLateReply=false <5> +spring.integration.readOnly.headers= <6> ---- <1> When true, `input-channel` s will be automatically declared as `DirectChannel` s when not explicitly found in the @@ -223,6 +224,11 @@ This can be overridden on individual channels with the `max-subscribers` attribu <5> When `true`, messages that arrive at a gateway reply channel will throw an exception, when the gateway is not expecting a reply - because the sending thread has timed out, or already received a reply. +<6> A comma-separated list of message header names which should not be populated into `Message` s during a header copying operation. +The list is used by the `DefaultMessageBuilderFactory` bean and propagated to the `IntegrationMessageHeaderAccessor` instances (see <>), used to build messages via `MessageBuilder` (see <>). +By default only `MessageHeaders.ID` and `MessageHeaders.TIMESTAMP` are not copied during message building. +_Since version 4.3.2_ + These properties can be overridden by adding a file `/META-INF/spring.integration.properties` to the classpath. It is not necessary to provide all the properties, just those that you want to override. diff --git a/src/reference/asciidoc/message.adoc b/src/reference/asciidoc/message.adoc index 122aab232d..8bf8898f64 100644 --- a/src/reference/asciidoc/message.adoc +++ b/src/reference/asciidoc/message.adoc @@ -461,3 +461,8 @@ assertEquals(2, lessImportantMessage.getHeaders().getPriority()); The `priority` header is only considered when using a `PriorityChannel` (as described in the next chapter). It is defined as _java.lang.Integer_. + +Since _version 4.3.2_, the `MessageBuilder` provides the `readOnlyHeaders(String... readOnlyHeaders)` API to customize a list of headers which should not be copied from an upstream `Message`. +Just the `MessageHeaders.ID` and `MessageHeaders.TIMESTAMP` are read only by default. +The global `spring.integration.readOnly.headers` property (see <>) is provided to customize `DefaultMessageBuilderFactory` for Framework components. +This can be useful when you would like do not populate some out-of-the-box headers, like `contentType` by the `ObjectToJsonTransformer` (see <>). diff --git a/src/reference/asciidoc/transformer.adoc b/src/reference/asciidoc/transformer.adoc index aa6b4fd47d..df16bdb760 100644 --- a/src/reference/asciidoc/transformer.adoc +++ b/src/reference/asciidoc/transformer.adoc @@ -241,6 +241,7 @@ public StreamTransformer streamToString() { } ---- +[[json-transformers]] ====== JSON Transformers _Object to JSON_ and _JSON to Object_ transformers are provided. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index f4112c6f8f..c1d044c36c 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -37,3 +37,8 @@ See <> for more information.