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 4b12e7c51e8..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; @@ -85,15 +86,21 @@ public class IntegrationAutoConfiguration { public static org.springframework.integration.context.IntegrationProperties integrationGlobalProperties( IntegrationProperties properties) { org.springframework.integration.context.IntegrationProperties integrationProperties = new org.springframework.integration.context.IntegrationProperties(); - integrationProperties.setChannelsAutoCreate(properties.getChannels().isAutoCreate()); - integrationProperties.setChannelsMaxUnicastSubscribers(properties.getChannels().getMaxUnicastSubscribers()); - integrationProperties.setChannelsMaxBroadcastSubscribers(properties.getChannels().getMaxBroadcastSubscribers()); - integrationProperties.setErrorChannelRequireSubscribers(properties.getChannels().isErrorRequireSubscribers()); - integrationProperties.setErrorChannelIgnoreFailures(properties.getChannels().isErrorIgnoreFailures()); - integrationProperties - .setMessagingTemplateThrowExceptionOnLateReply(properties.getEndpoints().isThrowExceptionOnLateReply()); - integrationProperties.setReadOnlyHeaders(properties.getEndpoints().getReadOnlyHeaders()); - integrationProperties.setNoAutoStartupEndpoints(properties.getEndpoints().getNoAutoStartup()); + 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; } 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 6b0fa5f14fa..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,20 +34,26 @@ import org.springframework.boot.jdbc.DataSourceInitializationMode; @ConfigurationProperties(prefix = "spring.integration") public class IntegrationProperties { - private final Channels channels = new Channels(); + private final Channel channel = new Channel(); - private final Endpoints endpoints = new Endpoints(); + 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 Channels getChannels() { - return this.channels; + public Channel getChannel() { + return this.channel; } - public Endpoints getEndpoints() { - return this.endpoints; + public Endpoint getEndpoint() { + return this.endpoint; + } + + public Error getError() { + return this.error; } public Jdbc getJdbc() { @@ -56,33 +64,24 @@ public class IntegrationProperties { return this.rsocket; } - public static class Channels { + public static class Channel { /** - * Whether to create input channels when no respective beans. + * Whether to create input channels if necessary. */ private boolean autoCreate = true; /** - * Default number of max subscribers on unicasting channels. + * Default number of subscribers allowed on, for example, a 'DirectChannel'. */ private int maxUnicastSubscribers = Integer.MAX_VALUE; /** - * Default number of max subscribers on broadcasting channels. + * Default number of subscribers allowed on, for example, a + * 'PublishSubscribeChannel'. */ private int maxBroadcastSubscribers = Integer.MAX_VALUE; - /** - * Require subscribers flag for global 'errorChannel'. - */ - private boolean errorRequireSubscribers = true; - - /** - * Ignore failures flag for global 'errorChannel'. - */ - private boolean errorIgnoreFailures = true; - public void setAutoCreate(boolean autoCreate) { this.autoCreate = autoCreate; } @@ -107,40 +106,27 @@ public class IntegrationProperties { return this.maxBroadcastSubscribers; } - public void setErrorRequireSubscribers(boolean errorRequireSubscribers) { - this.errorRequireSubscribers = errorRequireSubscribers; - } - - public boolean isErrorRequireSubscribers() { - return this.errorRequireSubscribers; - } - - public void setErrorIgnoreFailures(boolean errorIgnoreFailures) { - this.errorIgnoreFailures = errorIgnoreFailures; - } - - public boolean isErrorIgnoreFailures() { - return this.errorIgnoreFailures; - } - } - public static class Endpoints { + public static class Endpoint { /** - * Whether throw an exception on late reply for gateways. + * Whether to throw an exception when a reply is not expected anymore by a + * gateway. */ private boolean throwExceptionOnLateReply = false; /** - * Ignored headers during message building. + * A comma-separated list of message header names that should not be populated + * into Message instances during a header copying operation. */ - private String[] readOnlyHeaders = {}; + private List readOnlyHeaders = new ArrayList<>(); /** - * Spring Integration endpoints do not start automatically. + * A comma-separated list of endpoint bean names patterns that should not be + * started automatically during application startup. */ - private String[] noAutoStartup = {}; + private List noAutoStartup = new ArrayList<>(); public void setThrowExceptionOnLateReply(boolean throwExceptionOnLateReply) { this.throwExceptionOnLateReply = throwExceptionOnLateReply; @@ -150,20 +136,52 @@ public class IntegrationProperties { return this.throwExceptionOnLateReply; } - public void setReadOnlyHeaders(String[] readOnlyHeaders) { - this.readOnlyHeaders = readOnlyHeaders; - } - - public String[] getReadOnlyHeaders() { + public List getReadOnlyHeaders() { return this.readOnlyHeaders; } - public void setNoAutoStartup(String[] noAutoStartup) { + public void setReadOnlyHeaders(List readOnlyHeaders) { + this.readOnlyHeaders = readOnlyHeaders; + } + + public List getNoAutoStartup() { + return this.noAutoStartup; + } + + public void setNoAutoStartup(List noAutoStartup) { this.noAutoStartup = noAutoStartup; } - public String[] getNoAutoStartup() { - return this.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; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationEnvironmentPostProcessor.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessor.java similarity index 52% rename from spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationEnvironmentPostProcessor.java rename to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessor.java index 7033151a7a7..3ed91d7a501 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationEnvironmentPostProcessor.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessor.java @@ -16,8 +16,8 @@ package org.springframework.boot.autoconfigure.integration; -import java.io.FileNotFoundException; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -35,12 +35,13 @@ import org.springframework.core.io.Resource; import org.springframework.integration.context.IntegrationProperties; /** - * The {@link EnvironmentPostProcessor} for Spring Integration. + * An {@link EnvironmentPostProcessor} that maps the configuration of + * {@code META-INF/spring.integration.properties} in the environment. * * @author Artem Bilan - * @since 2.5 + * @author Stephane Nicoll */ -public class IntegrationEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { +class IntegrationPropertiesEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { @Override public int getOrder() { @@ -49,65 +50,62 @@ public class IntegrationEnvironmentPostProcessor implements EnvironmentPostProce @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { - registerIntegrationPropertiesFileSource(environment); + Resource resource = new ClassPathResource("META-INF/spring.integration.properties"); + if (resource.exists()) { + registerIntegrationPropertiesPropertySource(environment, resource); + } } - private static void registerIntegrationPropertiesFileSource(ConfigurableEnvironment environment) { - Resource integrationPropertiesResource = new ClassPathResource("META-INF/spring.integration.properties"); + protected void registerIntegrationPropertiesPropertySource(ConfigurableEnvironment environment, Resource resource) { PropertiesPropertySourceLoader loader = new PropertiesPropertySourceLoader(); try { OriginTrackedMapPropertySource propertyFileSource = (OriginTrackedMapPropertySource) loader - .load("integration-properties-file", integrationPropertiesResource).get(0); - - environment.getPropertySources().addLast(new IntegrationPropertySource(propertyFileSource)); - } - catch (FileNotFoundException ex) { - // Ignore when no META-INF/spring.integration.properties file in classpath + .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 " + integrationPropertiesResource, ex); + throw new IllegalStateException("Failed to load integration properties from " + resource, ex); } } - private static final class IntegrationPropertySource extends PropertySource> + private static final class IntegrationPropertiesPropertySource extends PropertySource> implements OriginLookup { private static final String PREFIX = "spring.integration."; - private static final Map KEYS_MAPPING = new HashMap<>(); + private static final Map KEYS_MAPPING; static { - KEYS_MAPPING.put(PREFIX + "channels.auto-create", IntegrationProperties.CHANNELS_AUTOCREATE); - KEYS_MAPPING.put(PREFIX + "channels.max-unicast-subscribers", + 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); - KEYS_MAPPING.put(PREFIX + "channels.max-broadcast-subscribers", + mappings.put(PREFIX + "channel.max-broadcast-subscribers", IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS); - KEYS_MAPPING.put(PREFIX + "channels.error-require-subscribers", - IntegrationProperties.ERROR_CHANNEL_REQUIRE_SUBSCRIBERS); - KEYS_MAPPING.put(PREFIX + "channels.error-ignore-failures", - IntegrationProperties.ERROR_CHANNEL_IGNORE_FAILURES); - KEYS_MAPPING.put(PREFIX + "endpoints.throw-exception-on-late-reply", + 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); - KEYS_MAPPING.put(PREFIX + "endpoints.read-only-headers", IntegrationProperties.READ_ONLY_HEADERS); - KEYS_MAPPING.put(PREFIX + "endpoints.no-auto-startup", IntegrationProperties.ENDPOINTS_NO_AUTO_STARTUP); + 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 origin; + private final OriginTrackedMapPropertySource delegate; - IntegrationPropertySource(OriginTrackedMapPropertySource origin) { - super("original-integration-properties", origin.getSource()); - this.origin = origin; + IntegrationPropertiesPropertySource(OriginTrackedMapPropertySource delegate) { + super("META-INF/spring.integration.properties", delegate.getSource()); + this.delegate = delegate; } @Override public Object getProperty(String name) { - return this.origin.getProperty(KEYS_MAPPING.get(name)); + return this.delegate.getProperty(KEYS_MAPPING.get(name)); } @Override public Origin getOrigin(String key) { - return this.origin.getOrigin(KEYS_MAPPING.get(name)); + 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 405f152e173..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 @@ -9,7 +9,7 @@ org.springframework.boot.autoconfigure.BackgroundPreinitializer # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ -org.springframework.boot.autoconfigure.integration.IntegrationEnvironmentPostProcessor +org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ 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 7b6aa5510e2..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 @@ -16,15 +16,10 @@ package org.springframework.boot.autoconfigure.integration; -import java.io.File; -import java.util.Arrays; -import java.util.List; - import javax.management.MBeanServer; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.netty.client.TcpClientTransport; -import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -42,25 +37,16 @@ import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfi import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration; import org.springframework.boot.jdbc.DataSourceInitializationMode; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; import org.springframework.integration.annotation.IntegrationComponentScan; import org.springframework.integration.annotation.MessagingGateway; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.PublishSubscribeChannel; import org.springframework.integration.config.IntegrationManagementConfigurer; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.core.MessageSource; -import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.endpoint.MessageProcessorMessageSource; import org.springframework.integration.gateway.RequestReplyExchanger; -import org.springframework.integration.handler.LoggingHandler; import org.springframework.integration.handler.MessageProcessor; import org.springframework.integration.rsocket.ClientRSocketConnector; import org.springframework.integration.rsocket.IntegrationRSocketEndpoint; @@ -266,111 +252,88 @@ class IntegrationAutoConfigurationTests { @Test void integrationGlobalPropertiesAutoConfigured() { - new ApplicationContextRunner(() -> { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.setResourceLoader( - new FilteringResourceLoader(new DefaultResourceLoader(), "META-INF/spring.integration.properties")); - return context; - }).withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, IntegrationAutoConfiguration.class)) - .withPropertyValues("spring.integration.channels.auto-create=false", - "spring.integration.channels.max-unicast-subscribers=2", - "spring.integration.channels.max-broadcast-subscribers=3", - "spring.integration.channels.error-require-subscribers=false", - "spring.integration.channels.error-ignore-failures=false", - "spring.integration.endpoints.throw-exception-on-late-reply=true", - "spring.integration.endpoints.read-only-headers=ignoredHeader", - "spring.integration.endpoints.no-auto-startup=notStartedEndpoint,_org.springframework.integration.errorLogger") - .withBean("testDirectChannel", DirectChannel.class) - .withInitializer((applicationContext) -> new IntegrationEnvironmentPostProcessor() - .postProcessEnvironment(applicationContext.getEnvironment(), null)) + 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) - .getBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME, PublishSubscribeChannel.class) - .hasFieldOrPropertyWithValue("requireSubscribers", false) - .hasFieldOrPropertyWithValue("ignoreFailures", false) - .hasFieldOrPropertyWithValue("maxSubscribers", 3); - assertThat(context).getBean("testDirectChannel", DirectChannel.class) - .hasFieldOrPropertyWithValue("maxSubscribers", 2); - LoggingHandler loggingHandler = context.getBean(LoggingHandler.class); - assertThat(loggingHandler) - .hasFieldOrPropertyWithValue("messageBuilderFactory.readOnlyHeaders", - new String[] { "ignoredHeader" }) - .extracting("integrationProperties", InstanceOfAssertFactories.MAP) - .containsEntry( - org.springframework.integration.context.IntegrationProperties.THROW_EXCEPTION_ON_LATE_REPLY, - "true") - .containsEntry( - org.springframework.integration.context.IntegrationProperties.ENDPOINTS_NO_AUTO_STARTUP, - "notStartedEndpoint,_org.springframework.integration.errorLogger"); - assertThat(context) - .getBean(IntegrationContextUtils.ERROR_LOGGER_BEAN_NAME, EventDrivenConsumer.class) - .hasFieldOrPropertyWithValue("autoStartup", false); + .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() { - this.contextRunner.withPropertyValues("spring.integration.channels.auto-create=false", - "spring.integration.channels.max-unicast-subscribers=2", - "spring.integration.channels.max-broadcast-subscribers=3", - "spring.integration.channels.error-require-subscribers=false", - "spring.integration.channels.error-ignore-failures=false", - "spring.integration.endpoints.throw-exception-on-late-reply=true", - "spring.integration.endpoints.read-only-headers=ignoredHeader", - "spring.integration.endpoints.no-auto-startup=notStartedEndpoint,_org.springframework.integration.errorLogger") + 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, () -> { - org.springframework.integration.context.IntegrationProperties properties = new org.springframework.integration.context.IntegrationProperties(); - properties.setChannelsMaxUnicastSubscribers(5); - return properties; - }) - .withInitializer((applicationContext) -> new IntegrationEnvironmentPostProcessor() - .postProcessEnvironment(applicationContext.getEnvironment(), null)) - .run((context) -> assertThat(context).getBean(LoggingHandler.class) - .extracting("integrationProperties", InstanceOfAssertFactories.MAP) - .containsEntry( - org.springframework.integration.context.IntegrationProperties.CHANNELS_AUTOCREATE, - "true") - .containsEntry( - org.springframework.integration.context.IntegrationProperties.ERROR_CHANNEL_REQUIRE_SUBSCRIBERS, - "true") - .containsEntry( - org.springframework.integration.context.IntegrationProperties.ERROR_CHANNEL_IGNORE_FAILURES, - "true") - .containsEntry( - org.springframework.integration.context.IntegrationProperties.THROW_EXCEPTION_ON_LATE_REPLY, - "false") - .containsEntry( - org.springframework.integration.context.IntegrationProperties.CHANNELS_MAX_UNICAST_SUBSCRIBERS, - "5") - .containsEntry( - org.springframework.integration.context.IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS, - "2147483647") - .containsEntry( - org.springframework.integration.context.IntegrationProperties.ENDPOINTS_NO_AUTO_STARTUP, - "") - .containsEntry(org.springframework.integration.context.IntegrationProperties.READ_ONLY_HEADERS, - "")); + 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() { - // See META-INF/spring.integration.properties this.contextRunner - .withPropertyValues("spring.integration.channels.auto-create=false", - "spring.integration.endpoints.read-only-headers=ignoredHeader") - .withInitializer((applicationContext) -> new IntegrationEnvironmentPostProcessor() + .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).getBean(LoggingHandler.class) - .extracting("integrationProperties", InstanceOfAssertFactories.MAP) - .containsEntry( - org.springframework.integration.context.IntegrationProperties.CHANNELS_AUTOCREATE, - "false") - .containsEntry(org.springframework.integration.context.IntegrationProperties.READ_ONLY_HEADERS, - "ignoredHeader") - .containsEntry( - org.springframework.integration.context.IntegrationProperties.ENDPOINTS_NO_AUTO_STARTUP, - "testService*")); + .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) @@ -427,32 +390,4 @@ class IntegrationAutoConfigurationTests { } - private static final class FilteringResourceLoader implements ResourceLoader { - - private final ResourceLoader delegate; - - private final List resourcesToFilter; - - FilteringResourceLoader(ResourceLoader delegate, String... resourcesToFilter) { - this.delegate = delegate; - this.resourcesToFilter = Arrays.asList(resourcesToFilter); - } - - @Override - public Resource getResource(String location) { - if (!this.resourcesToFilter.contains(location)) { - return this.delegate.getResource(location); - } - else { - return new FileSystemResource(mock(File.class)); - } - } - - @Override - public ClassLoader getClassLoader() { - return this.delegate.getClassLoader(); - } - - } - } 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/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