Always define connection details beans

Closes gh-34776
pull/34958/head
Andy Wilkinson 2 years ago
parent 466b81f13d
commit d4980ea993

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* 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.
@ -50,6 +50,12 @@ import org.springframework.context.annotation.Import;
@ConditionalOnEnabledTracing
public class ZipkinAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ZipkinConnectionDetails.class)
PropertiesZipkinConnectionDetails zipkinConnectionDetails(ZipkinProperties properties) {
return new PropertiesZipkinConnectionDetails(properties);
}
@Bean
@ConditionalOnMissingBean
public BytesEncoder<Span> spanBytesEncoder() {

@ -64,6 +64,25 @@ class ZipkinAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(BytesEncoder.class));
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesZipkinConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(ZipkinConnectionDetails.class, () -> new ZipkinConnectionDetails() {
@Override
public String getSpanEndpoint() {
return "http://localhost";
}
})
.run((context) -> assertThat(context).hasSingleBean(ZipkinConnectionDetails.class)
.doesNotHaveBean(PropertiesZipkinConnectionDetails.class));
}
@Configuration(proxyBeanMethods = false)
private static class CustomConfiguration {

@ -25,13 +25,12 @@ import java.util.List;
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public class PropertiesRabbitConnectionDetails implements RabbitConnectionDetails {
class PropertiesRabbitConnectionDetails implements RabbitConnectionDetails {
private final RabbitProperties properties;
public PropertiesRabbitConnectionDetails(RabbitProperties properties) {
PropertiesRabbitConnectionDetails(RabbitProperties properties) {
this.properties = properties;
}

@ -82,22 +82,24 @@ public class RabbitAutoConfiguration {
private final RabbitProperties properties;
private final RabbitConnectionDetails connectionDetails;
protected RabbitConnectionFactoryCreator(RabbitProperties properties,
ObjectProvider<RabbitConnectionDetails> connectionDetails) {
this.properties = properties;
this.connectionDetails = connectionDetails
.getIfAvailable(() -> new PropertiesRabbitConnectionDetails(properties));
}
@Bean
@ConditionalOnMissingBean(RabbitConnectionDetails.class)
RabbitConnectionDetails rabbitConnectionDetails() {
return new PropertiesRabbitConnectionDetails(this.properties);
}
@Bean
@ConditionalOnMissingBean
RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader,
ObjectProvider<CredentialsProvider> credentialsProvider,
RabbitConnectionDetails connectionDetails, ObjectProvider<CredentialsProvider> credentialsProvider,
ObjectProvider<CredentialsRefreshService> credentialsRefreshService) {
RabbitConnectionFactoryBeanConfigurer configurer = new RabbitConnectionFactoryBeanConfigurer(resourceLoader,
this.properties, this.connectionDetails);
this.properties, connectionDetails);
configurer.setCredentialsProvider(credentialsProvider.getIfUnique());
configurer.setCredentialsRefreshService(credentialsRefreshService.getIfUnique());
return configurer;
@ -105,10 +107,10 @@ public class RabbitAutoConfiguration {
@Bean
@ConditionalOnMissingBean
CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer(
CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer(RabbitConnectionDetails connectionDetails,
ObjectProvider<ConnectionNameStrategy> connectionNameStrategy) {
CachingConnectionFactoryConfigurer configurer = new CachingConnectionFactoryConfigurer(this.properties,
this.connectionDetails);
connectionDetails);
configurer.setConnectionNameStrategy(connectionNameStrategy.getIfUnique());
return configurer;
}

@ -84,13 +84,14 @@ public class CassandraAutoConfiguration {
private final CassandraProperties properties;
private final CassandraConnectionDetails connectionDetails;
CassandraAutoConfiguration(CassandraProperties properties,
ObjectProvider<CassandraConnectionDetails> connectionDetails) {
CassandraAutoConfiguration(CassandraProperties properties) {
this.properties = properties;
this.connectionDetails = connectionDetails
.getIfAvailable(() -> new PropertiesCassandraConnectionDetails(properties));
}
@Bean
@ConditionalOnMissingBean(CassandraConnectionDetails.class)
PropertiesCassandraConnectionDetails cassandraConnectionDetails() {
return new PropertiesCassandraConnectionDetails(this.properties);
}
@Bean
@ -104,24 +105,25 @@ public class CassandraAutoConfiguration {
@ConditionalOnMissingBean
@Scope("prototype")
public CqlSessionBuilder cassandraSessionBuilder(DriverConfigLoader driverConfigLoader,
CassandraConnectionDetails connectionDetails,
ObjectProvider<CqlSessionBuilderCustomizer> builderCustomizers) {
CqlSessionBuilder builder = CqlSession.builder().withConfigLoader(driverConfigLoader);
configureAuthentication(builder);
configureSsl(builder);
configureAuthentication(builder, connectionDetails);
configureSsl(builder, connectionDetails);
builder.withKeyspace(this.properties.getKeyspaceName());
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;
}
private void configureAuthentication(CqlSessionBuilder builder) {
String username = this.connectionDetails.getUsername();
private void configureAuthentication(CqlSessionBuilder builder, CassandraConnectionDetails connectionDetails) {
String username = connectionDetails.getUsername();
if (username != null) {
builder.withAuthCredentials(username, this.connectionDetails.getPassword());
builder.withAuthCredentials(username, connectionDetails.getPassword());
}
}
private void configureSsl(CqlSessionBuilder builder) {
if (this.connectionDetails instanceof PropertiesCassandraConnectionDetails && this.properties.isSsl()) {
private void configureSsl(CqlSessionBuilder builder, CassandraConnectionDetails connectionDetails) {
if (connectionDetails instanceof PropertiesCassandraConnectionDetails && this.properties.isSsl()) {
try {
builder.withSslContext(SSLContext.getDefault());
}
@ -133,18 +135,18 @@ public class CassandraAutoConfiguration {
@Bean(destroyMethod = "")
@ConditionalOnMissingBean
public DriverConfigLoader cassandraDriverConfigLoader(
public DriverConfigLoader cassandraDriverConfigLoader(CassandraConnectionDetails connectionDetails,
ObjectProvider<DriverConfigLoaderBuilderCustomizer> builderCustomizers) {
ProgrammaticDriverConfigLoaderBuilder builder = new DefaultProgrammaticDriverConfigLoaderBuilder(
() -> cassandraConfiguration(), DefaultDriverConfigLoader.DEFAULT_ROOT_PATH);
() -> cassandraConfiguration(connectionDetails), DefaultDriverConfigLoader.DEFAULT_ROOT_PATH);
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
private Config cassandraConfiguration() {
private Config cassandraConfiguration(CassandraConnectionDetails connectionDetails) {
ConfigFactory.invalidateCaches();
Config config = ConfigFactory.defaultOverrides();
config = config.withFallback(mapConfig());
config = config.withFallback(mapConfig(connectionDetails));
if (this.properties.getConfig() != null) {
config = config.withFallback(loadConfig(this.properties.getConfig()));
}
@ -162,24 +164,24 @@ public class CassandraAutoConfiguration {
}
}
private Config mapConfig() {
private Config mapConfig(CassandraConnectionDetails connectionDetails) {
CassandraDriverOptions options = new CassandraDriverOptions();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.properties.getSessionName())
.whenHasText()
.to((sessionName) -> options.add(DefaultDriverOption.SESSION_NAME, sessionName));
map.from(this.connectionDetails.getUsername())
map.from(connectionDetails.getUsername())
.to((value) -> options.add(DefaultDriverOption.AUTH_PROVIDER_USER_NAME, value)
.add(DefaultDriverOption.AUTH_PROVIDER_PASSWORD, this.connectionDetails.getPassword()));
.add(DefaultDriverOption.AUTH_PROVIDER_PASSWORD, connectionDetails.getPassword()));
map.from(this.properties::getCompression)
.to((compression) -> options.add(DefaultDriverOption.PROTOCOL_COMPRESSION, compression));
mapConnectionOptions(options);
mapPoolingOptions(options);
mapRequestOptions(options);
mapControlConnectionOptions(options);
map.from(mapContactPoints())
map.from(mapContactPoints(connectionDetails))
.to((contactPoints) -> options.add(DefaultDriverOption.CONTACT_POINTS, contactPoints));
map.from(this.connectionDetails.getLocalDatacenter())
map.from(connectionDetails.getLocalDatacenter())
.whenHasText()
.to((localDatacenter) -> options.add(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, localDatacenter));
return options.build();
@ -244,11 +246,8 @@ public class CassandraAutoConfiguration {
.to((timeout) -> options.add(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT, timeout));
}
private List<String> mapContactPoints() {
return this.connectionDetails.getContactPoints()
.stream()
.map((node) -> node.host() + ":" + node.port())
.toList();
private List<String> mapContactPoints(CassandraConnectionDetails connectionDetails) {
return connectionDetails.getContactPoints().stream().map((node) -> node.host() + ":" + node.port()).toList();
}
private static class CassandraDriverOptions {
@ -289,7 +288,7 @@ public class CassandraAutoConfiguration {
/**
* Adapts {@link CassandraProperties} to {@link CassandraConnectionDetails}.
*/
private static final class PropertiesCassandraConnectionDetails implements CassandraConnectionDetails {
static final class PropertiesCassandraConnectionDetails implements CassandraConnectionDetails {
private final CassandraProperties properties;

@ -69,34 +69,37 @@ public class CouchbaseAutoConfiguration {
private final CouchbaseProperties properties;
private final CouchbaseConnectionDetails connectionDetails;
CouchbaseAutoConfiguration(CouchbaseProperties properties,
ObjectProvider<CouchbaseConnectionDetails> connectionDetails) {
this.properties = properties;
this.connectionDetails = connectionDetails
.getIfAvailable(() -> new PropertiesCouchbaseConnectionDetails(properties));
}
@Bean
@ConditionalOnMissingBean(CouchbaseConnectionDetails.class)
PropertiesCouchbaseConnectionDetails couchbaseConnectionDetails() {
return new PropertiesCouchbaseConnectionDetails(this.properties);
}
@Bean
@ConditionalOnMissingBean
public ClusterEnvironment couchbaseClusterEnvironment(
public ClusterEnvironment couchbaseClusterEnvironment(CouchbaseConnectionDetails connectionDetails,
ObjectProvider<ClusterEnvironmentBuilderCustomizer> customizers) {
Builder builder = initializeEnvironmentBuilder();
Builder builder = initializeEnvironmentBuilder(connectionDetails);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
@Bean(destroyMethod = "disconnect")
@ConditionalOnMissingBean
public Cluster couchbaseCluster(ClusterEnvironment couchbaseClusterEnvironment) {
public Cluster couchbaseCluster(ClusterEnvironment couchbaseClusterEnvironment,
CouchbaseConnectionDetails connectionDetails) {
ClusterOptions options = ClusterOptions
.clusterOptions(this.connectionDetails.getUsername(), this.connectionDetails.getPassword())
.clusterOptions(connectionDetails.getUsername(), connectionDetails.getPassword())
.environment(couchbaseClusterEnvironment);
return Cluster.connect(this.connectionDetails.getConnectionString(), options);
return Cluster.connect(connectionDetails.getConnectionString(), options);
}
private ClusterEnvironment.Builder initializeEnvironmentBuilder() {
private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbaseConnectionDetails connectionDetails) {
ClusterEnvironment.Builder builder = ClusterEnvironment.builder();
Timeouts timeouts = this.properties.getEnv().getTimeouts();
builder.timeoutConfig((config) -> config.kvTimeout(timeouts.getKeyValue())
@ -112,7 +115,7 @@ public class CouchbaseAutoConfiguration {
builder.ioConfig((config) -> config.maxHttpConnections(io.getMaxEndpoints())
.numKvConnections(io.getMinEndpoints())
.idleHttpConnectionTimeout(io.getIdleHttpConnectionTimeout()));
if ((this.connectionDetails instanceof PropertiesCouchbaseConnectionDetails)
if ((connectionDetails instanceof PropertiesCouchbaseConnectionDetails)
&& this.properties.getEnv().getSsl().getEnabled()) {
builder.securityConfig((config) -> config.enableTls(true)
.trustManagerFactory(getTrustManagerFactory(this.properties.getEnv().getSsl())));

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* 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.
@ -21,9 +21,13 @@ import com.mongodb.client.MongoClient;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.autoconfigure.mongo.PropertiesMongoConnectionDetails;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
@ -53,4 +57,10 @@ import org.springframework.data.mongodb.gridfs.GridFsTemplate;
MongoDatabaseFactoryDependentConfiguration.class })
public class MongoDataAutoConfiguration {
@Bean
@ConditionalOnMissingBean(MongoConnectionDetails.class)
PropertiesMongoConnectionDetails mongoConnectionDetails(MongoProperties properties) {
return new PropertiesMongoConnectionDetails(properties);
}
}

@ -18,12 +18,10 @@ package org.springframework.boot.autoconfigure.data.mongo;
import com.mongodb.client.MongoClient;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.autoconfigure.mongo.PropertiesMongoConnectionDetails;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
@ -45,11 +43,8 @@ class MongoDatabaseFactoryConfiguration {
@Bean
MongoDatabaseFactorySupport<?> mongoDatabaseFactory(MongoClient mongoClient, MongoProperties properties,
ObjectProvider<MongoConnectionDetails> connectionDetails) {
return new SimpleMongoClientDatabaseFactory(mongoClient,
connectionDetails.getIfAvailable(() -> new PropertiesMongoConnectionDetails(properties))
.getConnectionString()
.getDatabase());
MongoConnectionDetails connectionDetails) {
return new SimpleMongoClientDatabaseFactory(mongoClient, connectionDetails.getConnectionString().getDatabase());
}
}

@ -20,14 +20,12 @@ import com.mongodb.ClientSessionOptions;
import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoDatabase;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails;
import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails.GridFs;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.autoconfigure.mongo.MongoProperties.Gridfs;
import org.springframework.boot.autoconfigure.mongo.PropertiesMongoConnectionDetails;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
@ -76,9 +74,7 @@ class MongoDatabaseFactoryDependentConfiguration {
@Bean
@ConditionalOnMissingBean(GridFsOperations.class)
GridFsTemplate gridFsTemplate(MongoProperties properties, MongoDatabaseFactory factory, MongoTemplate mongoTemplate,
ObjectProvider<MongoConnectionDetails> connectionDetailsProvider) {
MongoConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesMongoConnectionDetails(properties));
MongoConnectionDetails connectionDetails) {
return new GridFsTemplate(new GridFsMongoDatabaseFactory(factory, connectionDetails),
mongoTemplate.getConverter(),
(connectionDetails.getGridFs() != null) ? connectionDetails.getGridFs().getBucket() : null);

@ -26,7 +26,6 @@ import org.bson.codecs.Codec;
import org.bson.codecs.configuration.CodecRegistry;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -36,7 +35,6 @@ import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails;
import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails.GridFs;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.PropertiesMongoConnectionDetails;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
@ -80,10 +78,8 @@ public class MongoReactiveDataAutoConfiguration {
private final MongoConnectionDetails connectionDetails;
MongoReactiveDataAutoConfiguration(MongoProperties properties,
ObjectProvider<MongoConnectionDetails> connectionDetails) {
this.connectionDetails = connectionDetails
.getIfAvailable(() -> new PropertiesMongoConnectionDetails(properties));
MongoReactiveDataAutoConfiguration(MongoConnectionDetails connectionDetails) {
this.connectionDetails = connectionDetails;
}
@Bean

@ -55,10 +55,9 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration {
JedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration,
ObjectProvider<RedisConnectionDetails> connectionDetailsProvider) {
super(properties, standaloneConfigurationProvider, sentinelConfiguration, clusterConfiguration,
connectionDetailsProvider);
ObjectProvider<RedisClusterConfiguration> clusterConfiguration, RedisConnectionDetails connectionDetails) {
super(properties, connectionDetails, standaloneConfigurationProvider, sentinelConfiguration,
clusterConfiguration);
}
@Bean

@ -64,9 +64,9 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
ObjectProvider<RedisConnectionDetails> connectionDetailsProvider) {
super(properties, standaloneConfigurationProvider, sentinelConfigurationProvider, clusterConfigurationProvider,
connectionDetailsProvider);
RedisConnectionDetails connectionDetails) {
super(properties, connectionDetails, standaloneConfigurationProvider, sentinelConfigurationProvider,
clusterConfigurationProvider);
}
@Bean(destroyMethod = "shutdown")

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* 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.
@ -49,6 +49,12 @@ import org.springframework.data.redis.core.StringRedisTemplate;
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RedisConnectionDetails.class)
PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties) {
return new PropertiesRedisConnectionDetails(properties);
}
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)

@ -60,17 +60,15 @@ abstract class RedisConnectionConfiguration {
private final RedisConnectionDetails connectionDetails;
protected RedisConnectionConfiguration(RedisProperties properties,
protected RedisConnectionConfiguration(RedisProperties properties, RedisConnectionDetails connectionDetails,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
ObjectProvider<RedisConnectionDetails> connectionDetailsProvider) {
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
this.properties = properties;
this.standaloneConfiguration = standaloneConfigurationProvider.getIfAvailable();
this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
this.connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesRedisConnectionDetails(properties));
this.connectionDetails = connectionDetails;
}
protected final RedisStandaloneConfiguration getStandaloneConfig() {

@ -62,24 +62,27 @@ class ElasticsearchRestClientConfigurations {
private final ElasticsearchProperties properties;
private final ElasticsearchConnectionDetails connectionDetails;
RestClientBuilderConfiguration(ElasticsearchProperties properties,
ObjectProvider<ElasticsearchConnectionDetails> connectionDetails) {
this.properties = properties;
this.connectionDetails = connectionDetails
.getIfAvailable(() -> new PropertiesElasticsearchConnectionDetails(properties));
}
@Bean
RestClientBuilderCustomizer defaultRestClientBuilderCustomizer() {
return new DefaultRestClientBuilderCustomizer(this.properties, this.connectionDetails);
@ConditionalOnMissingBean(ElasticsearchConnectionDetails.class)
PropertiesElasticsearchConnectionDetails elasticsearchConnectionDetails() {
return new PropertiesElasticsearchConnectionDetails(this.properties);
}
@Bean
RestClientBuilderCustomizer defaultRestClientBuilderCustomizer(
ElasticsearchConnectionDetails connectionDetails) {
return new DefaultRestClientBuilderCustomizer(this.properties, connectionDetails);
}
@Bean
RestClientBuilder elasticsearchRestClientBuilder(
RestClientBuilder elasticsearchRestClientBuilder(ElasticsearchConnectionDetails connectionDetails,
ObjectProvider<RestClientBuilderCustomizer> builderCustomizers) {
RestClientBuilder builder = RestClient.builder(this.connectionDetails.getNodes()
RestClientBuilder builder = RestClient.builder(connectionDetails.getNodes()
.stream()
.map((node) -> new HttpHost(node.hostname(), node.port(), node.protocol().getScheme()))
.toArray(HttpHost[]::new));
@ -91,7 +94,7 @@ class ElasticsearchRestClientConfigurations {
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(requestConfigBuilder));
return requestConfigBuilder;
});
String pathPrefix = this.connectionDetails.getPathPrefix();
String pathPrefix = connectionDetails.getPathPrefix();
if (pathPrefix != null) {
builder.setPathPrefix(pathPrefix);
}
@ -212,7 +215,7 @@ class ElasticsearchRestClientConfigurations {
/**
* Adapts {@link ElasticsearchProperties} to {@link ElasticsearchConnectionDetails}.
*/
private static class PropertiesElasticsearchConnectionDetails implements ElasticsearchConnectionDetails {
static class PropertiesElasticsearchConnectionDetails implements ElasticsearchConnectionDetails {
private final ElasticsearchProperties properties;

@ -24,6 +24,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.sql.DataSource;
@ -121,27 +122,31 @@ public class FlywayAutoConfiguration {
return new ResourceProviderCustomizer();
}
@Bean
@ConditionalOnMissingBean(FlywayConnectionDetails.class)
PropertiesFlywayConnectionDetails flywayConnectionDetails(FlywayProperties properties,
ObjectProvider<JdbcConnectionDetails> jdbcConnectionDetails) {
return new PropertiesFlywayConnectionDetails(properties, jdbcConnectionDetails.getIfAvailable());
}
@Deprecated(since = "3.0.0", forRemoval = true)
public Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader,
ObjectProvider<DataSource> dataSource, ObjectProvider<DataSource> flywayDataSource,
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
return flyway(properties, resourceLoader, dataSource, flywayDataSource, fluentConfigurationCustomizers,
javaMigrations, callbacks, new ResourceProviderCustomizer(), null);
return flyway(properties, new PropertiesFlywayConnectionDetails(properties, null), resourceLoader,
dataSource, flywayDataSource, fluentConfigurationCustomizers, javaMigrations, callbacks,
new ResourceProviderCustomizer());
}
@Bean
Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource,
Flyway flyway(FlywayProperties properties, FlywayConnectionDetails connectionDetails,
ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource,
@FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks,
ResourceProviderCustomizer resourceProviderCustomizer,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
ResourceProviderCustomizer resourceProviderCustomizer) {
FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
JdbcConnectionDetails connectionDetails = (connectionDetailsProvider != null)
? connectionDetailsProvider.getIfAvailable() : null;
connectionDetails = (connectionDetails != null) ? connectionDetails
: new FlywayPropertiesJdbcConnectionDetails(properties);
configureDataSource(configuration, flywayDataSource.getIfAvailable(), dataSource.getIfUnique(),
connectionDetails);
configureProperties(configuration, properties);
@ -153,13 +158,13 @@ public class FlywayAutoConfiguration {
}
private void configureDataSource(FluentConfiguration configuration, DataSource flywayDataSource,
DataSource dataSource, JdbcConnectionDetails connectionDetails) {
DataSource dataSource, FlywayConnectionDetails connectionDetails) {
DataSource migrationDataSource = getMigrationDataSource(flywayDataSource, dataSource, connectionDetails);
configuration.dataSource(migrationDataSource);
}
private DataSource getMigrationDataSource(DataSource flywayDataSource, DataSource dataSource,
JdbcConnectionDetails connectionDetails) {
FlywayConnectionDetails connectionDetails) {
if (flywayDataSource != null) {
return flywayDataSource;
}
@ -181,7 +186,7 @@ public class FlywayAutoConfiguration {
return dataSource;
}
private void applyConnectionDetails(JdbcConnectionDetails connectionDetails, DataSourceBuilder<?> builder) {
private void applyConnectionDetails(FlywayConnectionDetails connectionDetails, DataSourceBuilder<?> builder) {
builder.username(connectionDetails.getUsername());
builder.password(connectionDetails.getPassword());
String driverClassName = connectionDetails.getDriverClassName();
@ -406,34 +411,46 @@ public class FlywayAutoConfiguration {
}
/**
* Adapts {@link FlywayProperties} to {@link JdbcConnectionDetails}.
* Adapts {@link FlywayProperties} to {@link FlywayConnectionDetails}, using
* {@link JdbcConnectionDetails} as a fallback when Flyway-specific properties have
* not be configured.
*/
private static final class FlywayPropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
static final class PropertiesFlywayConnectionDetails implements FlywayConnectionDetails {
private final JdbcConnectionDetails fallback;
private final FlywayProperties properties;
private FlywayPropertiesJdbcConnectionDetails(FlywayProperties properties) {
PropertiesFlywayConnectionDetails(FlywayProperties properties, JdbcConnectionDetails fallback) {
this.fallback = fallback;
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.getUser();
return get(this.properties.getUser(), JdbcConnectionDetails::getUsername);
}
@Override
public String getPassword() {
return this.properties.getPassword();
return get(this.properties.getPassword(), JdbcConnectionDetails::getPassword);
}
@Override
public String getJdbcUrl() {
return this.properties.getUrl();
return get(this.properties.getUrl(), JdbcConnectionDetails::getJdbcUrl);
}
@Override
public String getDriverClassName() {
return this.properties.getDriverClassName();
return get(this.properties.getDriverClassName(), JdbcConnectionDetails::getDriverClassName);
}
private String get(String primary, Function<JdbcConnectionDetails, String> fallbackProperty) {
if (primary != null) {
return primary;
}
return (this.fallback != null) ? fallbackProperty.apply(this.fallback) : null;
}
}

@ -0,0 +1,60 @@
/*
* 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.autoconfigure.flyway;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.boot.jdbc.DatabaseDriver;
/**
* Details required for Flyway to establish a connection to an SQL service using JDBC.
*
* @author Andy Wilkinson
* @since 3.1.0
*/
public interface FlywayConnectionDetails extends ConnectionDetails {
/**
* Username for the database.
* @return the username for the database
*/
String getUsername();
/**
* Password for the database.
* @return the password for the database
*/
String getPassword();
/**
* JDBC URL for the database.
* @return the JDBC URL for the database
*/
String getJdbcUrl();
/**
* The name of the JDBC driver class. Defaults to the class name of the driver
* specified in the JDBC URL.
* @return the JDBC driver class name
* @see #getJdbcUrl()
* @see DatabaseDriver#fromJdbcUrl(String)
* @see DatabaseDriver#getDriverClassName()
*/
default String getDriverClassName() {
return DatabaseDriver.fromJdbcUrl(getJdbcUrl()).getDriverClassName();
}
}

@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
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.influx.InfluxDbAutoConfiguration.InfluxDBCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
@ -48,17 +49,21 @@ import org.springframework.context.annotation.Conditional;
*/
@AutoConfiguration
@ConditionalOnClass(InfluxDB.class)
@Conditional(InfluxDBCondition.class)
@EnableConfigurationProperties(InfluxDbProperties.class)
public class InfluxDbAutoConfiguration {
@Bean
@ConditionalOnMissingBean(InfluxDbConnectionDetails.class)
PropertiesInfluxDbConnectionDetails influxDbConnectionDetails(InfluxDbProperties properties) {
return new PropertiesInfluxDbConnectionDetails(properties);
}
@Bean
@ConditionalOnMissingBean
@Conditional(InfluxDBCondition.class)
public InfluxDB influxDb(InfluxDbProperties properties, ObjectProvider<InfluxDbOkHttpClientBuilderProvider> builder,
ObjectProvider<InfluxDbCustomizer> customizers,
ObjectProvider<InfluxDbConnectionDetails> connectionDetailsProvider) {
InfluxDbConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesInfluxDbConnectionDetails(properties));
public InfluxDB influxDb(InfluxDbConnectionDetails connectionDetails,
ObjectProvider<InfluxDbOkHttpClientBuilderProvider> builder,
ObjectProvider<InfluxDbCustomizer> customizers) {
InfluxDB influxDb = new InfluxDBImpl(connectionDetails.getUrl().toString(), connectionDetails.getUsername(),
connectionDetails.getPassword(), determineBuilder(builder.getIfAvailable()));
customizers.orderedStream().forEach((customizer) -> customizer.customize(influxDb));

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* 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.
@ -33,6 +33,7 @@ import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConf
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
@ -75,6 +76,12 @@ public class DataSourceAutoConfiguration {
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
@Bean
@ConditionalOnMissingBean(JdbcConnectionDetails.class)
PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
return new PropertiesJdbcConnectionDetails(properties);
}
}
/**

@ -25,7 +25,6 @@ import oracle.jdbc.OracleConnection;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -48,20 +47,15 @@ import org.springframework.util.StringUtils;
*/
abstract class DataSourceConfiguration {
@SuppressWarnings("unchecked")
private static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
@SuppressWarnings("unchecked")
private static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type,
ClassLoader classLoader) {
return (T) DataSourceBuilder.create(classLoader)
.type(type)
.driverClassName(connectionDetails.getDriverClassName())
.url(connectionDetails.getJdbcUrl())
.username(connectionDetails.getUsername())
.password(connectionDetails.getPassword())
.driverClassName(connectionDetails.getDriverClassName())
.type(type)
.build();
}
@ -76,7 +70,7 @@ abstract class DataSourceConfiguration {
static class Tomcat {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
@ConditionalOnMissingBean(PropertiesJdbcConnectionDetails.class)
static TomcatJdbcConnectionDetailsBeanPostProcessor tomcatJdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new TomcatJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
@ -85,15 +79,12 @@ abstract class DataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.tomcat")
org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
JdbcConnectionDetails connectionDetails) {
Class<? extends DataSource> dataSourceType = org.apache.tomcat.jdbc.pool.DataSource.class;
org.apache.tomcat.jdbc.pool.DataSource dataSource = (connectionDetails != null)
? createDataSource(connectionDetails, dataSourceType, properties.getClassLoader())
: createDataSource(properties, dataSourceType);
org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(connectionDetails, dataSourceType,
properties.getClassLoader());
String validationQuery;
String url = (connectionDetails != null) ? connectionDetails.getJdbcUrl() : properties.determineUrl();
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(url);
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(connectionDetails.getJdbcUrl());
validationQuery = databaseDriver.getValidationQuery();
if (validationQuery != null) {
dataSource.setTestOnBorrow(true);
@ -115,7 +106,6 @@ abstract class DataSourceConfiguration {
static class Hikari {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static HikariJdbcConnectionDetailsBeanPostProcessor jdbcConnectionDetailsHikariBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new HikariJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
@ -123,12 +113,9 @@ abstract class DataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
HikariDataSource dataSource = (connectionDetails != null)
? createDataSource(connectionDetails, HikariDataSource.class, properties.getClassLoader())
: createDataSource(properties, HikariDataSource.class);
HikariDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
HikariDataSource dataSource = createDataSource(connectionDetails, HikariDataSource.class,
properties.getClassLoader());
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
@ -148,7 +135,6 @@ abstract class DataSourceConfiguration {
static class Dbcp2 {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static Dbcp2JdbcConnectionDetailsBeanPostProcessor dbcp2JdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new Dbcp2JdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
@ -157,12 +143,9 @@ abstract class DataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.dbcp2")
org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
JdbcConnectionDetails connectionDetails) {
Class<? extends DataSource> dataSourceType = org.apache.commons.dbcp2.BasicDataSource.class;
return (connectionDetails != null)
? createDataSource(connectionDetails, dataSourceType, properties.getClassLoader())
: createDataSource(properties, dataSourceType);
return createDataSource(connectionDetails, dataSourceType, properties.getClassLoader());
}
}
@ -178,7 +161,6 @@ abstract class DataSourceConfiguration {
static class OracleUcp {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static OracleUcpJdbcConnectionDetailsBeanPostProcessor oracleUcpJdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new OracleUcpJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
@ -186,12 +168,10 @@ abstract class DataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.oracleucp")
PoolDataSourceImpl dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) throws SQLException {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
PoolDataSourceImpl dataSource = (connectionDetails != null)
? createDataSource(connectionDetails, PoolDataSourceImpl.class, properties.getClassLoader())
: createDataSource(properties, PoolDataSourceImpl.class);
PoolDataSourceImpl dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails)
throws SQLException {
PoolDataSourceImpl dataSource = createDataSource(connectionDetails, PoolDataSourceImpl.class,
properties.getClassLoader());
dataSource.setValidateConnectionOnBorrow(true);
if (StringUtils.hasText(properties.getName())) {
dataSource.setConnectionPoolName(properties.getName());
@ -210,14 +190,9 @@ abstract class DataSourceConfiguration {
static class Generic {
@Bean
DataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
if (connectionDetails != null) {
DataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
return createDataSource(connectionDetails, properties.getType(), properties.getClassLoader());
}
return properties.initializeDataSourceBuilder().build();
}
}

@ -30,7 +30,7 @@ import org.springframework.boot.jdbc.DatabaseDriver;
public interface JdbcConnectionDetails extends ConnectionDetails {
/**
* Hostname for the database.
* Username for the database.
* @return the username for the database
*/
String getUsername();

@ -24,7 +24,10 @@ import org.springframework.core.PriorityOrdered;
/**
* Abstract base class for DataSource bean post processors which apply values from
* {@link JdbcConnectionDetails}. Acts on beans named 'dataSource' of type {@code T}.
* {@link JdbcConnectionDetails}. Property-based connection details
* ({@link PropertiesJdbcConnectionDetails} are ignored as the expectation is that they
* will have already been applied by configuration property binding. Acts on beans named
* 'dataSource' of type {@code T}.
*
* @param <T> type of the datasource
* @author Moritz Halbritter
@ -48,8 +51,10 @@ abstract class JdbcConnectionDetailsBeanPostProcessor<T> implements BeanPostProc
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (this.dataSourceClass.isAssignableFrom(bean.getClass()) && "dataSource".equals(beanName)) {
JdbcConnectionDetails connectionDetails = this.connectionDetailsProvider.getObject();
if (!(connectionDetails instanceof PropertiesJdbcConnectionDetails)) {
return processDataSource((T) bean, connectionDetails);
}
}
return bean;
}

@ -0,0 +1,59 @@
/*
* 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.autoconfigure.jdbc;
/**
* Adapts {@link DataSourceProperties} to {@link JdbcConnectionDetails}.
*
* @author Andy Wilkinson
*/
final class PropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
private final DataSourceProperties properties;
PropertiesJdbcConnectionDetails(DataSourceProperties properties) {
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.determineUsername();
}
@Override
public String getPassword() {
return this.properties.determinePassword();
}
@Override
public String getJdbcUrl() {
return this.properties.determineUrl();
}
@Override
public String getDriverClassName() {
return this.properties.determineDriverClassName();
}
@Override
public String getXaDataSourceClassName() {
return (this.properties.getXa().getDataSourceClassName() != null)
? this.properties.getXa().getDataSourceClassName()
: JdbcConnectionDetails.super.getXaDataSourceClassName();
}
}

@ -66,12 +66,17 @@ public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
private ClassLoader classLoader;
@Bean
@ConditionalOnMissingBean(JdbcConnectionDetails.class)
PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
return new PropertiesJdbcConnectionDetails(properties);
}
@Bean
public DataSource dataSource(XADataSourceWrapper wrapper, DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetails, ObjectProvider<XADataSource> xaDataSource)
throws Exception {
return wrapper.wrapDataSource(xaDataSource.getIfAvailable(() -> createXaDataSource(properties,
connectionDetails.getIfAvailable(() -> new PropertiesJdbcConnectionDetails(properties)))));
JdbcConnectionDetails connectionDetails, ObjectProvider<XADataSource> xaDataSource) throws Exception {
return wrapper
.wrapDataSource(xaDataSource.getIfAvailable(() -> createXaDataSource(properties, connectionDetails)));
}
@Override
@ -122,45 +127,4 @@ public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
return source.withAliases(aliases);
}
/**
* Adapts {@link DataSourceProperties} to {@link JdbcConnectionDetails}.
*/
private static class PropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
private final DataSourceProperties properties;
PropertiesJdbcConnectionDetails(DataSourceProperties properties) {
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.determineUsername();
}
@Override
public String getPassword() {
return this.properties.determinePassword();
}
@Override
public String getJdbcUrl() {
return this.properties.determineUrl();
}
@Override
public String getDriverClassName() {
return (this.properties.getDriverClassName() != null) ? this.properties.getDriverClassName()
: JdbcConnectionDetails.super.getDriverClassName();
}
@Override
public String getXaDataSourceClassName() {
return (this.properties.getXa().getDataSourceClassName() != null)
? this.properties.getXa().getDataSourceClassName()
: JdbcConnectionDetails.super.getXaDataSourceClassName();
}
}
}

@ -76,12 +76,14 @@ public class KafkaAutoConfiguration {
private final KafkaProperties properties;
private final KafkaConnectionDetails connectionDetails;
KafkaAutoConfiguration(KafkaProperties properties, ObjectProvider<KafkaConnectionDetails> connectionDetails) {
KafkaAutoConfiguration(KafkaProperties properties) {
this.properties = properties;
this.connectionDetails = connectionDetails
.getIfAvailable(() -> new PropertiesKafkaConnectionDetails(properties));
}
@Bean
@ConditionalOnMissingBean(KafkaConnectionDetails.class)
PropertiesKafkaConnectionDetails kafkaConnectionDetails(KafkaProperties properties) {
return new PropertiesKafkaConnectionDetails(properties);
}
@Bean
@ -106,10 +108,10 @@ public class KafkaAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ConsumerFactory.class)
public DefaultKafkaConsumerFactory<?, ?> kafkaConsumerFactory(
public DefaultKafkaConsumerFactory<?, ?> kafkaConsumerFactory(KafkaConnectionDetails connectionDetails,
ObjectProvider<DefaultKafkaConsumerFactoryCustomizer> customizers) {
Map<String, Object> properties = this.properties.buildConsumerProperties();
applyKafkaConnectionDetailsForConsumer(properties);
applyKafkaConnectionDetailsForConsumer(properties, connectionDetails);
DefaultKafkaConsumerFactory<Object, Object> factory = new DefaultKafkaConsumerFactory<>(properties);
customizers.orderedStream().forEach((customizer) -> customizer.customize(factory));
return factory;
@ -117,10 +119,10 @@ public class KafkaAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ProducerFactory.class)
public DefaultKafkaProducerFactory<?, ?> kafkaProducerFactory(
public DefaultKafkaProducerFactory<?, ?> kafkaProducerFactory(KafkaConnectionDetails connectionDetails,
ObjectProvider<DefaultKafkaProducerFactoryCustomizer> customizers) {
Map<String, Object> properties = this.properties.buildProducerProperties();
applyKafkaConnectionDetailsForProducer(properties);
applyKafkaConnectionDetailsForProducer(properties, connectionDetails);
DefaultKafkaProducerFactory<?, ?> factory = new DefaultKafkaProducerFactory<>(properties);
String transactionIdPrefix = this.properties.getProducer().getTransactionIdPrefix();
if (transactionIdPrefix != null) {
@ -155,9 +157,9 @@ public class KafkaAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public KafkaAdmin kafkaAdmin() {
public KafkaAdmin kafkaAdmin(KafkaConnectionDetails connectionDetails) {
Map<String, Object> properties = this.properties.buildAdminProperties();
applyKafkaConnectionDetailsForAdmin(properties);
applyKafkaConnectionDetailsForAdmin(properties, connectionDetails);
KafkaAdmin kafkaAdmin = new KafkaAdmin(properties);
KafkaProperties.Admin admin = this.properties.getAdmin();
if (admin.getCloseTimeout() != null) {
@ -186,26 +188,29 @@ public class KafkaAutoConfiguration {
return builder.create(kafkaTemplate);
}
private void applyKafkaConnectionDetailsForConsumer(Map<String, Object> properties) {
private void applyKafkaConnectionDetailsForConsumer(Map<String, Object> properties,
KafkaConnectionDetails connectionDetails) {
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
nodesToStringList(this.connectionDetails.getConsumerBootstrapNodes()));
if (!(this.connectionDetails instanceof PropertiesKafkaConnectionDetails)) {
nodesToStringList(connectionDetails.getConsumerBootstrapNodes()));
if (!(connectionDetails instanceof PropertiesKafkaConnectionDetails)) {
properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
}
}
private void applyKafkaConnectionDetailsForProducer(Map<String, Object> properties) {
private void applyKafkaConnectionDetailsForProducer(Map<String, Object> properties,
KafkaConnectionDetails connectionDetails) {
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
nodesToStringList(this.connectionDetails.getProducerBootstrapNodes()));
if (!(this.connectionDetails instanceof PropertiesKafkaConnectionDetails)) {
nodesToStringList(connectionDetails.getProducerBootstrapNodes()));
if (!(connectionDetails instanceof PropertiesKafkaConnectionDetails)) {
properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
}
}
private void applyKafkaConnectionDetailsForAdmin(Map<String, Object> properties) {
private void applyKafkaConnectionDetailsForAdmin(Map<String, Object> properties,
KafkaConnectionDetails connectionDetails) {
properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
nodesToStringList(this.connectionDetails.getAdminBootstrapNodes()));
if (!(this.connectionDetails instanceof PropertiesKafkaConnectionDetails)) {
nodesToStringList(connectionDetails.getAdminBootstrapNodes()));
if (!(connectionDetails instanceof PropertiesKafkaConnectionDetails)) {
properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
}
}

@ -63,11 +63,9 @@ class KafkaStreamsAnnotationDrivenConfiguration {
@ConditionalOnMissingBean
@Bean(KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
KafkaStreamsConfiguration defaultKafkaStreamsConfig(Environment environment,
ObjectProvider<KafkaConnectionDetails> connectionDetailsProvider) {
KafkaConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesKafkaConnectionDetails(this.properties));
KafkaConnectionDetails connectionDetails) {
Map<String, Object> properties = this.properties.buildStreamsProperties();
applyKafkaConnectionDetailsForStreams(connectionDetails, properties);
applyKafkaConnectionDetailsForStreams(properties, connectionDetails);
if (this.properties.getStreams().getApplicationId() == null) {
String applicationName = environment.getProperty("spring.application.name");
if (applicationName == null) {
@ -87,8 +85,8 @@ class KafkaStreamsAnnotationDrivenConfiguration {
return new KafkaStreamsFactoryBeanConfigurer(this.properties, factoryBean);
}
private void applyKafkaConnectionDetailsForStreams(KafkaConnectionDetails connectionDetails,
Map<String, Object> properties) {
private void applyKafkaConnectionDetailsForStreams(Map<String, Object> properties,
KafkaConnectionDetails connectionDetails) {
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
nodesToStringList(connectionDetails.getStreamsBootstrapNodes()));
if (!(connectionDetails instanceof PropertiesKafkaConnectionDetails)) {

@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.liquibase;
import java.util.function.Function;
import javax.sql.DataSource;
import liquibase.change.DatabaseChange;
@ -31,6 +33,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
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.flyway.FlywayConnectionDetails;
import org.springframework.boot.autoconfigure.flyway.FlywayProperties;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseAutoConfigurationRuntimeHints;
@ -85,13 +89,19 @@ public class LiquibaseAutoConfiguration {
@EnableConfigurationProperties(LiquibaseProperties.class)
public static class LiquibaseConfiguration {
@Bean
@ConditionalOnMissingBean(LiquibaseConnectionDetails.class)
PropertiesLiquibaseConnectionDetails liquibaseConnectionDetails(LiquibaseProperties properties,
ObjectProvider<JdbcConnectionDetails> jdbcConnectionDetails) {
return new PropertiesLiquibaseConnectionDetails(properties, jdbcConnectionDetails.getIfAvailable());
}
@Bean
public SpringLiquibase liquibase(ObjectProvider<DataSource> dataSource,
@LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource, LiquibaseProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetails) {
LiquibaseConnectionDetails connectionDetails) {
SpringLiquibase liquibase = createSpringLiquibase(liquibaseDataSource.getIfAvailable(),
dataSource.getIfUnique(),
connectionDetails.getIfAvailable(() -> new LiquibasePropertiesJdbcConnectionDetails(properties)));
dataSource.getIfUnique(), connectionDetails);
liquibase.setChangeLog(properties.getChangeLog());
liquibase.setClearCheckSums(properties.isClearChecksums());
liquibase.setContexts(properties.getContexts());
@ -111,7 +121,7 @@ public class LiquibaseAutoConfiguration {
}
private SpringLiquibase createSpringLiquibase(DataSource liquibaseDataSource, DataSource dataSource,
JdbcConnectionDetails connectionDetails) {
LiquibaseConnectionDetails connectionDetails) {
DataSource migrationDataSource = getMigrationDataSource(liquibaseDataSource, dataSource, connectionDetails);
SpringLiquibase liquibase = (migrationDataSource == liquibaseDataSource
|| migrationDataSource == dataSource) ? new SpringLiquibase()
@ -121,7 +131,7 @@ public class LiquibaseAutoConfiguration {
}
private DataSource getMigrationDataSource(DataSource liquibaseDataSource, DataSource dataSource,
JdbcConnectionDetails connectionDetails) {
LiquibaseConnectionDetails connectionDetails) {
if (liquibaseDataSource != null) {
return liquibaseDataSource;
}
@ -143,7 +153,8 @@ public class LiquibaseAutoConfiguration {
return dataSource;
}
private void applyConnectionDetails(JdbcConnectionDetails connectionDetails, DataSourceBuilder<?> builder) {
private void applyConnectionDetails(LiquibaseConnectionDetails connectionDetails,
DataSourceBuilder<?> builder) {
builder.username(connectionDetails.getUsername());
builder.password(connectionDetails.getPassword());
String driverClassName = connectionDetails.getDriverClassName();
@ -187,34 +198,46 @@ public class LiquibaseAutoConfiguration {
}
/**
* Adapts {@link LiquibaseProperties} to {@link JdbcConnectionDetails}.
* Adapts {@link FlywayProperties} to {@link FlywayConnectionDetails}, using
* {@link JdbcConnectionDetails} as a fallback when Flyway-specific properties have
* not be configured.
*/
private static final class LiquibasePropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
static final class PropertiesLiquibaseConnectionDetails implements LiquibaseConnectionDetails {
private final JdbcConnectionDetails fallback;
private final LiquibaseProperties properties;
private LiquibasePropertiesJdbcConnectionDetails(LiquibaseProperties properties) {
PropertiesLiquibaseConnectionDetails(LiquibaseProperties properties, JdbcConnectionDetails fallback) {
this.fallback = fallback;
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.getUser();
return get(this.properties.getUser(), JdbcConnectionDetails::getUsername);
}
@Override
public String getPassword() {
return this.properties.getPassword();
return get(this.properties.getPassword(), JdbcConnectionDetails::getPassword);
}
@Override
public String getJdbcUrl() {
return this.properties.getUrl();
return get(this.properties.getUrl(), JdbcConnectionDetails::getJdbcUrl);
}
@Override
public String getDriverClassName() {
return this.properties.getDriverClassName();
return get(this.properties.getDriverClassName(), JdbcConnectionDetails::getDriverClassName);
}
private String get(String primary, Function<JdbcConnectionDetails, String> fallbackProperty) {
if (primary != null) {
return primary;
}
return (this.fallback != null) ? fallbackProperty.apply(this.fallback) : null;
}
}

@ -0,0 +1,60 @@
/*
* 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.autoconfigure.liquibase;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.boot.jdbc.DatabaseDriver;
/**
* Details required for Liquibase to establish a connection to an SQL service using JDBC.
*
* @author Andy Wilkinson
* @since 3.1.0
*/
public interface LiquibaseConnectionDetails extends ConnectionDetails {
/**
* Username for the database.
* @return the username for the database
*/
String getUsername();
/**
* Password for the database.
* @return the password for the database
*/
String getPassword();
/**
* JDBC URL for the database.
* @return the JDBC URL for the database
*/
String getJdbcUrl();
/**
* The name of the JDBC driver class. Defaults to the class name of the driver
* specified in the JDBC URL.
* @return the JDBC driver class name
* @see #getJdbcUrl()
* @see DatabaseDriver#fromJdbcUrl(String)
* @see DatabaseDriver#getDriverClassName()
*/
default String getDriverClassName() {
return DatabaseDriver.fromJdbcUrl(getJdbcUrl()).getDriverClassName();
}
}

@ -45,6 +45,12 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnMissingBean(type = "org.springframework.data.mongodb.MongoDatabaseFactory")
public class MongoAutoConfiguration {
@Bean
@ConditionalOnMissingBean(MongoConnectionDetails.class)
PropertiesMongoConnectionDetails mongoConnectionDetails(MongoProperties properties) {
return new PropertiesMongoConnectionDetails(properties);
}
@Bean
@ConditionalOnMissingBean(MongoClient.class)
public MongoClient mongo(ObjectProvider<MongoClientSettingsBuilderCustomizer> builderCustomizers,
@ -63,9 +69,7 @@ public class MongoAutoConfiguration {
@Bean
StandardMongoClientSettingsBuilderCustomizer standardMongoSettingsCustomizer(MongoProperties properties,
ObjectProvider<MongoConnectionDetails> connectionDetailsProvider) {
MongoConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesMongoConnectionDetails(properties));
MongoConnectionDetails connectionDetails) {
return new StandardMongoClientSettingsBuilderCustomizer(connectionDetails.getConnectionString(),
properties.getUuidRepresentation());
}

@ -50,6 +50,12 @@ import org.springframework.core.annotation.Order;
@EnableConfigurationProperties(MongoProperties.class)
public class MongoReactiveAutoConfiguration {
@Bean
@ConditionalOnMissingBean(MongoConnectionDetails.class)
PropertiesMongoConnectionDetails mongoConnectionDetails(MongoProperties properties) {
return new PropertiesMongoConnectionDetails(properties);
}
@Bean
@ConditionalOnMissingBean
public MongoClient reactiveStreamsMongoClient(
@ -70,9 +76,7 @@ public class MongoReactiveAutoConfiguration {
@Bean
StandardMongoClientSettingsBuilderCustomizer standardMongoSettingsCustomizer(MongoProperties properties,
ObjectProvider<MongoConnectionDetails> connectionDetailsProvider) {
MongoConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesMongoConnectionDetails(properties));
MongoConnectionDetails connectionDetails) {
return new StandardMongoClientSettingsBuilderCustomizer(connectionDetails.getConnectionString(),
properties.getUuidRepresentation());
}

@ -61,13 +61,17 @@ import org.springframework.util.StringUtils;
@EnableConfigurationProperties(Neo4jProperties.class)
public class Neo4jAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Neo4jConnectionDetails.class)
PropertiesNeo4jConnectionDetails neo4jConnectionDetails(Neo4jProperties properties) {
return new PropertiesNeo4jConnectionDetails(properties);
}
@Bean
@ConditionalOnMissingBean
public Driver neo4jDriver(Neo4jProperties properties, Environment environment,
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers,
ObjectProvider<Neo4jConnectionDetails> connectionDetailsProvider) {
Neo4jConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesNeo4jConnectionDetails(properties));
Neo4jConnectionDetails connectionDetails,
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers) {
AuthToken authToken = connectionDetails.getAuthToken();
Config config = mapDriverConfig(properties, connectionDetails,
configBuilderCustomizers.orderedStream().toList());

@ -172,14 +172,21 @@ class RabbitAutoConfigurationTests {
});
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesRabbitConnectionDetails.class));
}
@Test
@SuppressWarnings("unchecked")
void testConnectionFactoryWithOverridesWhenUsingConnectionDetails() {
void testConnectionFactoryWithOverridesWhenUsingCustomConnectionDetails() {
this.contextRunner.withUserConfiguration(TestConfiguration.class, ConnectionDetailsConfiguration.class)
.withPropertyValues("spring.rabbitmq.host:remote-server", "spring.rabbitmq.port:9000",
"spring.rabbitmq.username:alice", "spring.rabbitmq.password:secret",
"spring.rabbitmq.virtual_host:/vhost")
.run((context) -> {
assertThat(context).hasSingleBean(RabbitConnectionDetails.class)
.doesNotHaveBean(PropertiesRabbitConnectionDetails.class);
CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class);
assertThat(connectionFactory.getHost()).isEqualTo("rabbit.example.com");
assertThat(connectionFactory.getPort()).isEqualTo(12345);

@ -33,6 +33,7 @@ import com.datastax.oss.driver.internal.core.session.throttling.RateLimitingRequ
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration.PropertiesCassandraConnectionDetails;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -95,13 +96,21 @@ class CassandraAutoConfigurationTests {
}
@Test
void shouldUseConnectionDetails() {
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner
.run((context) -> assertThat(context).hasSingleBean(PropertiesCassandraConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner
.withPropertyValues("spring.cassandra.contact-points=localhost:9042", "spring.cassandra.username=a-user",
"spring.cassandra.password=a-password", "spring.cassandra.local-datacenter=some-datacenter")
.withBean(CassandraConnectionDetails.class, this::cassandraConnectionDetails)
.run((context) -> {
assertThat(context).hasSingleBean(DriverConfigLoader.class);
assertThat(context).hasSingleBean(DriverConfigLoader.class)
.hasSingleBean(CassandraConnectionDetails.class)
.doesNotHaveBean(PropertiesCassandraConnectionDetails.class);
DriverExecutionProfile configuration = context.getBean(DriverConfigLoader.class)
.getInitialConfig()
.getDefaultProfile();

@ -34,6 +34,7 @@ import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.PropertiesCouchbaseConnectionDetails;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@ -64,10 +65,20 @@ class CouchbaseAutoConfigurationTests {
}
@Test
void shouldUseConnectionDetails() {
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
.withPropertyValues("spring.couchbase.connection-string=localhost")
.run((context) -> assertThat(context).hasSingleBean(PropertiesCouchbaseConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(CouchbaseConnectionDetails.class, this::couchbaseConnectionDetails)
.run((context) -> {
assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class);
assertThat(context).hasSingleBean(ClusterEnvironment.class)
.hasSingleBean(Cluster.class)
.hasSingleBean(CouchbaseConnectionDetails.class)
.doesNotHaveBean(PropertiesCouchbaseConnectionDetails.class);
Cluster cluster = context.getBean(Cluster.class);
assertThat(cluster.core()).extracting("connectionString.hosts")
.asList()

@ -33,6 +33,7 @@ import org.springframework.boot.autoconfigure.data.mongo.country.Country;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails;
import org.springframework.boot.autoconfigure.mongo.PropertiesMongoConnectionDetails;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@ -225,6 +226,25 @@ class MongoDataAutoConfigurationTests {
.run((context) -> assertThat(context).hasSingleBean(MongoTemplate.class));
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesMongoConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(MongoConnectionDetails.class, () -> new MongoConnectionDetails() {
@Override
public ConnectionString getConnectionString() {
return new ConnectionString("mongodb://localhost/testdb");
}
})
.run((context) -> assertThat(context).hasSingleBean(MongoConnectionDetails.class)
.doesNotHaveBean(PropertiesMongoConnectionDetails.class));
}
private static void assertDomainTypesDiscovered(MongoMappingContext mappingContext, Class<?>... types) {
ManagedTypes managedTypes = (ManagedTypes) ReflectionTestUtils.getField(mappingContext, "managedTypes");
assertThat(managedTypes.toList()).containsOnly(types);

@ -495,8 +495,15 @@ class RedisAutoConfigurationTests {
}
@Test
void usesStandaloneFromConnectionDetailsIfAvailable() {
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesRedisConnectionDetails.class));
}
@Test
void usesStandaloneFromCustomConnectionDetails() {
this.contextRunner.withUserConfiguration(ConnectionDetailsStandaloneConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(RedisConnectionDetails.class)
.doesNotHaveBean(PropertiesRedisConnectionDetails.class);
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.isUseSsl()).isFalse();
RedisStandaloneConfiguration configuration = cf.getStandaloneConfiguration();
@ -509,8 +516,10 @@ class RedisAutoConfigurationTests {
}
@Test
void usesSentinelFromConnectionDetailsIfAvailable() {
void usesSentinelFromCustomConnectionDetails() {
this.contextRunner.withUserConfiguration(ConnectionDetailsSentinelConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(RedisConnectionDetails.class)
.doesNotHaveBean(PropertiesRedisConnectionDetails.class);
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.isUseSsl()).isFalse();
RedisSentinelConfiguration configuration = cf.getSentinelConfiguration();
@ -526,8 +535,10 @@ class RedisAutoConfigurationTests {
}
@Test
void usesClusterFromConnectionDetailsIfAvailable() {
void usesClusterFromCustomConnectionDetails() {
this.contextRunner.withUserConfiguration(ConnectionDetailsClusterConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(RedisConnectionDetails.class)
.doesNotHaveBean(PropertiesRedisConnectionDetails.class);
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.isUseSsl()).isFalse();
RedisClusterConfiguration configuration = cf.getClusterConfiguration();

@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node.Protocol;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.PropertiesElasticsearchConnectionDetails;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@ -248,9 +249,17 @@ class ElasticsearchRestClientAutoConfigurationTests {
}
@Test
void connectionDetailsAreUsedIfAvailable() {
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner
.run((context) -> assertThat(context).hasSingleBean(PropertiesElasticsearchConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(RestClient.class);
assertThat(context).hasSingleBean(RestClient.class)
.hasSingleBean(ElasticsearchConnectionDetails.class)
.doesNotHaveBean(PropertiesElasticsearchConnectionDetails.class);
RestClient restClient = context.getBean(RestClient.class);
assertThat(restClient).hasFieldOrPropertyWithValue("pathPrefix", "/some-path");
assertThat(restClient.getNodes().stream().map(Node::getHost).map(HttpHost::toString))

@ -40,6 +40,7 @@ import org.jooq.impl.DefaultDSLContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InOrder;
import org.postgresql.Driver;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
@ -149,13 +150,34 @@ class FlywayAutoConfigurationTests {
}
@Test
void createDataSourceWithJdbcConnectionDetails() {
void flywayPropertiesAreUsedOverJdbcConnectionDetails() {
this.contextRunner
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JdbcConnectionDetailsConfiguration.class,
MockFlywayMigrationStrategy.class)
.withPropertyValues("spring.flyway.url=jdbc:hsqldb:mem:flywaytest", "spring.flyway.user=some-user",
"spring.flyway.password=some-password",
"spring.flyway.driver-class-name=org.hsqldb.jdbc.JDBCDriver")
.run((context) -> {
assertThat(context).hasSingleBean(Flyway.class);
Flyway flyway = context.getBean(Flyway.class);
DataSource dataSource = flyway.getConfiguration().getDataSource();
assertThat(dataSource).isInstanceOf(SimpleDriverDataSource.class);
SimpleDriverDataSource simpleDriverDataSource = (SimpleDriverDataSource) dataSource;
assertThat(simpleDriverDataSource.getUrl()).isEqualTo("jdbc:hsqldb:mem:flywaytest");
assertThat(simpleDriverDataSource.getUsername()).isEqualTo("some-user");
assertThat(simpleDriverDataSource.getPassword()).isEqualTo("some-password");
assertThat(simpleDriverDataSource.getDriver()).isInstanceOf(org.hsqldb.jdbc.JDBCDriver.class);
});
}
@Test
void flywayConnectionDetailsAreUsedOverFlywayProperties() {
this.contextRunner
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, FlywayConnectionDetailsConfiguration.class,
MockFlywayMigrationStrategy.class)
.withPropertyValues("spring.flyway.url=jdbc:hsqldb:mem:flywaytest", "spring.flyway.user=some-user",
"spring.flyway.password=some-password",
"spring.flyway.driver-class-name=org.hsqldb.jdbc.JDBCDriver")
.run((context) -> {
assertThat(context).hasSingleBean(Flyway.class);
Flyway flyway = context.getBean(Flyway.class);
@ -166,7 +188,7 @@ class FlywayAutoConfigurationTests {
.isEqualTo("jdbc:postgresql://database.example.com:12345/database-1");
assertThat(simpleDriverDataSource.getUsername()).isEqualTo("user-1");
assertThat(simpleDriverDataSource.getPassword()).isEqualTo("secret-1");
assertThat(simpleDriverDataSource.getDriver()).isInstanceOf(org.postgresql.Driver.class);
assertThat(simpleDriverDataSource.getDriver()).isInstanceOf(Driver.class);
});
}
@ -246,6 +268,19 @@ class FlywayAutoConfigurationTests {
});
}
@Test
void flywayDataSourceIsUsedWhenFlywayConnectionDetailsIsAvailable() {
this.contextRunner
.withUserConfiguration(FlywayDataSourceConfiguration.class, EmbeddedDataSourceConfiguration.class,
FlywayConnectionDetailsConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(FlywayConnectionDetails.class);
assertThat(context).hasSingleBean(Flyway.class);
assertThat(context.getBean(Flyway.class).getConfiguration().getDataSource())
.isEqualTo(context.getBean("flywayDataSource"));
});
}
@Test
void flywayDataSourceWithoutDataSourceAutoConfiguration() {
this.contextRunner.withUserConfiguration(FlywayDataSourceConfiguration.class).run((context) -> {
@ -1109,4 +1144,31 @@ class FlywayAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class FlywayConnectionDetailsConfiguration {
@Bean
FlywayConnectionDetails flywayConnectionDetails() {
return new FlywayConnectionDetails() {
@Override
public String getJdbcUrl() {
return "jdbc:postgresql://database.example.com:12345/database-1";
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "secret-1";
}
};
}
}
}

@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
import retrofit2.Retrofit;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration.PropertiesInfluxDbConnectionDetails;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@ -54,9 +55,17 @@ class InfluxDbAutoConfigurationTests {
}
@Test
void shouldUseConnectionDetails() {
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.withPropertyValues("spring.influx.url=http://localhost")
.run((context) -> assertThat(context).hasSingleBean(PropertiesInfluxDbConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(InfluxDbConnectionDetails.class, this::influxDbConnectionDetails).run((context) -> {
assertThat(context).hasSingleBean(InfluxDB.class);
assertThat(context).hasSingleBean(InfluxDB.class)
.hasSingleBean(InfluxDbConnectionDetails.class)
.doesNotHaveBean(PropertiesInfluxDbConnectionDetails.class);
InfluxDB influxDb = context.getBean(InfluxDB.class);
assertThat(influxDb).hasFieldOrPropertyWithValue("hostName", "localhost");
});

@ -249,13 +249,21 @@ class DataSourceAutoConfigurationTests {
}
@Test
void dbcp2UsesJdbcConnectionDetailsIfAvailable() {
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesJdbcConnectionDetails.class));
}
@Test
void dbcp2UsesCustomConnectionDetailsWhenDefined() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource",
"spring.datasource.dbcp2.url=jdbc:broken", "spring.datasource.dbcp2.username=alice",
"spring.datasource.dbcp2.password=secret")
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class));
runner.withUserConfiguration(JdbcConnectionDetailsConfiguration.class).run((context) -> {
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withBean(JdbcConnectionDetails.class, TestJdbcConnectionDetails::new);
runner.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).asInstanceOf(InstanceOfAssertFactories.type(BasicDataSource.class))
.satisfies((dbcp2) -> {
@ -268,11 +276,14 @@ class DataSourceAutoConfigurationTests {
}
@Test
void genericUsesJdbcConnectionDetailsIfAvailable() {
void genericUsesCustomJdbcConnectionDetailsWhenAvailable() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.type=" + TestDataSource.class.getName())
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class));
runner.withUserConfiguration(JdbcConnectionDetailsConfiguration.class).run((context) -> {
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withBean(JdbcConnectionDetails.class, TestJdbcConnectionDetails::new);
runner.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(TestDataSource.class);
TestDataSource source = (TestDataSource) dataSource;

@ -104,10 +104,12 @@ class HikariDataSourceConfigurationTests {
}
@Test
void usesConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class)
void usesCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(JdbcConnectionDetails.class, TestJdbcConnectionDetails::new)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).asInstanceOf(InstanceOfAssertFactories.type(HikariDataSource.class))
.satisfies((hikari) -> {

@ -28,8 +28,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@ -114,10 +112,12 @@ class OracleUcpDataSourceConfigurationTests {
}
@Test
void usesJdbcConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class)
void usesCustomJdbcConnectionDetailsWhenDefined() {
this.contextRunner.withBean(JdbcConnectionDetails.class, TestJdbcConnectionDetails::new)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(PoolDataSourceImpl.class);
PoolDataSourceImpl oracleUcp = (PoolDataSourceImpl) dataSource;
@ -131,14 +131,4 @@ class OracleUcpDataSourceConfigurationTests {
});
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
}

@ -117,10 +117,12 @@ class TomcatDataSourceConfigurationTests {
}
@Test
void usesJdbcConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class)
void usesCustomJdbcConnectionDetailsWhenDefined() {
this.contextRunner.withBean(JdbcConnectionDetails.class, TestJdbcConnectionDetails::new)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(org.apache.tomcat.jdbc.pool.DataSource.class);
org.apache.tomcat.jdbc.pool.DataSource tomcat = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
@ -133,16 +135,6 @@ class TomcatDataSourceConfigurationTests {
});
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@EnableMBeanExport

@ -96,7 +96,14 @@ class XADataSourceAutoConfigurationTests {
}
@Test
void shouldUseConnectionDetailsIfAvailable() {
void definesPropertiesBasedConnectionDetailsByDefault() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(XADataSourceAutoConfiguration.class))
.withUserConfiguration(FromProperties.class)
.run((context) -> assertThat(context).hasSingleBean(PropertiesJdbcConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
JdbcConnectionDetails connectionDetails = mock(JdbcConnectionDetails.class);
given(connectionDetails.getUsername()).willReturn("user-1");
given(connectionDetails.getPassword()).willReturn("password-1");
@ -108,6 +115,8 @@ class XADataSourceAutoConfigurationTests {
.withUserConfiguration(FromProperties.class)
.withBean(JdbcConnectionDetails.class, () -> connectionDetails)
.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
PGXADataSource dataSource = (PGXADataSource) wrapper.getXaDataSource();
assertThat(dataSource).isNotNull();

@ -164,6 +164,11 @@ class KafkaAutoConfigurationTests {
});
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesKafkaConnectionDetails.class));
}
@Test
void connectionDetailsAreAppliedToConsumer() {
this.contextRunner
@ -172,6 +177,8 @@ class KafkaAutoConfigurationTests {
"spring.kafka.consumer.security.protocol=SSL")
.withBean(KafkaConnectionDetails.class, this::kafkaConnectionDetails)
.run((context) -> {
assertThat(context).hasSingleBean(KafkaConnectionDetails.class)
.doesNotHaveBean(PropertiesKafkaConnectionDetails.class);
DefaultKafkaConsumerFactory<?, ?> consumerFactory = context.getBean(DefaultKafkaConsumerFactory.class);
Map<String, Object> configs = consumerFactory.getConfigurationProperties();
assertThat(configs).containsEntry(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
@ -240,6 +247,8 @@ class KafkaAutoConfigurationTests {
"spring.kafka.producer.security.protocol=SSL")
.withBean(KafkaConnectionDetails.class, this::kafkaConnectionDetails)
.run((context) -> {
assertThat(context).hasSingleBean(KafkaConnectionDetails.class)
.doesNotHaveBean(PropertiesKafkaConnectionDetails.class);
DefaultKafkaProducerFactory<?, ?> producerFactory = context.getBean(DefaultKafkaProducerFactory.class);
Map<String, Object> configs = producerFactory.getConfigurationProperties();
assertThat(configs).containsEntry(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
@ -298,6 +307,8 @@ class KafkaAutoConfigurationTests {
"spring.kafka.admin.security.protocol=SSL")
.withBean(KafkaConnectionDetails.class, this::kafkaConnectionDetails)
.run((context) -> {
assertThat(context).hasSingleBean(KafkaConnectionDetails.class)
.doesNotHaveBean(PropertiesKafkaConnectionDetails.class);
KafkaAdmin admin = context.getBean(KafkaAdmin.class);
Map<String, Object> configs = admin.getConfigurationProperties();
assertThat(configs).containsEntry(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
@ -363,6 +374,8 @@ class KafkaAutoConfigurationTests {
"spring.kafka.security.protocol=SSL", "spring.kafka.streams.security.protocol=SSL")
.withBean(KafkaConnectionDetails.class, this::kafkaConnectionDetails)
.run((context) -> {
assertThat(context).hasSingleBean(KafkaConnectionDetails.class)
.doesNotHaveBean(PropertiesKafkaConnectionDetails.class);
Properties configs = context
.getBean(KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME,
KafkaStreamsConfiguration.class)

@ -156,6 +156,49 @@ class LiquibaseAutoConfigurationTests {
}));
}
@Test
void liquibaseDataSourceIsUsedOverLiquibaseConnectionDetails() {
this.contextRunner
.withUserConfiguration(LiquibaseDataSourceConfiguration.class,
LiquibaseConnectionDetailsConfiguration.class)
.run(assertLiquibase((liquibase) -> {
HikariDataSource dataSource = (HikariDataSource) liquibase.getDataSource();
assertThat(dataSource.getJdbcUrl()).startsWith("jdbc:hsqldb:mem:liquibasetest");
assertThat(dataSource.getUsername()).isEqualTo("sa");
assertThat(dataSource.getPassword()).isNull();
}));
}
@Test
void liquibasePropertiesAreUsedOverJdbcConnectionDetails() {
this.contextRunner
.withPropertyValues("spring.liquibase.url=jdbc:hsqldb:mem:liquibasetest", "spring.liquibase.user=some-user",
"spring.liquibase.password=some-password",
"spring.liquibase.driver-class-name=org.hsqldb.jdbc.JDBCDriver")
.withUserConfiguration(JdbcConnectionDetailsConfiguration.class)
.run(assertLiquibase((liquibase) -> {
SimpleDriverDataSource dataSource = (SimpleDriverDataSource) liquibase.getDataSource();
assertThat(dataSource.getUrl()).startsWith("jdbc:hsqldb:mem:liquibasetest");
assertThat(dataSource.getUsername()).isEqualTo("some-user");
assertThat(dataSource.getPassword()).isEqualTo("some-password");
}));
}
@Test
void liquibaseConnectionDetailsAreUsedOverLiquibaseProperties() {
this.contextRunner.withSystemProperties("shouldRun=false")
.withPropertyValues("spring.liquibase.url=jdbc:hsqldb:mem:liquibasetest", "spring.liquibase.user=some-user",
"spring.liquibase.password=some-password",
"spring.liquibase.driver-class-name=org.hsqldb.jdbc.JDBCDriver")
.withUserConfiguration(LiquibaseConnectionDetailsConfiguration.class)
.run(assertLiquibase((liquibase) -> {
SimpleDriverDataSource dataSource = (SimpleDriverDataSource) liquibase.getDataSource();
assertThat(dataSource.getUrl()).isEqualTo("jdbc:postgresql://database.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("secret-1");
}));
}
@Test
void changelogXml() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
@ -578,6 +621,33 @@ class LiquibaseAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class LiquibaseConnectionDetailsConfiguration {
@Bean
LiquibaseConnectionDetails liquibaseConnectionDetails() {
return new LiquibaseConnectionDetails() {
@Override
public String getJdbcUrl() {
return "jdbc:postgresql://database.example.com:12345/database-1";
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "secret-1";
}
};
}
}
static class CustomH2Driver extends org.h2.Driver {
}

@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.mongo;
import java.util.concurrent.TimeUnit;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
@ -84,6 +85,25 @@ class MongoAutoConfigurationTests {
.run((context) -> assertThat(getSettings(context).getApplicationName()).isEqualTo("overridden-name"));
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesMongoConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(MongoConnectionDetails.class, () -> new MongoConnectionDetails() {
@Override
public ConnectionString getConnectionString() {
return new ConnectionString("mongodb://localhost");
}
})
.run((context) -> assertThat(context).hasSingleBean(MongoConnectionDetails.class)
.doesNotHaveBean(PropertiesMongoConnectionDetails.class));
}
private MongoClientSettings getSettings(AssertableApplicationContext context) {
assertThat(context).hasSingleBean(MongoClient.class);
MongoClientImpl client = (MongoClientImpl) context.getBean(MongoClient.class);

@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.mongo;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.ReadPreference;
import com.mongodb.connection.AsynchronousSocketChannelStreamFactoryFactory;
@ -111,6 +112,25 @@ class MongoReactiveAutoConfigurationTests {
});
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesMongoConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(MongoConnectionDetails.class, () -> new MongoConnectionDetails() {
@Override
public ConnectionString getConnectionString() {
return new ConnectionString("mongodb://localhost");
}
})
.run((context) -> assertThat(context).hasSingleBean(MongoConnectionDetails.class)
.doesNotHaveBean(PropertiesMongoConnectionDetails.class));
}
private MongoClientSettings getSettings(ApplicationContext context) {
MongoClientImpl client = (MongoClientImpl) context.getBean(MongoClient.class);
return client.getSettings();

@ -105,8 +105,13 @@ class Neo4jAutoConfigurationTests {
.hasMessageContaining("'%s' is not a supported scheme.", invalidScheme));
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesNeo4jConnectionDetails.class));
}
@Bean
void usesCustomConnectionDetails() {
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(Neo4jConnectionDetails.class, () -> new Neo4jConnectionDetails() {
@Override
@ -115,7 +120,9 @@ class Neo4jAutoConfigurationTests {
}
}).run((context) -> {
assertThat(context).hasSingleBean(Driver.class);
assertThat(context).hasSingleBean(Driver.class)
.hasSingleBean(Neo4jConnectionDetails.class)
.doesNotHaveBean(PropertiesNeo4jConnectionDetails.class);
Driver driver = context.getBean(Driver.class);
assertThat(driver.isEncrypted()).isTrue();
});

@ -0,0 +1,78 @@
/*
* 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.test.autoconfigure.flyway;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.springframework.boot.autoconfigure.flyway.FlywayConnectionDetails;
import org.springframework.boot.test.autoconfigure.service.connection.ContainerConnectionDetailsFactory;
import org.springframework.boot.test.autoconfigure.service.connection.ContainerConnectionSource;
import org.springframework.boot.test.autoconfigure.service.connection.ServiceConnection;
/**
* {@link ContainerConnectionDetailsFactory} for
* {@link ServiceConnection @ServiceConnection}-annotated {@link JdbcDatabaseContainer}
* fields that should produce {@link FlywayConnectionDetails}.
*
* @author Andy Wilkinson
*/
class FlywayContainerConnectionDetailsFactory extends
ContainerConnectionDetailsFactory<ServiceConnection, FlywayConnectionDetails, JdbcDatabaseContainer<?>> {
@Override
protected FlywayConnectionDetails getContainerConnectionDetails(
ContainerConnectionSource<ServiceConnection, FlywayConnectionDetails, JdbcDatabaseContainer<?>> source) {
return new FlywayContainerConnectionDetails(source);
}
/**
* {@link FlywayConnectionDetails} backed by a {@link JdbcDatabaseContainer}.
*/
private static final class FlywayContainerConnectionDetails extends ContainerConnectionDetails
implements FlywayConnectionDetails {
private final JdbcDatabaseContainer<?> container;
private FlywayContainerConnectionDetails(
ContainerConnectionSource<ServiceConnection, FlywayConnectionDetails, JdbcDatabaseContainer<?>> source) {
super(source);
this.container = source.getContainer();
}
@Override
public String getUsername() {
return this.container.getUsername();
}
@Override
public String getPassword() {
return this.container.getPassword();
}
@Override
public String getJdbcUrl() {
return this.container.getJdbcUrl();
}
@Override
public String getDriverClassName() {
return this.container.getDriverClassName();
}
}
}

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Auto-configuration for using Flyway in tests.
*/
package org.springframework.boot.test.autoconfigure.flyway;

@ -0,0 +1,78 @@
/*
* 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.test.autoconfigure.liquibase;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseConnectionDetails;
import org.springframework.boot.test.autoconfigure.service.connection.ContainerConnectionDetailsFactory;
import org.springframework.boot.test.autoconfigure.service.connection.ContainerConnectionSource;
import org.springframework.boot.test.autoconfigure.service.connection.ServiceConnection;
/**
* {@link ContainerConnectionDetailsFactory} for
* {@link ServiceConnection @ServiceConnection}-annotated {@link JdbcDatabaseContainer}
* fields that should produce {@link LiquibaseConnectionDetails}.
*
* @author Andy Wilkinson
*/
class LiquibaseContainerConnectionDetailsFactory extends
ContainerConnectionDetailsFactory<ServiceConnection, LiquibaseConnectionDetails, JdbcDatabaseContainer<?>> {
@Override
protected LiquibaseConnectionDetails getContainerConnectionDetails(
ContainerConnectionSource<ServiceConnection, LiquibaseConnectionDetails, JdbcDatabaseContainer<?>> source) {
return new LiquibaseContainerConnectionDetails(source);
}
/**
* {@link LiquibaseConnectionDetails} backed by a {@link JdbcDatabaseContainer}.
*/
private static final class LiquibaseContainerConnectionDetails extends ContainerConnectionDetails
implements LiquibaseConnectionDetails {
private final JdbcDatabaseContainer<?> container;
private LiquibaseContainerConnectionDetails(
ContainerConnectionSource<ServiceConnection, LiquibaseConnectionDetails, JdbcDatabaseContainer<?>> source) {
super(source);
this.container = source.getContainer();
}
@Override
public String getUsername() {
return this.container.getUsername();
}
@Override
public String getPassword() {
return this.container.getPassword();
}
@Override
public String getJdbcUrl() {
return this.container.getJdbcUrl();
}
@Override
public String getDriverClassName() {
return this.container.getDriverClassName();
}
}
}

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Auto-configuration for using Liquibase in tests.
*/
package org.springframework.boot.test.autoconfigure.liquibase;

@ -26,9 +26,11 @@ org.springframework.boot.test.autoconfigure.cassandra.CassandraContainerConnecti
org.springframework.boot.test.autoconfigure.couchbase.CouchbaseContainerConnectionDetailsFactory,\
org.springframework.boot.test.autoconfigure.data.redis.RedisContainerConnectionDetailsFactory,\
org.springframework.boot.test.autoconfigure.elasticsearch.ElasticsearchContainerConnectionDetailsFactory,\
org.springframework.boot.test.autoconfigure.flyway.FlywayContainerConnectionDetailsFactory,\
org.springframework.boot.test.autoconfigure.influx.InfluxDbContainerConnectionDetailsFactory,\
org.springframework.boot.test.autoconfigure.jdbc.JdbcContainerConnectionDetailsFactory,\
org.springframework.boot.test.autoconfigure.kafka.KafkaContainerConnectionDetailsFactory,\
org.springframework.boot.test.autoconfigure.liquibase.LiquibaseContainerConnectionDetailsFactory,\
org.springframework.boot.test.autoconfigure.mongo.MongoContainerConnectionDetailsFactory,\
org.springframework.boot.test.autoconfigure.neo4j.Neo4jContainerConnectionDetailsFactory,\
org.springframework.boot.test.autoconfigure.r2dbc.MariaDbR2dbcContainerConnectionDetailsFactory,\

@ -23,9 +23,10 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import reactor.test.StepVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.flyway.FlywayConnectionDetails;
import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest;
import org.springframework.boot.test.autoconfigure.jdbc.JdbcServiceConnection;
import org.springframework.boot.test.autoconfigure.r2dbc.R2dbcServiceConnection;
import org.springframework.boot.test.autoconfigure.service.connection.ServiceConnection;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import static org.assertj.core.api.Assertions.assertThat;
@ -42,8 +43,8 @@ import static org.assertj.core.api.Assertions.assertThat;
class CityRepositoryTests {
@Container
@JdbcServiceConnection
@R2dbcServiceConnection
@ServiceConnection(FlywayConnectionDetails.class)
static PostgreSQLContainer<?> postgresql = new PostgreSQLContainer<>(DockerImageNames.postgresql())
.withDatabaseName("test_flyway");

@ -23,9 +23,10 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import reactor.test.StepVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseConnectionDetails;
import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest;
import org.springframework.boot.test.autoconfigure.jdbc.JdbcServiceConnection;
import org.springframework.boot.test.autoconfigure.r2dbc.R2dbcServiceConnection;
import org.springframework.boot.test.autoconfigure.service.connection.ServiceConnection;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import static org.assertj.core.api.Assertions.assertThat;
@ -42,8 +43,8 @@ import static org.assertj.core.api.Assertions.assertThat;
class CityRepositoryTests {
@Container
@JdbcServiceConnection
@R2dbcServiceConnection
@ServiceConnection(LiquibaseConnectionDetails.class)
static PostgreSQLContainer<?> postgresql = new PostgreSQLContainer<>(DockerImageNames.postgresql())
.withDatabaseName("test_liquibase");

Loading…
Cancel
Save