Create spring-boot-integration module

This commit is contained in:
Andy Wilkinson
2025-03-21 12:29:13 +00:00
committed by Phillip Webb
parent 0d5a141a41
commit cf7d8332e2
29 changed files with 68 additions and 38 deletions

View File

@@ -0,0 +1,385 @@
/*
* Copyright 2012-2025 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.integration.autoconfigure;
import java.time.Duration;
import javax.management.MBeanServer;
import javax.sql.DataSource;
import io.rsocket.transport.netty.server.TcpServerTransport;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxProperties;
import org.springframework.boot.autoconfigure.sql.init.OnDatabaseInitializationCondition;
import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder;
import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.config.EnableIntegrationManagement;
import org.springframework.integration.config.IntegrationComponentScanRegistrar;
import org.springframework.integration.config.IntegrationManagementConfigurer;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.jdbc.store.JdbcMessageStore;
import org.springframework.integration.jmx.config.EnableIntegrationMBeanExport;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
import org.springframework.integration.rsocket.ClientRSocketConnector;
import org.springframework.integration.rsocket.IntegrationRSocketEndpoint;
import org.springframework.integration.rsocket.ServerRSocketConnector;
import org.springframework.integration.rsocket.ServerRSocketMessageHandler;
import org.springframework.integration.rsocket.outbound.RSocketOutboundGateway;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.util.StringUtils;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} for Spring Integration.
*
* @author Artem Bilan
* @author Dave Syer
* @author Stephane Nicoll
* @author Vedran Pavic
* @author Madhura Bhave
* @author Yong-Hyun Kim
* @author Yanming Zhou
* @since 1.1.0
*/
@AutoConfiguration(beforeName = "org.springframework.boot.rsocket.autoconfigure.RSocketMessagingAutoConfiguration",
after = { JmxAutoConfiguration.class, TaskSchedulingAutoConfiguration.class },
afterName = "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration")
@ConditionalOnClass(EnableIntegration.class)
@EnableConfigurationProperties({ IntegrationProperties.class, JmxProperties.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().getDefaultTimeout())
.as(Duration::toMillis)
.to(integrationProperties::setEndpointsDefaultTimeout);
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.
*/
@Configuration(proxyBeanMethods = false)
@EnableIntegration
protected static class IntegrationConfiguration {
@Bean(PollerMetadata.DEFAULT_POLLER)
@ConditionalOnMissingBean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata defaultPollerMetadata(IntegrationProperties integrationProperties,
ObjectProvider<PollerMetadataCustomizer> customizers) {
IntegrationProperties.Poller poller = integrationProperties.getPoller();
MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> {
entries.put("spring.integration.poller.cron",
StringUtils.hasText(poller.getCron()) ? poller.getCron() : null);
entries.put("spring.integration.poller.fixed-delay", poller.getFixedDelay());
entries.put("spring.integration.poller.fixed-rate", poller.getFixedRate());
});
PollerMetadata pollerMetadata = new PollerMetadata();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(poller::getMaxMessagesPerPoll).to(pollerMetadata::setMaxMessagesPerPoll);
map.from(poller::getReceiveTimeout).as(Duration::toMillis).to(pollerMetadata::setReceiveTimeout);
map.from(poller).as(this::asTrigger).to(pollerMetadata::setTrigger);
customizers.orderedStream().forEach((customizer) -> customizer.customize(pollerMetadata));
return pollerMetadata;
}
private Trigger asTrigger(IntegrationProperties.Poller poller) {
if (StringUtils.hasText(poller.getCron())) {
return new CronTrigger(poller.getCron());
}
if (poller.getFixedDelay() != null) {
return createPeriodicTrigger(poller.getFixedDelay(), poller.getInitialDelay(), false);
}
if (poller.getFixedRate() != null) {
return createPeriodicTrigger(poller.getFixedRate(), poller.getInitialDelay(), true);
}
return null;
}
private Trigger createPeriodicTrigger(Duration period, Duration initialDelay, boolean fixedRate) {
PeriodicTrigger trigger = new PeriodicTrigger(period);
if (initialDelay != null) {
trigger.setInitialDelay(initialDelay);
}
trigger.setFixedRate(fixedRate);
return trigger;
}
}
/**
* Expose a standard {@link org.springframework.scheduling.TaskScheduler
* TaskScheduler} if the user has not enabled task scheduling explicitly. A
* {@link SimpleAsyncTaskScheduler} is exposed if the user enables virtual threads via
* {@code spring.threads.virtual.enabled=true}, otherwise
* {@link ThreadPoolTaskScheduler}.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME)
protected static class IntegrationTaskSchedulerConfiguration {
@Bean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME)
@ConditionalOnBean(ThreadPoolTaskSchedulerBuilder.class)
@ConditionalOnThreading(Threading.PLATFORM)
public ThreadPoolTaskScheduler taskScheduler(ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder) {
return threadPoolTaskSchedulerBuilder.build();
}
@Bean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME)
@ConditionalOnBean(SimpleAsyncTaskSchedulerBuilder.class)
@ConditionalOnThreading(Threading.VIRTUAL)
public SimpleAsyncTaskScheduler taskSchedulerVirtualThreads(
SimpleAsyncTaskSchedulerBuilder simpleAsyncTaskSchedulerBuilder) {
return simpleAsyncTaskSchedulerBuilder.build();
}
}
/**
* Spring Integration JMX configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableIntegrationMBeanExport.class)
@ConditionalOnMissingBean(value = IntegrationMBeanExporter.class, search = SearchStrategy.CURRENT)
@ConditionalOnBean(MBeanServer.class)
@ConditionalOnBooleanProperty("spring.jmx.enabled")
protected static class IntegrationJmxConfiguration {
@Bean
public static IntegrationMBeanExporter integrationMbeanExporter(ApplicationContext applicationContext) {
return new IntegrationMBeanExporter() {
@Override
public void afterSingletonsInstantiated() {
JmxProperties properties = applicationContext.getBean(JmxProperties.class);
String defaultDomain = properties.getDefaultDomain();
if (StringUtils.hasLength(defaultDomain)) {
setDefaultDomain(defaultDomain);
}
setServer(applicationContext.getBean(properties.getServer(), MBeanServer.class));
super.afterSingletonsInstantiated();
}
};
}
}
/**
* Integration management configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableIntegrationManagement.class)
@ConditionalOnMissingBean(value = IntegrationManagementConfigurer.class,
name = IntegrationManagementConfigurer.MANAGEMENT_CONFIGURER_NAME, search = SearchStrategy.CURRENT)
protected static class IntegrationManagementConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableIntegrationManagement(
defaultLoggingEnabled = "${spring.integration.management.default-logging-enabled:true}",
observationPatterns = "${spring.integration.management.observation-patterns:}")
protected static class EnableIntegrationManagementConfiguration {
}
}
/**
* Integration component scan configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(IntegrationComponentScanRegistrar.class)
@Import(IntegrationAutoConfigurationScanRegistrar.class)
protected static class IntegrationComponentScanConfiguration {
}
/**
* Integration JDBC configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JdbcMessageStore.class, DataSourceScriptDatabaseInitializer.class })
@ConditionalOnSingleCandidate(DataSource.class)
@Conditional(OnIntegrationDatasourceInitializationCondition.class)
protected static class IntegrationJdbcConfiguration {
@Bean
@ConditionalOnMissingBean
public IntegrationDataSourceScriptDatabaseInitializer integrationDataSourceInitializer(DataSource dataSource,
IntegrationProperties properties) {
return new IntegrationDataSourceScriptDatabaseInitializer(dataSource, properties.getJdbc());
}
}
/**
* Integration RSocket configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ IntegrationRSocketEndpoint.class, RSocketRequester.class, io.rsocket.RSocket.class })
@Conditional(IntegrationRSocketConfiguration.AnyRSocketChannelAdapterAvailable.class)
protected static class IntegrationRSocketConfiguration {
/**
* Check if either an {@link IntegrationRSocketEndpoint} or
* {@link RSocketOutboundGateway} bean is available.
*/
static class AnyRSocketChannelAdapterAvailable extends AnyNestedCondition {
AnyRSocketChannelAdapterAvailable() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(IntegrationRSocketEndpoint.class)
static class IntegrationRSocketEndpointAvailable {
}
@ConditionalOnBean(RSocketOutboundGateway.class)
static class RSocketOutboundGatewayAvailable {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(TcpServerTransport.class)
protected static class IntegrationRSocketServerConfiguration {
@Bean
@ConditionalOnMissingBean(ServerRSocketMessageHandler.class)
public RSocketMessageHandler serverRSocketMessageHandler(RSocketStrategies rSocketStrategies,
IntegrationProperties integrationProperties) {
RSocketMessageHandler messageHandler = new ServerRSocketMessageHandler(
integrationProperties.getRsocket().getServer().isMessageMappingEnabled());
messageHandler.setRSocketStrategies(rSocketStrategies);
return messageHandler;
}
@Bean
@ConditionalOnMissingBean
public ServerRSocketConnector serverRSocketConnector(ServerRSocketMessageHandler messageHandler) {
return new ServerRSocketConnector(messageHandler);
}
}
@Configuration(proxyBeanMethods = false)
protected static class IntegrationRSocketClientConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(RemoteRSocketServerAddressConfigured.class)
public ClientRSocketConnector clientRSocketConnector(IntegrationProperties integrationProperties,
RSocketStrategies rSocketStrategies) {
IntegrationProperties.RSocket.Client client = integrationProperties.getRsocket().getClient();
ClientRSocketConnector clientRSocketConnector = (client.getUri() != null)
? new ClientRSocketConnector(client.getUri())
: new ClientRSocketConnector(client.getHost(), client.getPort());
clientRSocketConnector.setRSocketStrategies(rSocketStrategies);
return clientRSocketConnector;
}
/**
* Check if a remote address is configured for the RSocket Integration client.
*/
static class RemoteRSocketServerAddressConfigured extends AnyNestedCondition {
RemoteRSocketServerAddressConfigured() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty("spring.integration.rsocket.client.uri")
static class WebSocketAddressConfigured {
}
@ConditionalOnProperty({ "spring.integration.rsocket.client.host",
"spring.integration.rsocket.client.port" })
static class TcpAddressConfigured {
}
}
}
}
static class OnIntegrationDatasourceInitializationCondition extends OnDatabaseInitializationCondition {
OnIntegrationDatasourceInitializationCondition() {
super("Integration", "spring.integration.jdbc.initialize-schema");
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2012-2024 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.integration.autoconfigure;
import java.util.Collection;
import java.util.Collections;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.config.IntegrationComponentScanRegistrar;
/**
* Variation of {@link IntegrationComponentScanRegistrar} the links
* {@link AutoConfigurationPackages}.
*
* @author Artem Bilan
* @author Phillip Webb
*/
class IntegrationAutoConfigurationScanRegistrar extends IntegrationComponentScanRegistrar implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
final BeanDefinitionRegistry registry) {
super.registerBeanDefinitions(AnnotationMetadata.introspect(IntegrationComponentScanConfiguration.class),
registry);
}
@Override
protected Collection<String> getBasePackages(AnnotationAttributes componentScan, BeanDefinitionRegistry registry) {
return (AutoConfigurationPackages.has(this.beanFactory) ? AutoConfigurationPackages.get(this.beanFactory)
: Collections.emptyList());
}
@IntegrationComponentScan
private static final class IntegrationComponentScanConfiguration {
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2012-2022 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.integration.autoconfigure;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.jdbc.init.PlatformPlaceholderDatabaseDriverResolver;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.util.StringUtils;
/**
* {@link DataSourceScriptDatabaseInitializer} for the Spring Integration database. May be
* registered as a bean to override auto-configuration.
*
* @author Vedran Pavic
* @author Andy Wilkinson
* @since 2.6.0
*/
public class IntegrationDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer {
/**
* Create a new {@link IntegrationDataSourceScriptDatabaseInitializer} instance.
* @param dataSource the Spring Integration data source
* @param properties the Spring Integration JDBC properties
* @see #getSettings
*/
public IntegrationDataSourceScriptDatabaseInitializer(DataSource dataSource,
IntegrationProperties.Jdbc properties) {
this(dataSource, getSettings(dataSource, properties));
}
/**
* Create a new {@link IntegrationDataSourceScriptDatabaseInitializer} instance.
* @param dataSource the Spring Integration data source
* @param settings the database initialization settings
* @see #getSettings
*/
public IntegrationDataSourceScriptDatabaseInitializer(DataSource dataSource,
DatabaseInitializationSettings settings) {
super(dataSource, settings);
}
/**
* Adapts {@link IntegrationProperties.Jdbc Spring Integration JDBC properties} to
* {@link DatabaseInitializationSettings} replacing any {@literal @@platform@@}
* placeholders.
* @param dataSource the Spring Integration data source
* @param properties the Spring Integration JDBC properties
* @return a new {@link DatabaseInitializationSettings} instance
* @see #IntegrationDataSourceScriptDatabaseInitializer(DataSource,
* DatabaseInitializationSettings)
*/
static DatabaseInitializationSettings getSettings(DataSource dataSource, IntegrationProperties.Jdbc properties) {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(resolveSchemaLocations(dataSource, properties));
settings.setMode(properties.getInitializeSchema());
settings.setContinueOnError(true);
return settings;
}
private static List<String> resolveSchemaLocations(DataSource dataSource, IntegrationProperties.Jdbc properties) {
PlatformPlaceholderDatabaseDriverResolver platformResolver = new PlatformPlaceholderDatabaseDriverResolver();
platformResolver = platformResolver.withDriverPlatform(DatabaseDriver.MARIADB, "mysql");
if (StringUtils.hasText(properties.getPlatform())) {
return platformResolver.resolveAll(properties.getPlatform(), properties.getSchema());
}
return platformResolver.resolveAll(dataSource, properties.getSchema());
}
}

View File

@@ -0,0 +1,458 @@
/*
* Copyright 2012-2025 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.integration.autoconfigure;
import java.net.URI;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
/**
* Configuration properties for Spring Integration.
*
* @author Vedran Pavic
* @author Stephane Nicoll
* @author Artem Bilan
* @since 2.0.0
*/
@ConfigurationProperties("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();
private final Poller poller = new Poller();
private final Management management = new Management();
public Channel getChannel() {
return this.channel;
}
public Endpoint getEndpoint() {
return this.endpoint;
}
public Error getError() {
return this.error;
}
public Jdbc getJdbc() {
return this.jdbc;
}
public RSocket getRsocket() {
return this.rsocket;
}
public Poller getPoller() {
return this.poller;
}
public Management getManagement() {
return this.management;
}
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;
/**
* List of message header names that should not be populated into Message
* instances during a header copying operation.
*/
private List<String> readOnlyHeaders = new ArrayList<>();
/**
* List of endpoint bean names patterns that should not be started automatically
* during application startup.
*/
private List<String> noAutoStartup = new ArrayList<>();
/**
* Default timeout for blocking operations such as sending or receiving messages.
*/
private Duration defaultTimeout = Duration.ofSeconds(30);
public void setThrowExceptionOnLateReply(boolean throwExceptionOnLateReply) {
this.throwExceptionOnLateReply = throwExceptionOnLateReply;
}
public boolean isThrowExceptionOnLateReply() {
return this.throwExceptionOnLateReply;
}
public List<String> getReadOnlyHeaders() {
return this.readOnlyHeaders;
}
public void setReadOnlyHeaders(List<String> readOnlyHeaders) {
this.readOnlyHeaders = readOnlyHeaders;
}
public List<String> getNoAutoStartup() {
return this.noAutoStartup;
}
public void setNoAutoStartup(List<String> noAutoStartup) {
this.noAutoStartup = noAutoStartup;
}
public Duration getDefaultTimeout() {
return this.defaultTimeout;
}
public void setDefaultTimeout(Duration defaultTimeout) {
this.defaultTimeout = defaultTimeout;
}
}
public static class Error {
/**
* Whether to not silently ignore messages on the global 'errorChannel' when there
* 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/"
+ "integration/jdbc/schema-@@platform@@.sql";
/**
* Path to the SQL file to use to initialize the database schema.
*/
private String schema = DEFAULT_SCHEMA_LOCATION;
/**
* Platform to use in initialization scripts if the @@platform@@ placeholder is
* used. Auto-detected by default.
*/
private String platform;
/**
* Database schema initialization mode.
*/
private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED;
public String getSchema() {
return this.schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public String getPlatform() {
return this.platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public DatabaseInitializationMode getInitializeSchema() {
return this.initializeSchema;
}
public void setInitializeSchema(DatabaseInitializationMode initializeSchema) {
this.initializeSchema = initializeSchema;
}
}
public static class RSocket {
private final Client client = new Client();
private final Server server = new Server();
public Client getClient() {
return this.client;
}
public Server getServer() {
return this.server;
}
public static class Client {
/**
* TCP RSocket server host to connect to.
*/
private String host;
/**
* TCP RSocket server port to connect to.
*/
private Integer port;
/**
* WebSocket RSocket server uri to connect to.
*/
private URI uri;
public void setHost(String host) {
this.host = host;
}
public String getHost() {
return this.host;
}
public void setPort(Integer port) {
this.port = port;
}
public Integer getPort() {
return this.port;
}
public void setUri(URI uri) {
this.uri = uri;
}
public URI getUri() {
return this.uri;
}
}
public static class Server {
/**
* Whether to handle message mapping for RSocket through Spring Integration.
*/
private boolean messageMappingEnabled;
public boolean isMessageMappingEnabled() {
return this.messageMappingEnabled;
}
public void setMessageMappingEnabled(boolean messageMappingEnabled) {
this.messageMappingEnabled = messageMappingEnabled;
}
}
}
public static class Poller {
/**
* Maximum number of messages to poll per polling cycle.
*/
private int maxMessagesPerPoll = Integer.MIN_VALUE; // PollerMetadata.MAX_MESSAGES_UNBOUNDED
/**
* How long to wait for messages on poll.
*/
private Duration receiveTimeout = Duration.ofSeconds(1); // PollerMetadata.DEFAULT_RECEIVE_TIMEOUT
/**
* Polling delay period. Mutually exclusive with 'cron' and 'fixedRate'.
*/
private Duration fixedDelay;
/**
* Polling rate period. Mutually exclusive with 'fixedDelay' and 'cron'.
*/
private Duration fixedRate;
/**
* Polling initial delay. Applied for 'fixedDelay' and 'fixedRate'; ignored for
* 'cron'.
*/
private Duration initialDelay;
/**
* Cron expression for polling. Mutually exclusive with 'fixedDelay' and
* 'fixedRate'.
*/
private String cron;
public int getMaxMessagesPerPoll() {
return this.maxMessagesPerPoll;
}
public void setMaxMessagesPerPoll(int maxMessagesPerPoll) {
this.maxMessagesPerPoll = maxMessagesPerPoll;
}
public Duration getReceiveTimeout() {
return this.receiveTimeout;
}
public void setReceiveTimeout(Duration receiveTimeout) {
this.receiveTimeout = receiveTimeout;
}
public Duration getFixedDelay() {
return this.fixedDelay;
}
public void setFixedDelay(Duration fixedDelay) {
this.fixedDelay = fixedDelay;
}
public Duration getFixedRate() {
return this.fixedRate;
}
public void setFixedRate(Duration fixedRate) {
this.fixedRate = fixedRate;
}
public Duration getInitialDelay() {
return this.initialDelay;
}
public void setInitialDelay(Duration initialDelay) {
this.initialDelay = initialDelay;
}
public String getCron() {
return this.cron;
}
public void setCron(String cron) {
this.cron = cron;
}
}
public static class Management {
/**
* Whether Spring Integration components should perform logging in the main
* message flow. When disabled, such logging will be skipped without checking the
* logging level. When enabled, such logging is controlled as normal by the
* logging system's log level configuration.
*/
private boolean defaultLoggingEnabled = true;
/**
* List of simple patterns to match against the names of Spring Integration
* components. When matched, observation instrumentation will be performed for the
* component. Please refer to the javadoc of the smartMatch method of Spring
* Integration's PatternMatchUtils for details of the pattern syntax.
*/
private List<String> observationPatterns = new ArrayList<>();
public boolean isDefaultLoggingEnabled() {
return this.defaultLoggingEnabled;
}
public void setDefaultLoggingEnabled(boolean defaultLoggingEnabled) {
this.defaultLoggingEnabled = defaultLoggingEnabled;
}
public List<String> getObservationPatterns() {
return this.observationPatterns;
}
public void setObservationPatterns(List<String> observationPatterns) {
this.observationPatterns = observationPatterns;
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2012-2024 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.integration.autoconfigure;
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<Map<String, Object>>
implements OriginLookup<String> {
private static final String PREFIX = "spring.integration.";
private static final Map<String, String> KEYS_MAPPING;
static {
Map<String, String> 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.default-timeout", IntegrationProperties.ENDPOINTS_DEFAULT_TIMEOUT);
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));
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012-2025 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.integration.autoconfigure;
import org.springframework.integration.scheduling.PollerMetadata;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link PollerMetadata} whilst retaining default auto-configuration.
*
* @author Yanming Zhou
* @since 3.5.0
*/
@FunctionalInterface
public interface PollerMetadataCustomizer {
/**
* Customize the {@link PollerMetadata}.
* @param pollerMetadata the {@code PollerMetadata} to customize
*/
void customize(PollerMetadata pollerMetadata);
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2019 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.
*/
/**
* Auto-configuration for Spring Integration.
*/
package org.springframework.boot.integration.autoconfigure;

View File

@@ -0,0 +1,3 @@
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.integration.autoconfigure.IntegrationPropertiesEnvironmentPostProcessor

View File

@@ -0,0 +1 @@
org.springframework.boot.integration.autoconfigure.IntegrationAutoConfiguration

View File

@@ -0,0 +1,697 @@
/*
* Copyright 2012-2025 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.integration.autoconfigure;
import java.beans.PropertyDescriptor;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import javax.management.MBeanServer;
import javax.sql.DataSource;
import io.micrometer.observation.ObservationRegistry;
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 org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import reactor.core.publisher.Mono;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration;
import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration;
import org.springframework.boot.integration.autoconfigure.IntegrationAutoConfiguration.IntegrationComponentScanConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.rsocket.autoconfigure.RSocketMessagingAutoConfiguration;
import org.springframework.boot.rsocket.autoconfigure.RSocketRequesterAutoConfiguration;
import org.springframework.boot.rsocket.autoconfigure.RSocketServerAutoConfiguration;
import org.springframework.boot.rsocket.autoconfigure.RSocketStrategiesAutoConfiguration;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.assertj.SimpleAsyncTaskExecutorAssert;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.IntegrationManagementConfigurer;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.endpoint.MessageProcessorMessageSource;
import org.springframework.integration.gateway.RequestReplyExchanger;
import org.springframework.integration.handler.BridgeHandler;
import org.springframework.integration.handler.LoggingHandler;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.jmx.config.EnableIntegrationMBeanExport;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
import org.springframework.integration.rsocket.ClientRSocketConnector;
import org.springframework.integration.rsocket.IntegrationRSocketEndpoint;
import org.springframework.integration.rsocket.ServerRSocketConnector;
import org.springframework.integration.rsocket.ServerRSocketMessageHandler;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.integration.support.channel.HeaderChannelRegistry;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link IntegrationAutoConfiguration}.
*
* @author Artem Bilan
* @author Stephane Nicoll
* @author Vedran Pavic
* @author Yong-Hyun Kim
* @author Yanming Zhou
*/
class IntegrationAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, IntegrationAutoConfiguration.class));
@Test
void integrationIsAvailable() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(TestGateway.class);
assertThat(context).hasSingleBean(IntegrationComponentScanConfiguration.class);
});
}
@Test
void explicitIntegrationComponentScan() {
this.contextRunner.withUserConfiguration(CustomIntegrationComponentScanConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(TestGateway.class);
assertThat(context).doesNotHaveBean(IntegrationComponentScanConfiguration.class);
});
}
@Test
void noMBeanServerAvailable() {
ApplicationContextRunner contextRunnerWithoutJmx = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(IntegrationAutoConfiguration.class));
contextRunnerWithoutJmx.run((context) -> {
assertThat(context).hasSingleBean(TestGateway.class);
assertThat(context).hasSingleBean(IntegrationComponentScanConfiguration.class);
});
}
@Test
void parentContext() {
this.contextRunner.run((context) -> this.contextRunner.withParent(context)
.withPropertyValues("spring.jmx.default_domain=org.foo")
.run((child) -> assertThat(child).hasSingleBean(HeaderChannelRegistry.class)));
}
@Test
void enableJmxIntegration() {
this.contextRunner.withPropertyValues("spring.jmx.enabled=true").run((context) -> {
MBeanServer mBeanServer = context.getBean(MBeanServer.class);
assertThat(mBeanServer.getDomains()).contains("org.springframework.integration",
"org.springframework.boot.integration.autoconfigure");
assertThat(context).hasBean(IntegrationManagementConfigurer.MANAGEMENT_CONFIGURER_NAME);
});
}
@Test
void jmxIntegrationIsDisabledByDefault() {
this.contextRunner.run((context) -> {
assertThat(context).doesNotHaveBean(MBeanServer.class);
assertThat(context).hasSingleBean(IntegrationManagementConfigurer.class);
});
}
@Test
void customizeJmxDomain() {
this.contextRunner.withPropertyValues("spring.jmx.enabled=true", "spring.jmx.default_domain=org.foo")
.run((context) -> {
MBeanServer mBeanServer = context.getBean(MBeanServer.class);
assertThat(mBeanServer.getDomains()).contains("org.foo")
.doesNotContain("org.springframework.integration", "org.springframework.integration.monitor");
});
}
@Test
void customJmxDomainUsingEnableIntegrationMBeanExport() {
this.contextRunner.withConfiguration(UserConfigurations.of(CustomJmxDomainConfiguration.class))
.withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, IntegrationAutoConfiguration.class))
.run((context) -> {
assertThat(context).hasSingleBean(IntegrationMBeanExporter.class);
IntegrationMBeanExporter exporter = context.getBean(IntegrationMBeanExporter.class);
assertThat(exporter).hasFieldOrPropertyWithValue("domain", "foo.my");
});
}
@Test
void primaryExporterIsAllowed() {
this.contextRunner.withPropertyValues("spring.jmx.enabled=true")
.withUserConfiguration(CustomMBeanExporter.class)
.run((context) -> {
assertThat(context).getBeans(MBeanExporter.class).hasSize(2);
assertThat(context.getBean(MBeanExporter.class)).isSameAs(context.getBean("myMBeanExporter"));
});
}
@Test
void integrationJdbcDataSourceInitializerEnabled() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceTransactionManagerAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class, IntegrationAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true",
"spring.integration.jdbc.initialize-schema=always")
.run((context) -> {
IntegrationProperties properties = context.getBean(IntegrationProperties.class);
assertThat(properties.getJdbc().getInitializeSchema()).isEqualTo(DatabaseInitializationMode.ALWAYS);
JdbcOperations jdbc = context.getBean(JdbcOperations.class);
assertThat(jdbc.queryForList("select * from INT_MESSAGE")).isEmpty();
assertThat(jdbc.queryForList("select * from INT_GROUP_TO_MESSAGE")).isEmpty();
assertThat(jdbc.queryForList("select * from INT_MESSAGE_GROUP")).isEmpty();
assertThat(jdbc.queryForList("select * from INT_LOCK")).isEmpty();
assertThat(jdbc.queryForList("select * from INT_CHANNEL_MESSAGE")).isEmpty();
});
}
@Test
void whenIntegrationJdbcDataSourceInitializerIsEnabledThenFlywayCanBeUsed() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceTransactionManagerAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class, IntegrationAutoConfiguration.class,
FlywayAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true",
"spring.integration.jdbc.initialize-schema=always")
.run((context) -> {
IntegrationProperties properties = context.getBean(IntegrationProperties.class);
assertThat(properties.getJdbc().getInitializeSchema()).isEqualTo(DatabaseInitializationMode.ALWAYS);
JdbcOperations jdbc = context.getBean(JdbcOperations.class);
assertThat(jdbc.queryForList("select * from INT_MESSAGE")).isEmpty();
assertThat(jdbc.queryForList("select * from INT_GROUP_TO_MESSAGE")).isEmpty();
assertThat(jdbc.queryForList("select * from INT_MESSAGE_GROUP")).isEmpty();
assertThat(jdbc.queryForList("select * from INT_LOCK")).isEmpty();
assertThat(jdbc.queryForList("select * from INT_CHANNEL_MESSAGE")).isEmpty();
});
}
@Test
void integrationJdbcDataSourceInitializerDisabled() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceTransactionManagerAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class, IntegrationAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true",
"spring.integration.jdbc.initialize-schema=never")
.run((context) -> {
assertThat(context).doesNotHaveBean(IntegrationDataSourceScriptDatabaseInitializer.class);
IntegrationProperties properties = context.getBean(IntegrationProperties.class);
assertThat(properties.getJdbc().getInitializeSchema()).isEqualTo(DatabaseInitializationMode.NEVER);
JdbcOperations jdbc = context.getBean(JdbcOperations.class);
assertThatExceptionOfType(BadSqlGrammarException.class)
.isThrownBy(() -> jdbc.queryForList("select * from INT_MESSAGE"));
});
}
@Test
void integrationJdbcDataSourceInitializerEnabledByDefaultWithEmbeddedDb() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceTransactionManagerAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class, IntegrationAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true")
.run((context) -> {
IntegrationProperties properties = context.getBean(IntegrationProperties.class);
assertThat(properties.getJdbc().getInitializeSchema()).isEqualTo(DatabaseInitializationMode.EMBEDDED);
JdbcOperations jdbc = context.getBean(JdbcOperations.class);
assertThat(jdbc.queryForList("select * from INT_MESSAGE")).isEmpty();
});
}
@Test
void rsocketSupportEnabled() {
this.contextRunner.withUserConfiguration(RSocketServerConfiguration.class)
.withConfiguration(AutoConfigurations.of(RSocketServerAutoConfiguration.class,
RSocketStrategiesAutoConfiguration.class, RSocketMessagingAutoConfiguration.class,
RSocketRequesterAutoConfiguration.class, IntegrationAutoConfiguration.class))
.withPropertyValues("spring.rsocket.server.port=0", "spring.integration.rsocket.client.port=0",
"spring.integration.rsocket.client.host=localhost",
"spring.integration.rsocket.server.message-mapping-enabled=true")
.run((context) -> {
assertThat(context).hasSingleBean(ClientRSocketConnector.class)
.hasBean("clientRSocketConnector")
.hasSingleBean(ServerRSocketConnector.class)
.hasSingleBean(ServerRSocketMessageHandler.class)
.hasSingleBean(RSocketMessageHandler.class);
ServerRSocketMessageHandler serverRSocketMessageHandler = context
.getBean(ServerRSocketMessageHandler.class);
assertThat(context).getBean(RSocketMessageHandler.class).isSameAs(serverRSocketMessageHandler);
ClientRSocketConnector clientRSocketConnector = context.getBean(ClientRSocketConnector.class);
ClientTransport clientTransport = (ClientTransport) new DirectFieldAccessor(clientRSocketConnector)
.getPropertyValue("clientTransport");
assertThat(clientTransport).isInstanceOf(TcpClientTransport.class);
});
}
@Test
void taskSchedulerIsNotOverridden() {
this.contextRunner.withConfiguration(AutoConfigurations.of(TaskSchedulingAutoConfiguration.class))
.withPropertyValues("spring.task.scheduling.thread-name-prefix=integration-scheduling-",
"spring.task.scheduling.pool.size=3")
.run((context) -> {
assertThat(context).hasSingleBean(TaskScheduler.class);
assertThat(context).getBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class)
.hasFieldOrPropertyWithValue("threadNamePrefix", "integration-scheduling-")
.hasFieldOrPropertyWithValue("scheduledExecutor.corePoolSize", 3);
});
}
@Test
void taskSchedulerCanBeCustomized() {
TaskScheduler customTaskScheduler = mock(TaskScheduler.class);
this.contextRunner.withConfiguration(AutoConfigurations.of(TaskSchedulingAutoConfiguration.class))
.withBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class, () -> customTaskScheduler)
.run((context) -> {
assertThat(context).hasSingleBean(TaskScheduler.class);
assertThat(context).getBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME)
.isSameAs(customTaskScheduler);
});
}
@Test
void integrationGlobalPropertiesAutoConfigured() {
String[] propertyValues = { "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.defaultTimeout=60s",
"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" };
assertThat(propertyValues).hasSameSizeAs(globalIntegrationPropertyNames());
this.contextRunner.withPropertyValues(propertyValues).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.getEndpointsDefaultTimeout()).isEqualTo(60000);
assertThat(integrationProperties.isMessagingTemplateThrowExceptionOnLateReply()).isTrue();
assertThat(integrationProperties.getReadOnlyHeaders()).containsOnly("ignoredHeader");
assertThat(integrationProperties.getNoAutoStartupEndpoints()).containsOnly("notStartedEndpoint",
"_org.springframework.integration.errorLogger");
});
}
@Test
void integrationGlobalPropertiesUseConsistentDefault() {
List<PropertyAccessor> properties = List
.of("isChannelsAutoCreate", "getChannelsMaxUnicastSubscribers", "getChannelsMaxBroadcastSubscribers",
"isErrorChannelRequireSubscribers", "isErrorChannelIgnoreFailures", "getEndpointsDefaultTimeout",
"isMessagingTemplateThrowExceptionOnLateReply", "getReadOnlyHeaders", "getNoAutoStartupEndpoints")
.stream()
.map(PropertyAccessor::new)
.toList();
assertThat(properties).hasSameSizeAs(globalIntegrationPropertyNames());
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);
properties.forEach((property) -> assertThat(property.get(integrationProperties))
.isEqualTo(property.get(defaultIntegrationProperties)));
});
}
private List<String> globalIntegrationPropertyNames() {
return Stream
.of(PropertyAccessorFactory
.forBeanPropertyAccess(new org.springframework.integration.context.IntegrationProperties())
.getPropertyDescriptors())
.map(PropertyDescriptor::getName)
.filter((name) -> !"class".equals(name))
.filter((name) -> !"taskSchedulerPoolSize".equals(name))
.toList();
}
@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
@WithResource(name = "META-INF/spring.integration.properties",
content = "spring.integration.endpoints.noAutoStartup=testService*")
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*");
});
}
@Test
void whenTheUserDefinesTheirOwnIntegrationDatabaseInitializerThenTheAutoConfiguredInitializerBacksOff() {
this.contextRunner.withUserConfiguration(CustomIntegrationDatabaseInitializerConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class))
.run((context) -> assertThat(context).hasSingleBean(IntegrationDataSourceScriptDatabaseInitializer.class)
.doesNotHaveBean("integrationDataSourceScriptDatabaseInitializer")
.hasBean("customInitializer"));
}
@Test
void whenTheUserDefinesTheirOwnDatabaseInitializerThenTheAutoConfiguredIntegrationInitializerRemains() {
this.contextRunner.withUserConfiguration(CustomDatabaseInitializerConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class))
.run((context) -> assertThat(context).hasSingleBean(IntegrationDataSourceScriptDatabaseInitializer.class)
.hasBean("customInitializer"));
}
@Test
void defaultPoller() {
this.contextRunner.withUserConfiguration(PollingConsumerConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(PollerMetadata.class);
PollerMetadata metadata = context.getBean(PollerMetadata.DEFAULT_POLLER, PollerMetadata.class);
assertThat(metadata.getMaxMessagesPerPoll()).isEqualTo(PollerMetadata.MAX_MESSAGES_UNBOUNDED);
assertThat(metadata.getReceiveTimeout()).isEqualTo(PollerMetadata.DEFAULT_RECEIVE_TIMEOUT);
assertThat(metadata.getTrigger()).isNull();
GenericMessage<String> testMessage = new GenericMessage<>("test");
context.getBean("testChannel", QueueChannel.class).send(testMessage);
assertThat(context.getBean("sink", BlockingQueue.class).poll(10, TimeUnit.SECONDS)).isSameAs(testMessage);
});
}
@Test
void whenCustomPollerPropertiesAreSetThenTheyAreReflectedInPollerMetadata() {
this.contextRunner.withUserConfiguration(PollingConsumerConfiguration.class)
.withPropertyValues("spring.integration.poller.cron=* * * ? * *",
"spring.integration.poller.max-messages-per-poll=1",
"spring.integration.poller.receive-timeout=10s")
.run((context) -> {
assertThat(context).hasSingleBean(PollerMetadata.class);
PollerMetadata metadata = context.getBean(PollerMetadata.DEFAULT_POLLER, PollerMetadata.class);
assertThat(metadata.getMaxMessagesPerPoll()).isOne();
assertThat(metadata.getReceiveTimeout()).isEqualTo(10000L);
assertThat(metadata.getTrigger()).asInstanceOf(InstanceOfAssertFactories.type(CronTrigger.class))
.satisfies((trigger) -> assertThat(trigger.getExpression()).isEqualTo("* * * ? * *"));
});
}
@Test
void whenPollerPropertiesForMultipleTriggerTypesAreSetThenRefreshFails() {
this.contextRunner
.withPropertyValues("spring.integration.poller.cron=* * * ? * *",
"spring.integration.poller.fixed-delay=1s")
.run((context) -> assertThat(context).hasFailed()
.getFailure()
.hasRootCauseExactlyInstanceOf(MutuallyExclusiveConfigurationPropertiesException.class)
.rootCause()
.asInstanceOf(InstanceOfAssertFactories.type(MutuallyExclusiveConfigurationPropertiesException.class))
.satisfies((ex) -> {
assertThat(ex.getConfiguredNames()).containsExactlyInAnyOrder("spring.integration.poller.cron",
"spring.integration.poller.fixed-delay");
assertThat(ex.getMutuallyExclusiveNames()).containsExactlyInAnyOrder(
"spring.integration.poller.cron", "spring.integration.poller.fixed-delay",
"spring.integration.poller.fixed-rate");
}));
}
@Test
void whenFixedDelayPollerPropertyIsSetThenItIsReflectedAsFixedDelayPropertyOfPeriodicTrigger() {
this.contextRunner.withUserConfiguration(PollingConsumerConfiguration.class)
.withPropertyValues("spring.integration.poller.fixed-delay=5000")
.run((context) -> {
assertThat(context).hasSingleBean(PollerMetadata.class);
PollerMetadata metadata = context.getBean(PollerMetadata.DEFAULT_POLLER, PollerMetadata.class);
assertThat(metadata.getTrigger()).asInstanceOf(InstanceOfAssertFactories.type(PeriodicTrigger.class))
.satisfies((trigger) -> {
assertThat(trigger.getPeriodDuration()).isEqualTo(Duration.ofSeconds(5));
assertThat(trigger.isFixedRate()).isFalse();
});
});
}
@Test
void whenFixedRatePollerPropertyIsSetThenItIsReflectedAsFixedRatePropertyOfPeriodicTrigger() {
this.contextRunner.withUserConfiguration(PollingConsumerConfiguration.class)
.withPropertyValues("spring.integration.poller.fixed-rate=5000")
.run((context) -> {
assertThat(context).hasSingleBean(PollerMetadata.class);
PollerMetadata metadata = context.getBean(PollerMetadata.DEFAULT_POLLER, PollerMetadata.class);
assertThat(metadata.getTrigger()).asInstanceOf(InstanceOfAssertFactories.type(PeriodicTrigger.class))
.satisfies((trigger) -> {
assertThat(trigger.getPeriodDuration()).isEqualTo(Duration.ofSeconds(5));
assertThat(trigger.isFixedRate()).isTrue();
});
});
}
@Test
void integrationManagementLoggingIsEnabledByDefault() {
this.contextRunner.withBean(DirectChannel.class, DirectChannel::new)
.run((context) -> assertThat(context).getBean(DirectChannel.class)
.extracting(DirectChannel::isLoggingEnabled)
.isEqualTo(true));
}
@Test
void integrationManagementLoggingCanBeDisabled() {
this.contextRunner.withPropertyValues("spring.integration.management.defaultLoggingEnabled=false")
.withBean(DirectChannel.class, DirectChannel::new)
.run((context) -> assertThat(context).getBean(DirectChannel.class)
.extracting(DirectChannel::isLoggingEnabled)
.isEqualTo(false));
}
@Test
void integrationManagementInstrumentedWithObservation() {
this.contextRunner.withPropertyValues("spring.integration.management.observation-patterns=testHandler")
.withBean("testHandler", LoggingHandler.class, () -> new LoggingHandler("warn"))
.withBean(ObservationRegistry.class, ObservationRegistry::create)
.withBean(BridgeHandler.class, BridgeHandler::new)
.run((context) -> {
assertThat(context).getBean("testHandler").extracting("observationRegistry").isNotNull();
assertThat(context).getBean(BridgeHandler.class)
.extracting("observationRegistry")
.isEqualTo(ObservationRegistry.NOOP);
});
}
@Test
@EnabledForJreRange(min = JRE.JAVA_21)
void integrationVirtualThreadsEnabled() {
this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true")
.withConfiguration(AutoConfigurations.of(TaskSchedulingAutoConfiguration.class))
.run((context) -> assertThat(context).hasSingleBean(TaskScheduler.class)
.getBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class)
.isInstanceOf(SimpleAsyncTaskScheduler.class)
.satisfies((taskScheduler) -> SimpleAsyncTaskExecutorAssert
.assertThat((SimpleAsyncTaskExecutor) taskScheduler)
.usesVirtualThreads()));
}
@Test
void pollerMetadataCanBeCustomizedViaPollerMetadataCustomizer() {
TaskExecutor taskExecutor = new SyncTaskExecutor();
this.contextRunner.withUserConfiguration(PollingConsumerConfiguration.class)
.withBean(PollerMetadataCustomizer.class,
() -> (pollerMetadata) -> pollerMetadata.setTaskExecutor(taskExecutor))
.run((context) -> {
assertThat(context).hasSingleBean(PollerMetadata.class);
PollerMetadata metadata = context.getBean(PollerMetadata.DEFAULT_POLLER, PollerMetadata.class);
assertThat(metadata.getTaskExecutor()).isSameAs(taskExecutor);
});
}
@Configuration(proxyBeanMethods = false)
static class CustomMBeanExporter {
@Bean
@Primary
MBeanExporter myMBeanExporter() {
return mock(MBeanExporter.class);
}
}
@Configuration(proxyBeanMethods = false)
@IntegrationComponentScan
static class CustomIntegrationComponentScanConfiguration {
}
@MessagingGateway
public interface TestGateway extends RequestReplyExchanger {
}
@Configuration(proxyBeanMethods = false)
static class MessageSourceConfiguration {
@Bean
MessageSource<?> myMessageSource() {
return new MessageProcessorMessageSource(mock(MessageProcessor.class));
}
}
@Configuration(proxyBeanMethods = false)
static class RSocketServerConfiguration {
@Bean
IntegrationRSocketEndpoint mockIntegrationRSocketEndpoint() {
return new IntegrationRSocketEndpoint() {
@Override
public Mono<Void> handleMessage(Message<?> message) {
return null;
}
@Override
public String[] getPath() {
return new String[] { "/rsocketTestPath" };
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class CustomIntegrationDatabaseInitializerConfiguration {
@Bean
IntegrationDataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource,
IntegrationProperties properties) {
return new IntegrationDataSourceScriptDatabaseInitializer(dataSource, properties.getJdbc());
}
}
@Configuration(proxyBeanMethods = false)
static class CustomDatabaseInitializerConfiguration {
@Bean
DataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource) {
return new DataSourceScriptDatabaseInitializer(dataSource, new DatabaseInitializationSettings());
}
}
@Configuration(proxyBeanMethods = false)
static class PollingConsumerConfiguration {
@Bean
QueueChannel testChannel() {
return new QueueChannel();
}
@Bean
BlockingQueue<Message<?>> sink() {
return new LinkedBlockingQueue<>();
}
@ServiceActivator(inputChannel = "testChannel")
@Bean
MessageHandler handler(BlockingQueue<Message<?>> sink) {
return sink::add;
}
}
static class PropertyAccessor {
private final String name;
PropertyAccessor(String name) {
this.name = name;
}
Object get(org.springframework.integration.context.IntegrationProperties properties) {
return ReflectionTestUtils.invokeMethod(properties, this.name);
}
@Override
public String toString() {
return this.name;
}
}
@Configuration(proxyBeanMethods = false)
@EnableIntegrationMBeanExport(defaultDomain = "foo.my")
static class CustomJmxDomainConfiguration {
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2023 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.integration.autoconfigure;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link IntegrationDataSourceScriptDatabaseInitializer}.
*
* @author Stephane Nicoll
*/
class IntegrationDataSourceScriptDatabaseInitializerTests {
@Test
void getSettingsWithPlatformDoesNotTouchDataSource() {
DataSource dataSource = mock(DataSource.class);
IntegrationProperties properties = new IntegrationProperties();
properties.getJdbc().setPlatform("test");
DatabaseInitializationSettings settings = IntegrationDataSourceScriptDatabaseInitializer.getSettings(dataSource,
properties.getJdbc());
assertThat(settings.getSchemaLocations())
.containsOnly("classpath:org/springframework/integration/jdbc/schema-test.sql");
then(dataSource).shouldHaveNoInteractions();
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2012-2025 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.integration.autoconfigure;
import java.io.FileNotFoundException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.boot.origin.TextResourceOrigin;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
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 org.springframework.integration.context.IntegrationProperties;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link IntegrationPropertiesEnvironmentPostProcessor}.
*
* @author Stephane Nicoll
*/
class IntegrationPropertiesEnvironmentPostProcessorTests {
@Test
@WithResource(name = "META-INF/spring.integration.properties",
content = "spring.integration.endpoints.noAutoStartup=testService*")
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
@WithResource(name = "META-INF/spring.integration.properties",
content = "spring.integration.endpoints.noAutoStartup=testService*")
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());
assertThatIllegalStateException()
.isThrownBy(() -> new IntegrationPropertiesEnvironmentPostProcessor()
.registerIntegrationPropertiesPropertySource(environment, unknown))
.withCauseInstanceOf(FileNotFoundException.class)
.withMessageContaining(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).isInstanceOf(OriginLookup.class);
OriginLookup<String> originLookup = (OriginLookup<String>) 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));
}
@Test
@SuppressWarnings("unchecked")
void hasMappingsForAllMappableProperties() throws Exception {
Class<?> propertySource = ClassUtils.forName("%s.IntegrationPropertiesPropertySource"
.formatted(IntegrationPropertiesEnvironmentPostProcessor.class.getName()), getClass().getClassLoader());
Map<String, String> mappings = (Map<String, String>) ReflectionTestUtils.getField(propertySource,
"KEYS_MAPPING");
assertThat(mappings.values()).containsExactlyInAnyOrderElementsOf(integrationPropertyNames());
}
private static List<String> integrationPropertyNames() {
List<String> propertiesToMap = new ArrayList<>();
ReflectionUtils.doWithFields(IntegrationProperties.class, (field) -> {
String value = (String) ReflectionUtils.getField(field, null);
if (value.startsWith(IntegrationProperties.INTEGRATION_PROPERTIES_PREFIX)
&& value.length() > IntegrationProperties.INTEGRATION_PROPERTIES_PREFIX.length()) {
propertiesToMap.add(value);
}
}, (field) -> Modifier.isStatic(field.getModifiers()) && field.getType().equals(String.class));
propertiesToMap.remove(IntegrationProperties.TASK_SCHEDULER_POOL_SIZE);
return propertiesToMap;
}
@MethodSource("mappedConfigurationProperties")
@ParameterizedTest
void mappedPropertiesExistOnBootsIntegrationProperties(String mapping) {
Bindable<org.springframework.boot.integration.autoconfigure.IntegrationProperties> bindable = Bindable
.of(org.springframework.boot.integration.autoconfigure.IntegrationProperties.class);
MockEnvironment environment = new MockEnvironment().withProperty(mapping,
(mapping.contains("max") || mapping.contains("timeout")) ? "1" : "true");
BindResult<org.springframework.boot.integration.autoconfigure.IntegrationProperties> result = Binder
.get(environment)
.bind("spring.integration", bindable);
assertThat(result.isBound()).isTrue();
}
@SuppressWarnings("unchecked")
private static Collection<String> mappedConfigurationProperties() {
try {
Class<?> propertySource = ClassUtils.forName("%s.IntegrationPropertiesPropertySource"
.formatted(IntegrationPropertiesEnvironmentPostProcessor.class.getName()), null);
Map<String, String> mappings = (Map<String, String>) ReflectionTestUtils.getField(propertySource,
"KEYS_MAPPING");
return mappings.keySet();
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private Consumer<Origin> 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);
};
}
}

View File

@@ -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