diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java index c0ef5e18cb7..054d321781e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java @@ -36,6 +36,7 @@ import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration; import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.task.TaskSchedulerBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -80,6 +81,29 @@ import org.springframework.util.StringUtils; TaskSchedulingAutoConfiguration.class }) public class IntegrationAutoConfiguration { + @Bean(name = IntegrationContextUtils.INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME) + @ConditionalOnMissingBean(name = IntegrationContextUtils.INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME) + public static org.springframework.integration.context.IntegrationProperties integrationGlobalProperties( + IntegrationProperties properties) { + org.springframework.integration.context.IntegrationProperties integrationProperties = new org.springframework.integration.context.IntegrationProperties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties.getChannel().isAutoCreate()).to(integrationProperties::setChannelsAutoCreate); + map.from(properties.getChannel().getMaxUnicastSubscribers()) + .to(integrationProperties::setChannelsMaxUnicastSubscribers); + map.from(properties.getChannel().getMaxBroadcastSubscribers()) + .to(integrationProperties::setChannelsMaxBroadcastSubscribers); + map.from(properties.getError().isRequireSubscribers()) + .to(integrationProperties::setErrorChannelRequireSubscribers); + map.from(properties.getError().isIgnoreFailures()).to(integrationProperties::setErrorChannelIgnoreFailures); + map.from(properties.getEndpoint().isThrowExceptionOnLateReply()) + .to(integrationProperties::setMessagingTemplateThrowExceptionOnLateReply); + map.from(properties.getEndpoint().getReadOnlyHeaders()).as(StringUtils::toStringArray) + .to(integrationProperties::setReadOnlyHeaders); + map.from(properties.getEndpoint().getNoAutoStartup()).as(StringUtils::toStringArray) + .to(integrationProperties::setNoAutoStartupEndpoints); + return integrationProperties; + } + /** * Basic Spring Integration configuration. */ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationProperties.java index 743c64f63b6..b5e10e9c20a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -17,6 +17,8 @@ package org.springframework.boot.autoconfigure.integration; import java.net.URI; +import java.util.ArrayList; +import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceInitializationMode; @@ -32,10 +34,28 @@ import org.springframework.boot.jdbc.DataSourceInitializationMode; @ConfigurationProperties(prefix = "spring.integration") public class IntegrationProperties { + private final Channel channel = new Channel(); + + private final Endpoint endpoint = new Endpoint(); + + private final Error error = new Error(); + private final Jdbc jdbc = new Jdbc(); private final RSocket rsocket = new RSocket(); + public Channel getChannel() { + return this.channel; + } + + public Endpoint getEndpoint() { + return this.endpoint; + } + + public Error getError() { + return this.error; + } + public Jdbc getJdbc() { return this.jdbc; } @@ -44,6 +64,128 @@ public class IntegrationProperties { return this.rsocket; } + public static class Channel { + + /** + * Whether to create input channels if necessary. + */ + private boolean autoCreate = true; + + /** + * Default number of subscribers allowed on, for example, a 'DirectChannel'. + */ + private int maxUnicastSubscribers = Integer.MAX_VALUE; + + /** + * Default number of subscribers allowed on, for example, a + * 'PublishSubscribeChannel'. + */ + private int maxBroadcastSubscribers = Integer.MAX_VALUE; + + public void setAutoCreate(boolean autoCreate) { + this.autoCreate = autoCreate; + } + + public boolean isAutoCreate() { + return this.autoCreate; + } + + public void setMaxUnicastSubscribers(int maxUnicastSubscribers) { + this.maxUnicastSubscribers = maxUnicastSubscribers; + } + + public int getMaxUnicastSubscribers() { + return this.maxUnicastSubscribers; + } + + public void setMaxBroadcastSubscribers(int maxBroadcastSubscribers) { + this.maxBroadcastSubscribers = maxBroadcastSubscribers; + } + + public int getMaxBroadcastSubscribers() { + return this.maxBroadcastSubscribers; + } + + } + + public static class Endpoint { + + /** + * Whether to throw an exception when a reply is not expected anymore by a + * gateway. + */ + private boolean throwExceptionOnLateReply = false; + + /** + * A comma-separated list of message header names that should not be populated + * into Message instances during a header copying operation. + */ + private List readOnlyHeaders = new ArrayList<>(); + + /** + * A comma-separated list of endpoint bean names patterns that should not be + * started automatically during application startup. + */ + private List noAutoStartup = new ArrayList<>(); + + public void setThrowExceptionOnLateReply(boolean throwExceptionOnLateReply) { + this.throwExceptionOnLateReply = throwExceptionOnLateReply; + } + + public boolean isThrowExceptionOnLateReply() { + return this.throwExceptionOnLateReply; + } + + public List getReadOnlyHeaders() { + return this.readOnlyHeaders; + } + + public void setReadOnlyHeaders(List readOnlyHeaders) { + this.readOnlyHeaders = readOnlyHeaders; + } + + public List getNoAutoStartup() { + return this.noAutoStartup; + } + + public void setNoAutoStartup(List noAutoStartup) { + this.noAutoStartup = noAutoStartup; + } + + } + + public static class Error { + + /** + * Whether to not silently ignore messages on the global 'errorChannel' when they + * are no subscribers. + */ + private boolean requireSubscribers = true; + + /** + * Whether to ignore failures for one or more of the handlers of the global + * 'errorChannel'. + */ + private boolean ignoreFailures = true; + + public boolean isRequireSubscribers() { + return this.requireSubscribers; + } + + public void setRequireSubscribers(boolean requireSubscribers) { + this.requireSubscribers = requireSubscribers; + } + + public boolean isIgnoreFailures() { + return this.ignoreFailures; + } + + public void setIgnoreFailures(boolean ignoreFailures) { + this.ignoreFailures = ignoreFailures; + } + + } + public static class Jdbc { private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/" @@ -139,7 +281,7 @@ public class IntegrationProperties { /** * Whether to handle message mapping for RSocket via Spring Integration. */ - boolean messageMappingEnabled; + private boolean messageMappingEnabled; public boolean isMessageMappingEnabled() { return this.messageMappingEnabled; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessor.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessor.java new file mode 100644 index 00000000000..3ed91d7a501 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessor.java @@ -0,0 +1,113 @@ +/* + * Copyright 2012-2021 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.integration; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.boot.env.OriginTrackedMapPropertySource; +import org.springframework.boot.env.PropertiesPropertySourceLoader; +import org.springframework.boot.origin.Origin; +import org.springframework.boot.origin.OriginLookup; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.integration.context.IntegrationProperties; + +/** + * An {@link EnvironmentPostProcessor} that maps the configuration of + * {@code META-INF/spring.integration.properties} in the environment. + * + * @author Artem Bilan + * @author Stephane Nicoll + */ +class IntegrationPropertiesEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + Resource resource = new ClassPathResource("META-INF/spring.integration.properties"); + if (resource.exists()) { + registerIntegrationPropertiesPropertySource(environment, resource); + } + } + + protected void registerIntegrationPropertiesPropertySource(ConfigurableEnvironment environment, Resource resource) { + PropertiesPropertySourceLoader loader = new PropertiesPropertySourceLoader(); + try { + OriginTrackedMapPropertySource propertyFileSource = (OriginTrackedMapPropertySource) loader + .load("META-INF/spring.integration.properties", resource).get(0); + environment.getPropertySources().addLast(new IntegrationPropertiesPropertySource(propertyFileSource)); + } + catch (IOException ex) { + throw new IllegalStateException("Failed to load integration properties from " + resource, ex); + } + } + + private static final class IntegrationPropertiesPropertySource extends PropertySource> + implements OriginLookup { + + private static final String PREFIX = "spring.integration."; + + private static final Map KEYS_MAPPING; + + static { + Map mappings = new HashMap<>(); + mappings.put(PREFIX + "channel.auto-create", IntegrationProperties.CHANNELS_AUTOCREATE); + mappings.put(PREFIX + "channel.max-unicast-subscribers", + IntegrationProperties.CHANNELS_MAX_UNICAST_SUBSCRIBERS); + mappings.put(PREFIX + "channel.max-broadcast-subscribers", + IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS); + mappings.put(PREFIX + "error.require-subscribers", IntegrationProperties.ERROR_CHANNEL_REQUIRE_SUBSCRIBERS); + mappings.put(PREFIX + "error.ignore-failures", IntegrationProperties.ERROR_CHANNEL_IGNORE_FAILURES); + mappings.put(PREFIX + "endpoint.throw-exception-on-late-reply", + IntegrationProperties.THROW_EXCEPTION_ON_LATE_REPLY); + mappings.put(PREFIX + "endpoint.read-only-headers", IntegrationProperties.READ_ONLY_HEADERS); + mappings.put(PREFIX + "endpoint.no-auto-startup", IntegrationProperties.ENDPOINTS_NO_AUTO_STARTUP); + KEYS_MAPPING = Collections.unmodifiableMap(mappings); + } + + private final OriginTrackedMapPropertySource delegate; + + IntegrationPropertiesPropertySource(OriginTrackedMapPropertySource delegate) { + super("META-INF/spring.integration.properties", delegate.getSource()); + this.delegate = delegate; + } + + @Override + public Object getProperty(String name) { + return this.delegate.getProperty(KEYS_MAPPING.get(name)); + } + + @Override + public Origin getOrigin(String key) { + return this.delegate.getOrigin(KEYS_MAPPING.get(key)); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 730818f509d..446a139d16a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -7,6 +7,10 @@ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingL org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer +# Environment Post Processors +org.springframework.boot.env.EnvironmentPostProcessor=\ +org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor + # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java index a6b9c0eaa74..03d1f5cc861 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java @@ -250,6 +250,92 @@ class IntegrationAutoConfigurationTests { }); } + @Test + void integrationGlobalPropertiesAutoConfigured() { + this.contextRunner.withPropertyValues("spring.integration.channel.auto-create=false", + "spring.integration.channel.max-unicast-subscribers=2", + "spring.integration.channel.max-broadcast-subscribers=3", + "spring.integration.error.require-subscribers=false", "spring.integration.error.ignore-failures=false", + "spring.integration.endpoint.throw-exception-on-late-reply=true", + "spring.integration.endpoint.read-only-headers=ignoredHeader", + "spring.integration.endpoint.no-auto-startup=notStartedEndpoint,_org.springframework.integration.errorLogger") + .run((context) -> { + assertThat(context) + .hasSingleBean(org.springframework.integration.context.IntegrationProperties.class); + org.springframework.integration.context.IntegrationProperties integrationProperties = context + .getBean(org.springframework.integration.context.IntegrationProperties.class); + assertThat(integrationProperties.isChannelsAutoCreate()).isFalse(); + assertThat(integrationProperties.getChannelsMaxUnicastSubscribers()).isEqualTo(2); + assertThat(integrationProperties.getChannelsMaxBroadcastSubscribers()).isEqualTo(3); + assertThat(integrationProperties.isErrorChannelRequireSubscribers()).isFalse(); + assertThat(integrationProperties.isErrorChannelIgnoreFailures()).isFalse(); + assertThat(integrationProperties.isMessagingTemplateThrowExceptionOnLateReply()).isTrue(); + assertThat(integrationProperties.getReadOnlyHeaders()).containsOnly("ignoredHeader"); + assertThat(integrationProperties.getNoAutoStartupEndpoints()).containsOnly("notStartedEndpoint", + "_org.springframework.integration.errorLogger"); + }); + } + + @Test + void integrationGlobalPropertiesUseConsistentDefault() { + org.springframework.integration.context.IntegrationProperties defaultIntegrationProperties = new org.springframework.integration.context.IntegrationProperties(); + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(org.springframework.integration.context.IntegrationProperties.class); + org.springframework.integration.context.IntegrationProperties integrationProperties = context + .getBean(org.springframework.integration.context.IntegrationProperties.class); + assertThat(integrationProperties.isChannelsAutoCreate()) + .isEqualTo(defaultIntegrationProperties.isChannelsAutoCreate()); + assertThat(integrationProperties.getChannelsMaxUnicastSubscribers()) + .isEqualTo(defaultIntegrationProperties.getChannelsMaxBroadcastSubscribers()); + assertThat(integrationProperties.getChannelsMaxBroadcastSubscribers()) + .isEqualTo(defaultIntegrationProperties.getChannelsMaxBroadcastSubscribers()); + assertThat(integrationProperties.isErrorChannelRequireSubscribers()) + .isEqualTo(defaultIntegrationProperties.isErrorChannelIgnoreFailures()); + assertThat(integrationProperties.isErrorChannelIgnoreFailures()) + .isEqualTo(defaultIntegrationProperties.isErrorChannelIgnoreFailures()); + assertThat(integrationProperties.isMessagingTemplateThrowExceptionOnLateReply()) + .isEqualTo(defaultIntegrationProperties.isMessagingTemplateThrowExceptionOnLateReply()); + assertThat(integrationProperties.getReadOnlyHeaders()) + .isEqualTo(defaultIntegrationProperties.getReadOnlyHeaders()); + assertThat(integrationProperties.getNoAutoStartupEndpoints()) + .isEqualTo(defaultIntegrationProperties.getNoAutoStartupEndpoints()); + }); + } + + @Test + void integrationGlobalPropertiesUserBeanOverridesAutoConfiguration() { + org.springframework.integration.context.IntegrationProperties userIntegrationProperties = new org.springframework.integration.context.IntegrationProperties(); + this.contextRunner.withPropertyValues() + .withBean(IntegrationContextUtils.INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME, + org.springframework.integration.context.IntegrationProperties.class, + () -> userIntegrationProperties) + .run((context) -> { + assertThat(context) + .hasSingleBean(org.springframework.integration.context.IntegrationProperties.class); + assertThat(context.getBean(org.springframework.integration.context.IntegrationProperties.class)) + .isSameAs(userIntegrationProperties); + }); + } + + @Test + void integrationGlobalPropertiesFromSpringIntegrationPropertiesFile() { + this.contextRunner + .withPropertyValues("spring.integration.channel.auto-create=false", + "spring.integration.endpoint.read-only-headers=ignoredHeader") + .withInitializer((applicationContext) -> new IntegrationPropertiesEnvironmentPostProcessor() + .postProcessEnvironment(applicationContext.getEnvironment(), null)) + .run((context) -> { + assertThat(context) + .hasSingleBean(org.springframework.integration.context.IntegrationProperties.class); + org.springframework.integration.context.IntegrationProperties integrationProperties = context + .getBean(org.springframework.integration.context.IntegrationProperties.class); + assertThat(integrationProperties.isChannelsAutoCreate()).isFalse(); + assertThat(integrationProperties.getReadOnlyHeaders()).containsOnly("ignoredHeader"); + // See META-INF/spring.integration.properties + assertThat(integrationProperties.getNoAutoStartupEndpoints()).containsOnly("testService*"); + }); + } + @Configuration(proxyBeanMethods = false) static class CustomMBeanExporter { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessorTests.java new file mode 100644 index 00000000000..d5961921ecd --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessorTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2012-2021 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.integration; + +import java.io.FileNotFoundException; +import java.util.Collections; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.origin.Origin; +import org.springframework.boot.origin.OriginLookup; +import org.springframework.boot.origin.TextResourceOrigin; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link IntegrationPropertiesEnvironmentPostProcessor}. + * + * @author Stephane Nicoll + */ +class IntegrationPropertiesEnvironmentPostProcessorTests { + + @Test + void postProcessEnvironmentAddPropertySource() { + ConfigurableEnvironment environment = new StandardEnvironment(); + new IntegrationPropertiesEnvironmentPostProcessor().postProcessEnvironment(environment, + mock(SpringApplication.class)); + assertThat(environment.getPropertySources().contains("META-INF/spring.integration.properties")).isTrue(); + assertThat(environment.getProperty("spring.integration.endpoint.no-auto-startup")).isEqualTo("testService*"); + } + + @Test + void postProcessEnvironmentAddPropertySourceLast() { + ConfigurableEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addLast(new MapPropertySource("test", + Collections.singletonMap("spring.integration.endpoint.no-auto-startup", "another*"))); + new IntegrationPropertiesEnvironmentPostProcessor().postProcessEnvironment(environment, + mock(SpringApplication.class)); + assertThat(environment.getPropertySources().contains("META-INF/spring.integration.properties")).isTrue(); + assertThat(environment.getProperty("spring.integration.endpoint.no-auto-startup")).isEqualTo("another*"); + } + + @Test + void registerIntegrationPropertiesPropertySourceWithUnknownResourceThrowsException() { + ConfigurableEnvironment environment = new StandardEnvironment(); + ClassPathResource unknown = new ClassPathResource("does-not-exist.properties", getClass()); + assertThatThrownBy(() -> new IntegrationPropertiesEnvironmentPostProcessor() + .registerIntegrationPropertiesPropertySource(environment, unknown)) + .isInstanceOf(IllegalStateException.class).hasCauseInstanceOf(FileNotFoundException.class) + .hasMessageContaining(unknown.toString()); + } + + @Test + void registerIntegrationPropertiesPropertySourceWithResourceAddPropertySource() { + ConfigurableEnvironment environment = new StandardEnvironment(); + new IntegrationPropertiesEnvironmentPostProcessor().registerIntegrationPropertiesPropertySource(environment, + new ClassPathResource("spring.integration.properties", getClass())); + assertThat(environment.getProperty("spring.integration.channel.auto-create", Boolean.class)).isFalse(); + assertThat(environment.getProperty("spring.integration.channel.max-unicast-subscribers", Integer.class)) + .isEqualTo(4); + assertThat(environment.getProperty("spring.integration.channel.max-broadcast-subscribers", Integer.class)) + .isEqualTo(6); + assertThat(environment.getProperty("spring.integration.error.require-subscribers", Boolean.class)).isFalse(); + assertThat(environment.getProperty("spring.integration.error.ignore-failures", Boolean.class)).isFalse(); + assertThat(environment.getProperty("spring.integration.endpoint.throw-exception-on-late-reply", Boolean.class)) + .isTrue(); + assertThat(environment.getProperty("spring.integration.endpoint.read-only-headers", String.class)) + .isEqualTo("header1,header2"); + assertThat(environment.getProperty("spring.integration.endpoint.no-auto-startup", String.class)) + .isEqualTo("testService,anotherService"); + } + + @Test + @SuppressWarnings("unchecked") + void registerIntegrationPropertiesPropertySourceWithResourceCanRetrieveOrigin() { + ConfigurableEnvironment environment = new StandardEnvironment(); + ClassPathResource resource = new ClassPathResource("spring.integration.properties", getClass()); + new IntegrationPropertiesEnvironmentPostProcessor().registerIntegrationPropertiesPropertySource(environment, + resource); + PropertySource ps = environment.getPropertySources().get("META-INF/spring.integration.properties"); + assertThat(ps).isNotNull().isInstanceOf(OriginLookup.class); + OriginLookup originLookup = (OriginLookup) ps; + assertThat(originLookup.getOrigin("spring.integration.channel.auto-create")) + .satisfies(textOrigin(resource, 0, 39)); + assertThat(originLookup.getOrigin("spring.integration.channel.max-unicast-subscribers")) + .satisfies(textOrigin(resource, 1, 50)); + assertThat(originLookup.getOrigin("spring.integration.channel.max-broadcast-subscribers")) + .satisfies(textOrigin(resource, 2, 52)); + } + + private Consumer textOrigin(Resource resource, int line, int column) { + return (origin) -> { + assertThat(origin).isInstanceOf(TextResourceOrigin.class); + TextResourceOrigin textOrigin = (TextResourceOrigin) origin; + assertThat(textOrigin.getResource()).isEqualTo(resource); + assertThat(textOrigin.getLocation().getLine()).isEqualTo(line); + assertThat(textOrigin.getLocation().getColumn()).isEqualTo(column); + }; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring.integration.properties b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring.integration.properties new file mode 100644 index 00000000000..2b2aca228b9 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring.integration.properties @@ -0,0 +1 @@ +spring.integration.endpoints.noAutoStartup=testService* diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/integration/spring.integration.properties b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/integration/spring.integration.properties new file mode 100644 index 00000000000..ea598a9feb6 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/integration/spring.integration.properties @@ -0,0 +1,8 @@ +spring.integration.channels.autoCreate=false +spring.integration.channels.maxUnicastSubscribers=4 +spring.integration.channels.maxBroadcastSubscribers=6 +spring.integration.channels.error.requireSubscribers=false +spring.integration.channels.error.ignoreFailures=false +spring.integration.messagingTemplate.throwExceptionOnLateReply=true +spring.integration.readOnly.headers=header1,header2 +spring.integration.endpoints.noAutoStartup=testService,anotherService