Merge branch 'gh-34657'

Closes gh-34657
This commit is contained in:
Andy Wilkinson
2023-03-24 09:51:15 +00:00
81 changed files with 4271 additions and 518 deletions

View File

@@ -0,0 +1,39 @@
/*
* 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.actuate.autoconfigure.tracing.zipkin;
/**
* Adapts {@link ZipkinProperties} to {@link ZipkinConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class PropertiesZipkinConnectionDetails implements ZipkinConnectionDetails {
private final ZipkinProperties properties;
PropertiesZipkinConnectionDetails(ZipkinProperties properties) {
this.properties = properties;
}
@Override
public String getSpanEndpoint() {
return this.properties.getEndpoint();
}
}

View File

@@ -59,11 +59,14 @@ class ZipkinConfigurations {
@Bean
@ConditionalOnMissingBean(Sender.class)
URLConnectionSender urlConnectionSender(ZipkinProperties properties) {
URLConnectionSender urlConnectionSender(ZipkinProperties properties,
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider) {
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
URLConnectionSender.Builder builder = URLConnectionSender.newBuilder();
builder.connectTimeout((int) properties.getConnectTimeout().toMillis());
builder.readTimeout((int) properties.getReadTimeout().toMillis());
builder.endpoint(properties.getEndpoint());
builder.endpoint(connectionDetails.getSpanEndpoint());
return builder.build();
}
@@ -77,12 +80,15 @@ class ZipkinConfigurations {
@Bean
@ConditionalOnMissingBean(Sender.class)
ZipkinRestTemplateSender restTemplateSender(ZipkinProperties properties,
ObjectProvider<ZipkinRestTemplateBuilderCustomizer> customizers) {
ObjectProvider<ZipkinRestTemplateBuilderCustomizer> customizers,
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider) {
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder()
.setConnectTimeout(properties.getConnectTimeout())
.setReadTimeout(properties.getReadTimeout());
restTemplateBuilder = applyCustomizers(restTemplateBuilder, customizers);
return new ZipkinRestTemplateSender(properties.getEndpoint(), restTemplateBuilder.build());
return new ZipkinRestTemplateSender(connectionDetails.getSpanEndpoint(), restTemplateBuilder.build());
}
private RestTemplateBuilder applyCustomizers(RestTemplateBuilder restTemplateBuilder,
@@ -106,10 +112,13 @@ class ZipkinConfigurations {
@Bean
@ConditionalOnMissingBean(Sender.class)
ZipkinWebClientSender webClientSender(ZipkinProperties properties,
ObjectProvider<ZipkinWebClientBuilderCustomizer> customizers) {
ObjectProvider<ZipkinWebClientBuilderCustomizer> customizers,
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider) {
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
WebClient.Builder builder = WebClient.builder();
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return new ZipkinWebClientSender(properties.getEndpoint(), builder.build());
return new ZipkinWebClientSender(connectionDetails.getSpanEndpoint(), builder.build());
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.actuate.autoconfigure.tracing.zipkin;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to a Zipkin server.
*
* @author Moritz Halbritter
* @since 3.1.0
*/
public interface ZipkinConnectionDetails extends ConnectionDetails {
/**
* The endpoint for the span reporting.
* @return the endpoint
*/
String getSpanEndpoint();
}

View File

@@ -236,6 +236,8 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-junit-jupiter")
testImplementation("org.postgresql:postgresql")
testImplementation("org.postgresql:r2dbc-postgresql")
testImplementation("org.skyscreamer:jsonassert")
testImplementation("org.springframework:spring-test")
testImplementation("org.springframework:spring-core-test")

View File

@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.amqp;
import java.util.stream.Collectors;
import org.springframework.amqp.rabbit.connection.AbstractConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy;
import org.springframework.boot.context.properties.PropertyMapper;
@@ -27,6 +29,9 @@ import org.springframework.util.Assert;
*
* @param <T> the connection factory type.
* @author Chris Bono
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 2.6.0
*/
public abstract class AbstractConnectionFactoryConfigurer<T extends AbstractConnectionFactory> {
@@ -35,9 +40,31 @@ public abstract class AbstractConnectionFactoryConfigurer<T extends AbstractConn
private ConnectionNameStrategy connectionNameStrategy;
private final RabbitConnectionDetails connectionDetails;
/**
* Creates a new configurer that will configure the connection factory using the given
* {@code properties}.
* @param properties the properties to use to configure the connection factory
*/
protected AbstractConnectionFactoryConfigurer(RabbitProperties properties) {
Assert.notNull(properties, "RabbitProperties must not be null");
this(properties, new PropertiesRabbitConnectionDetails(properties));
}
/**
* Creates a new configurer that will configure the connection factory using the given
* {@code properties} and {@code connectionDetails}, with the latter taking priority.
* @param properties the properties to use to configure the connection factory
* @param connectionDetails the connection details to use to configure the connection
* factory
* @since 3.1.0
*/
protected AbstractConnectionFactoryConfigurer(RabbitProperties properties,
RabbitConnectionDetails connectionDetails) {
Assert.notNull(properties, "Properties must not be null");
Assert.notNull(connectionDetails, "ConnectionDetails must not be null");
this.rabbitProperties = properties;
this.connectionDetails = connectionDetails;
}
protected final ConnectionNameStrategy getConnectionNameStrategy() {
@@ -55,7 +82,11 @@ public abstract class AbstractConnectionFactoryConfigurer<T extends AbstractConn
public final void configure(T connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
PropertyMapper map = PropertyMapper.get();
map.from(this.rabbitProperties::determineAddresses).to(connectionFactory::setAddresses);
String addresses = this.connectionDetails.getAddresses()
.stream()
.map((address) -> address.host() + ":" + address.port())
.collect(Collectors.joining(","));
map.from(addresses).to(connectionFactory::setAddresses);
map.from(this.rabbitProperties::getAddressShuffleMode)
.whenNonNull()
.to(connectionFactory::setAddressShuffleMode);

View File

@@ -25,12 +25,32 @@ import org.springframework.boot.context.properties.PropertyMapper;
* Configures Rabbit {@link CachingConnectionFactory} with sensible defaults.
*
* @author Chris Bono
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 2.6.0
*/
public class CachingConnectionFactoryConfigurer extends AbstractConnectionFactoryConfigurer<CachingConnectionFactory> {
/**
* Creates a new configurer that will configure the connection factory using the given
* {@code properties}.
* @param properties the properties to use to configure the connection factory
*/
public CachingConnectionFactoryConfigurer(RabbitProperties properties) {
super(properties);
this(properties, new PropertiesRabbitConnectionDetails(properties));
}
/**
* Creates a new configurer that will configure the connection factory using the given
* {@code properties} and {@code connectionDetails}, with the latter taking priority.
* @param properties the properties to use to configure the connection factory
* @param connectionDetails the connection details to use to configure the connection
* factory
* @since 3.1.0
*/
public CachingConnectionFactoryConfigurer(RabbitProperties properties, RabbitConnectionDetails connectionDetails) {
super(properties, connectionDetails);
}
@Override

View File

@@ -0,0 +1,63 @@
/*
* 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.amqp;
import java.util.ArrayList;
import java.util.List;
/**
* Adapts {@link RabbitProperties} to {@link RabbitConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public class PropertiesRabbitConnectionDetails implements RabbitConnectionDetails {
private final RabbitProperties properties;
public PropertiesRabbitConnectionDetails(RabbitProperties properties) {
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.determineUsername();
}
@Override
public String getPassword() {
return this.properties.determinePassword();
}
@Override
public String getVirtualHost() {
return this.properties.determineVirtualHost();
}
@Override
public List<Address> getAddresses() {
List<Address> addresses = new ArrayList<>();
for (String address : this.properties.determineAddresses().split(",")) {
String[] components = address.split(":");
addresses.add(new Address(components[0], Integer.parseInt(components[1])));
}
return addresses;
}
}

View File

@@ -59,21 +59,6 @@ import org.springframework.core.io.ResourceLoader;
* <li>{@link org.springframework.amqp.core.AmqpAdmin } instance as long as
* {@literal spring.rabbitmq.dynamic=true}.</li>
* </ul>
* <p>
* The {@link org.springframework.amqp.rabbit.connection.CachingConnectionFactory} honors
* the following properties:
* <ul>
* <li>{@literal spring.rabbitmq.port} is used to specify the port to which the client
* should connect, and defaults to 5672.</li>
* <li>{@literal spring.rabbitmq.username} is used to specify the (optional) username.
* </li>
* <li>{@literal spring.rabbitmq.password} is used to specify the (optional) password.
* </li>
* <li>{@literal spring.rabbitmq.host} is used to specify the host, and defaults to
* {@literal localhost}.</li>
* <li>{@literal spring.rabbitmq.virtualHost} is used to specify the (optional) virtual
* host to which the client should connect.</li>
* </ul>
*
* @author Greg Turnquist
* @author Josh Long
@@ -82,6 +67,8 @@ import org.springframework.core.io.ResourceLoader;
* @author Phillip Webb
* @author Artsiom Yudovin
* @author Chris Bono
* @author Moritz Halbritter
* @author Andy Wilkinson
* @since 1.0.0
*/
@AutoConfiguration
@@ -93,13 +80,24 @@ public class RabbitAutoConfiguration {
@Configuration(proxyBeanMethods = false)
protected static class RabbitConnectionFactoryCreator {
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
RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer(RabbitProperties properties,
ResourceLoader resourceLoader, ObjectProvider<CredentialsProvider> credentialsProvider,
RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader,
ObjectProvider<CredentialsProvider> credentialsProvider,
ObjectProvider<CredentialsRefreshService> credentialsRefreshService) {
RabbitConnectionFactoryBeanConfigurer configurer = new RabbitConnectionFactoryBeanConfigurer(resourceLoader,
properties);
this.properties, this.connectionDetails);
configurer.setCredentialsProvider(credentialsProvider.getIfUnique());
configurer.setCredentialsRefreshService(credentialsRefreshService.getIfUnique());
return configurer;
@@ -107,9 +105,10 @@ public class RabbitAutoConfiguration {
@Bean
@ConditionalOnMissingBean
CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer(RabbitProperties rabbitProperties,
CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer(
ObjectProvider<ConnectionNameStrategy> connectionNameStrategy) {
CachingConnectionFactoryConfigurer configurer = new CachingConnectionFactoryConfigurer(rabbitProperties);
CachingConnectionFactoryConfigurer configurer = new CachingConnectionFactoryConfigurer(this.properties,
this.connectionDetails);
configurer.setConnectionNameStrategy(connectionNameStrategy.getIfUnique());
return configurer;
}

View File

@@ -0,0 +1,85 @@
/*
* 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.amqp;
import java.util.List;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.util.Assert;
/**
* Details required to establish a connection to a RabbitMQ service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface RabbitConnectionDetails extends ConnectionDetails {
/**
* Login user to authenticate to the broker.
* @return the login user to authenticate to the broker or {@code null}
*/
default String getUsername() {
return null;
}
/**
* Login to authenticate against the broker.
* @return the login to authenticate against the broker or {@code null}
*/
default String getPassword() {
return null;
}
/**
* Virtual host to use when connecting to the broker.
* @return the virtual host to use when connecting to the broker or {@code null}
*/
default String getVirtualHost() {
return null;
}
/**
* List of addresses to which the client should connect. Must return at least one
* address.
* @return the list of addresses to which the client should connect
*/
List<Address> getAddresses();
/**
* Returns the first address.
* @return the first address
* @throws IllegalStateException if the address list is empty
*/
default Address getFirstAddress() {
List<Address> addresses = getAddresses();
Assert.state(!addresses.isEmpty(), "Address list is empty");
return addresses.get(0);
}
/**
* A RabbitMQ address.
*
* @param host the host
* @param port the port
*/
record Address(String host, int port) {
}
}

View File

@@ -22,6 +22,7 @@ import com.rabbitmq.client.impl.CredentialsProvider;
import com.rabbitmq.client.impl.CredentialsRefreshService;
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails.Address;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
@@ -30,6 +31,9 @@ import org.springframework.util.Assert;
* Configures {@link RabbitConnectionFactoryBean} with sensible defaults.
*
* @author Chris Bono
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 2.6.0
*/
public class RabbitConnectionFactoryBeanConfigurer {
@@ -38,13 +42,39 @@ public class RabbitConnectionFactoryBeanConfigurer {
private final ResourceLoader resourceLoader;
private final RabbitConnectionDetails connectionDetails;
private CredentialsProvider credentialsProvider;
private CredentialsRefreshService credentialsRefreshService;
/**
* Creates a new configurer that will use the given {@code resourceLoader} and
* {@code properties}.
* @param resourceLoader the resource loader
* @param properties the properties
*/
public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitProperties properties) {
this(resourceLoader, properties, new PropertiesRabbitConnectionDetails(properties));
}
/**
* Creates a new configurer that will use the given {@code resourceLoader},
* {@code properties}, and {@code connectionDetails}. The connection details have
* priority over the properties.
* @param resourceLoader the resource loader
* @param properties the properties
* @param connectionDetails the connection details.
* @since 3.1.0
*/
public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitProperties properties,
RabbitConnectionDetails connectionDetails) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
Assert.notNull(properties, "Properties must not be null");
Assert.notNull(connectionDetails, "ConnectionDetails must not be null");
this.resourceLoader = resourceLoader;
this.rabbitProperties = properties;
this.connectionDetails = connectionDetails;
}
public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
@@ -65,12 +95,13 @@ public class RabbitConnectionFactoryBeanConfigurer {
public void configure(RabbitConnectionFactoryBean factory) {
Assert.notNull(factory, "RabbitConnectionFactoryBean must not be null");
factory.setResourceLoader(this.resourceLoader);
Address address = this.connectionDetails.getFirstAddress();
PropertyMapper map = PropertyMapper.get();
map.from(this.rabbitProperties::determineHost).whenNonNull().to(factory::setHost);
map.from(this.rabbitProperties::determinePort).to(factory::setPort);
map.from(this.rabbitProperties::determineUsername).whenNonNull().to(factory::setUsername);
map.from(this.rabbitProperties::determinePassword).whenNonNull().to(factory::setPassword);
map.from(this.rabbitProperties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost);
map.from(address::host).whenNonNull().to(factory::setHost);
map.from(address::port).to(factory::setPort);
map.from(this.connectionDetails::getUsername).whenNonNull().to(factory::setUsername);
map.from(this.connectionDetails::getPassword).whenNonNull().to(factory::setPassword);
map.from(this.connectionDetails::getVirtualHost).whenNonNull().to(factory::setVirtualHost);
map.from(this.rabbitProperties::getRequestedHeartbeat)
.whenNonNull()
.asInt(Duration::getSeconds)

View File

@@ -23,7 +23,6 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import javax.net.ssl.SSLContext;
@@ -64,10 +63,13 @@ import org.springframework.core.io.Resource;
* @author Stephane Nicoll
* @author Steffen F. Qvistgaard
* @author Ittay Stern
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 1.3.0
*/
@AutoConfiguration
@ConditionalOnClass({ CqlSession.class })
@ConditionalOnClass(CqlSession.class)
@EnableConfigurationProperties(CassandraProperties.class)
public class CassandraAutoConfiguration {
@@ -80,6 +82,17 @@ public class CassandraAutoConfiguration {
SPRING_BOOT_DEFAULTS = options.build();
}
private final CassandraProperties properties;
private final CassandraConnectionDetails connectionDetails;
CassandraAutoConfiguration(CassandraProperties properties,
ObjectProvider<CassandraConnectionDetails> connectionDetails) {
this.properties = properties;
this.connectionDetails = connectionDetails
.getIfAvailable(() -> new PropertiesCassandraConnectionDetails(properties));
}
@Bean
@ConditionalOnMissingBean
@Lazy
@@ -90,24 +103,25 @@ public class CassandraAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@Scope("prototype")
public CqlSessionBuilder cassandraSessionBuilder(CassandraProperties properties,
DriverConfigLoader driverConfigLoader, ObjectProvider<CqlSessionBuilderCustomizer> builderCustomizers) {
public CqlSessionBuilder cassandraSessionBuilder(DriverConfigLoader driverConfigLoader,
ObjectProvider<CqlSessionBuilderCustomizer> builderCustomizers) {
CqlSessionBuilder builder = CqlSession.builder().withConfigLoader(driverConfigLoader);
configureAuthentication(properties, builder);
configureSsl(properties, builder);
builder.withKeyspace(properties.getKeyspaceName());
configureAuthentication(builder);
configureSsl(builder);
builder.withKeyspace(this.properties.getKeyspaceName());
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;
}
private void configureAuthentication(CassandraProperties properties, CqlSessionBuilder builder) {
if (properties.getUsername() != null) {
builder.withAuthCredentials(properties.getUsername(), properties.getPassword());
private void configureAuthentication(CqlSessionBuilder builder) {
String username = this.connectionDetails.getUsername();
if (username != null) {
builder.withAuthCredentials(username, this.connectionDetails.getPassword());
}
}
private void configureSsl(CassandraProperties properties, CqlSessionBuilder builder) {
if (properties.isSsl()) {
private void configureSsl(CqlSessionBuilder builder) {
if (this.connectionDetails instanceof PropertiesCassandraConnectionDetails && this.properties.isSsl()) {
try {
builder.withSslContext(SSLContext.getDefault());
}
@@ -119,20 +133,20 @@ public class CassandraAutoConfiguration {
@Bean(destroyMethod = "")
@ConditionalOnMissingBean
public DriverConfigLoader cassandraDriverConfigLoader(CassandraProperties properties,
public DriverConfigLoader cassandraDriverConfigLoader(
ObjectProvider<DriverConfigLoaderBuilderCustomizer> builderCustomizers) {
ProgrammaticDriverConfigLoaderBuilder builder = new DefaultProgrammaticDriverConfigLoaderBuilder(
() -> cassandraConfiguration(properties), DefaultDriverConfigLoader.DEFAULT_ROOT_PATH);
() -> cassandraConfiguration(), DefaultDriverConfigLoader.DEFAULT_ROOT_PATH);
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
private Config cassandraConfiguration(CassandraProperties properties) {
private Config cassandraConfiguration() {
ConfigFactory.invalidateCaches();
Config config = ConfigFactory.defaultOverrides();
config = config.withFallback(mapConfig(properties));
if (properties.getConfig() != null) {
config = config.withFallback(loadConfig(properties.getConfig()));
config = config.withFallback(mapConfig());
if (this.properties.getConfig() != null) {
config = config.withFallback(loadConfig(this.properties.getConfig()));
}
config = config.withFallback(SPRING_BOOT_DEFAULTS);
config = config.withFallback(ConfigFactory.defaultReference());
@@ -148,32 +162,32 @@ public class CassandraAutoConfiguration {
}
}
private Config mapConfig(CassandraProperties properties) {
private Config mapConfig() {
CassandraDriverOptions options = new CassandraDriverOptions();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties.getSessionName())
map.from(this.properties.getSessionName())
.whenHasText()
.to((sessionName) -> options.add(DefaultDriverOption.SESSION_NAME, sessionName));
map.from(properties::getUsername)
.to((username) -> options.add(DefaultDriverOption.AUTH_PROVIDER_USER_NAME, username)
.add(DefaultDriverOption.AUTH_PROVIDER_PASSWORD, properties.getPassword()));
map.from(properties::getCompression)
map.from(this.connectionDetails.getUsername())
.to((value) -> options.add(DefaultDriverOption.AUTH_PROVIDER_USER_NAME, value)
.add(DefaultDriverOption.AUTH_PROVIDER_PASSWORD, this.connectionDetails.getPassword()));
map.from(this.properties::getCompression)
.to((compression) -> options.add(DefaultDriverOption.PROTOCOL_COMPRESSION, compression));
mapConnectionOptions(properties, options);
mapPoolingOptions(properties, options);
mapRequestOptions(properties, options);
mapControlConnectionOptions(properties, options);
map.from(mapContactPoints(properties))
mapConnectionOptions(options);
mapPoolingOptions(options);
mapRequestOptions(options);
mapControlConnectionOptions(options);
map.from(mapContactPoints())
.to((contactPoints) -> options.add(DefaultDriverOption.CONTACT_POINTS, contactPoints));
map.from(properties.getLocalDatacenter())
map.from(this.connectionDetails.getLocalDatacenter())
.whenHasText()
.to((localDatacenter) -> options.add(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, localDatacenter));
return options.build();
}
private void mapConnectionOptions(CassandraProperties properties, CassandraDriverOptions options) {
private void mapConnectionOptions(CassandraDriverOptions options) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
Connection connectionProperties = properties.getConnection();
Connection connectionProperties = this.properties.getConnection();
map.from(connectionProperties::getConnectTimeout)
.asInt(Duration::toMillis)
.to((connectTimeout) -> options.add(DefaultDriverOption.CONNECTION_CONNECT_TIMEOUT, connectTimeout));
@@ -182,9 +196,9 @@ public class CassandraAutoConfiguration {
.to((initQueryTimeout) -> options.add(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, initQueryTimeout));
}
private void mapPoolingOptions(CassandraProperties properties, CassandraDriverOptions options) {
private void mapPoolingOptions(CassandraDriverOptions options) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
CassandraProperties.Pool poolProperties = properties.getPool();
CassandraProperties.Pool poolProperties = this.properties.getPool();
map.from(poolProperties::getIdleTimeout)
.asInt(Duration::toMillis)
.to((idleTimeout) -> options.add(DefaultDriverOption.HEARTBEAT_TIMEOUT, idleTimeout));
@@ -193,9 +207,9 @@ public class CassandraAutoConfiguration {
.to((heartBeatInterval) -> options.add(DefaultDriverOption.HEARTBEAT_INTERVAL, heartBeatInterval));
}
private void mapRequestOptions(CassandraProperties properties, CassandraDriverOptions options) {
private void mapRequestOptions(CassandraDriverOptions options) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
Request requestProperties = properties.getRequest();
Request requestProperties = this.properties.getRequest();
map.from(requestProperties::getTimeout)
.asInt(Duration::toMillis)
.to(((timeout) -> options.add(DefaultDriverOption.REQUEST_TIMEOUT, timeout)));
@@ -222,40 +236,19 @@ public class CassandraAutoConfiguration {
.to((drainInterval) -> options.add(DefaultDriverOption.REQUEST_THROTTLER_DRAIN_INTERVAL, drainInterval));
}
private void mapControlConnectionOptions(CassandraProperties properties, CassandraDriverOptions options) {
private void mapControlConnectionOptions(CassandraDriverOptions options) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
Controlconnection controlProperties = properties.getControlconnection();
Controlconnection controlProperties = this.properties.getControlconnection();
map.from(controlProperties::getTimeout)
.asInt(Duration::toMillis)
.to((timeout) -> options.add(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT, timeout));
}
private List<String> mapContactPoints(CassandraProperties properties) {
if (properties.getContactPoints() != null) {
return properties.getContactPoints()
.stream()
.map((candidate) -> formatContactPoint(candidate, properties.getPort()))
.toList();
}
return null;
}
private String formatContactPoint(String candidate, int port) {
int i = candidate.lastIndexOf(':');
if (i == -1 || !isPort(() -> candidate.substring(i + 1))) {
return String.format("%s:%s", candidate, port);
}
return candidate;
}
private boolean isPort(Supplier<String> value) {
try {
int i = Integer.parseInt(value.get());
return i > 0 && i < 65535;
}
catch (Exception ex) {
return false;
}
private List<String> mapContactPoints() {
return this.connectionDetails.getContactPoints()
.stream()
.map((node) -> node.host() + ":" + node.port())
.toList();
}
private static class CassandraDriverOptions {
@@ -293,4 +286,61 @@ public class CassandraAutoConfiguration {
}
/**
* Adapts {@link CassandraProperties} to {@link CassandraConnectionDetails}.
*/
private static final class PropertiesCassandraConnectionDetails implements CassandraConnectionDetails {
private final CassandraProperties properties;
private PropertiesCassandraConnectionDetails(CassandraProperties properties) {
this.properties = properties;
}
@Override
public List<Node> getContactPoints() {
List<String> contactPoints = this.properties.getContactPoints();
return (contactPoints != null) ? contactPoints.stream().map(this::asNode).toList()
: Collections.emptyList();
}
@Override
public String getUsername() {
return this.properties.getUsername();
}
@Override
public String getPassword() {
return this.properties.getPassword();
}
@Override
public String getLocalDatacenter() {
return this.properties.getLocalDatacenter();
}
private Node asNode(String contactPoint) {
int i = contactPoint.lastIndexOf(':');
if (i >= 0) {
String portCandidate = contactPoint.substring(i + 1);
Integer port = asPort(portCandidate);
if (port != null) {
return new Node(contactPoint.substring(0, i), port);
}
}
return new Node(contactPoint, this.properties.getPort());
}
private Integer asPort(String value) {
try {
int i = Integer.parseInt(value);
return (i > 0 && i < 65535) ? i : null;
}
catch (Exception ex) {
return null;
}
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.cassandra;
import java.util.List;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to a Cassandra service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface CassandraConnectionDetails extends ConnectionDetails {
/**
* Cluster node addresses.
* @return the cluster node addresses
*/
List<Node> getContactPoints();
/**
* Login user of the server.
* @return the login user of the server or {@code null}
*/
default String getUsername() {
return null;
}
/**
* Login password of the server.
* @return the login password of the server or {@code null}
*/
default String getPassword() {
return null;
}
/**
* Datacenter that is considered "local". Contact points should be from this
* datacenter.
* @return the datacenter that is considered "local"
*/
String getLocalDatacenter();
/**
* A Cassandra node.
*
* @param host the hostname
* @param port the port
*/
record Node(String host, int port) {
}
}

View File

@@ -34,14 +34,18 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
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.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.CouchbaseCondition;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Timeouts;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.util.ResourceUtils;
@@ -52,34 +56,49 @@ import org.springframework.util.ResourceUtils;
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Yulin Qin
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 1.4.0
*/
@AutoConfiguration(after = JacksonAutoConfiguration.class)
@ConditionalOnClass(Cluster.class)
@ConditionalOnProperty("spring.couchbase.connection-string")
@Conditional(CouchbaseCondition.class)
@EnableConfigurationProperties(CouchbaseProperties.class)
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
public ClusterEnvironment couchbaseClusterEnvironment(CouchbaseProperties properties,
public ClusterEnvironment couchbaseClusterEnvironment(
ObjectProvider<ClusterEnvironmentBuilderCustomizer> customizers) {
Builder builder = initializeEnvironmentBuilder(properties);
Builder builder = initializeEnvironmentBuilder();
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
@Bean(destroyMethod = "disconnect")
@ConditionalOnMissingBean
public Cluster couchbaseCluster(CouchbaseProperties properties, ClusterEnvironment couchbaseClusterEnvironment) {
ClusterOptions options = ClusterOptions.clusterOptions(properties.getUsername(), properties.getPassword())
public Cluster couchbaseCluster(ClusterEnvironment couchbaseClusterEnvironment) {
ClusterOptions options = ClusterOptions
.clusterOptions(this.connectionDetails.getUsername(), this.connectionDetails.getPassword())
.environment(couchbaseClusterEnvironment);
return Cluster.connect(properties.getConnectionString(), options);
return Cluster.connect(this.connectionDetails.getConnectionString(), options);
}
private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbaseProperties properties) {
private ClusterEnvironment.Builder initializeEnvironmentBuilder() {
ClusterEnvironment.Builder builder = ClusterEnvironment.builder();
Timeouts timeouts = properties.getEnv().getTimeouts();
Timeouts timeouts = this.properties.getEnv().getTimeouts();
builder.timeoutConfig((config) -> config.kvTimeout(timeouts.getKeyValue())
.analyticsTimeout(timeouts.getAnalytics())
.kvDurableTimeout(timeouts.getKeyValueDurable())
@@ -89,13 +108,14 @@ public class CouchbaseAutoConfiguration {
.managementTimeout(timeouts.getManagement())
.connectTimeout(timeouts.getConnect())
.disconnectTimeout(timeouts.getDisconnect()));
CouchbaseProperties.Io io = properties.getEnv().getIo();
CouchbaseProperties.Io io = this.properties.getEnv().getIo();
builder.ioConfig((config) -> config.maxHttpConnections(io.getMaxEndpoints())
.numKvConnections(io.getMinEndpoints())
.idleHttpConnectionTimeout(io.getIdleHttpConnectionTimeout()));
if (properties.getEnv().getSsl().getEnabled()) {
if ((this.connectionDetails instanceof PropertiesCouchbaseConnectionDetails)
&& this.properties.getEnv().getSsl().getEnabled()) {
builder.securityConfig((config) -> config.enableTls(true)
.trustManagerFactory(getTrustManagerFactory(properties.getEnv().getSsl())));
.trustManagerFactory(getTrustManagerFactory(this.properties.getEnv().getSsl())));
}
return builder;
}
@@ -157,4 +177,54 @@ public class CouchbaseAutoConfiguration {
}
/**
* Condition that matches when {@code spring.couchbase.connection-string} has been
* configured or there is a {@link CouchbaseConnectionDetails} bean.
*/
static final class CouchbaseCondition extends AnyNestedCondition {
CouchbaseCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(prefix = "spring.couchbase", name = "connection-string")
private static final class CouchbaseUrlCondition {
}
@ConditionalOnBean(CouchbaseConnectionDetails.class)
private static final class CouchbaseConnectionDetailsCondition {
}
}
/**
* Adapts {@link CouchbaseProperties} to {@link CouchbaseConnectionDetails}.
*/
static final class PropertiesCouchbaseConnectionDetails implements CouchbaseConnectionDetails {
private final CouchbaseProperties properties;
PropertiesCouchbaseConnectionDetails(CouchbaseProperties properties) {
this.properties = properties;
}
@Override
public String getConnectionString() {
return this.properties.getConnectionString();
}
@Override
public String getUsername() {
return this.properties.getUsername();
}
@Override
public String getPassword() {
return this.properties.getPassword();
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.couchbase;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to a Couchbase service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface CouchbaseConnectionDetails extends ConnectionDetails {
/**
* Connection string used to locate the Couchbase cluster.
* @return the connection string used to locate the Couchbase cluster
*/
String getConnectionString();
/**
* Cluster username.
* @return the cluster username
*/
String getUsername();
/**
* Cluster password.
* @return the cluster password
*/
String getPassword();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 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.
@@ -18,9 +18,12 @@ 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;
@@ -32,6 +35,8 @@ import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Phillip Webb
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(MongoDatabaseFactory.class)
@@ -39,8 +44,12 @@ import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
class MongoDatabaseFactoryConfiguration {
@Bean
MongoDatabaseFactorySupport<?> mongoDatabaseFactory(MongoClient mongoClient, MongoProperties properties) {
return new SimpleMongoClientDatabaseFactory(mongoClient, properties.getMongoClientDatabase());
MongoDatabaseFactorySupport<?> mongoDatabaseFactory(MongoClient mongoClient, MongoProperties properties,
ObjectProvider<MongoConnectionDetails> connectionDetails) {
return new SimpleMongoClientDatabaseFactory(mongoClient,
connectionDetails.getIfAvailable(() -> new PropertiesMongoConnectionDetails(properties))
.getConnectionString()
.getDatabase());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 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.
@@ -20,10 +20,14 @@ 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;
@@ -46,17 +50,13 @@ import org.springframework.util.StringUtils;
* Configuration for Mongo-related beans that depend on a {@link MongoDatabaseFactory}.
*
* @author Andy Wilkinson
* @author Moritz Halbritter
* @author Phillip Webb
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(MongoDatabaseFactory.class)
class MongoDatabaseFactoryDependentConfiguration {
private final MongoProperties properties;
MongoDatabaseFactoryDependentConfiguration(MongoProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean(MongoOperations.class)
MongoTemplate mongoTemplate(MongoDatabaseFactory factory, MongoConverter converter) {
@@ -75,31 +75,36 @@ class MongoDatabaseFactoryDependentConfiguration {
@Bean
@ConditionalOnMissingBean(GridFsOperations.class)
GridFsTemplate gridFsTemplate(MongoDatabaseFactory factory, MongoTemplate mongoTemplate) {
return new GridFsTemplate(new GridFsMongoDatabaseFactory(factory, this.properties),
mongoTemplate.getConverter(), this.properties.getGridfs().getBucket());
GridFsTemplate gridFsTemplate(MongoProperties properties, MongoDatabaseFactory factory, MongoTemplate mongoTemplate,
ObjectProvider<MongoConnectionDetails> connectionDetailsProvider) {
MongoConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesMongoConnectionDetails(properties));
return new GridFsTemplate(new GridFsMongoDatabaseFactory(factory, connectionDetails),
mongoTemplate.getConverter(),
(connectionDetails.getGridFs() != null) ? connectionDetails.getGridFs().getBucket() : null);
}
/**
* {@link MongoDatabaseFactory} decorator to respect {@link Gridfs#getDatabase()} if
* set.
* {@link MongoDatabaseFactory} decorator to respect {@link Gridfs#getDatabase()} or
* {@link GridFs#getGridFs()} from the {@link MongoConnectionDetails} if set.
*/
static class GridFsMongoDatabaseFactory implements MongoDatabaseFactory {
private final MongoDatabaseFactory mongoDatabaseFactory;
private final MongoProperties properties;
private final MongoConnectionDetails connectionDetails;
GridFsMongoDatabaseFactory(MongoDatabaseFactory mongoDatabaseFactory, MongoProperties properties) {
GridFsMongoDatabaseFactory(MongoDatabaseFactory mongoDatabaseFactory,
MongoConnectionDetails connectionDetails) {
Assert.notNull(mongoDatabaseFactory, "MongoDatabaseFactory must not be null");
Assert.notNull(properties, "Properties must not be null");
Assert.notNull(connectionDetails, "ConnectionDetails must not be null");
this.mongoDatabaseFactory = mongoDatabaseFactory;
this.properties = properties;
this.connectionDetails = connectionDetails;
}
@Override
public MongoDatabase getMongoDatabase() throws DataAccessException {
String gridFsDatabase = this.properties.getGridfs().getDatabase();
String gridFsDatabase = getGridFsDatabase(this.connectionDetails);
if (StringUtils.hasText(gridFsDatabase)) {
return this.mongoDatabaseFactory.getMongoDatabase(gridFsDatabase);
}
@@ -126,6 +131,10 @@ class MongoDatabaseFactoryDependentConfiguration {
return this.mongoDatabaseFactory.withSession(session);
}
private String getGridFsDatabase(MongoConnectionDetails connectionDetails) {
return (connectionDetails.getGridFs() != null) ? connectionDetails.getGridFs().getDatabase() : null;
}
}
}

View File

@@ -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.
@@ -26,14 +26,17 @@ 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;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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.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;
@@ -60,12 +63,12 @@ import org.springframework.util.StringUtils;
* <p>
* Registers a {@link ReactiveMongoTemplate} bean if no other bean of the same type is
* configured.
* <p>
* Honors the {@literal spring.data.mongodb.database} property if set, otherwise connects
* to the {@literal test} database.
*
* @author Mark Paluch
* @author Artsiom Yudovin
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 2.0.0
*/
@AutoConfiguration(after = MongoReactiveAutoConfiguration.class)
@@ -75,12 +78,19 @@ import org.springframework.util.StringUtils;
@Import(MongoDataConfiguration.class)
public class MongoReactiveDataAutoConfiguration {
private final MongoConnectionDetails connectionDetails;
MongoReactiveDataAutoConfiguration(MongoProperties properties,
ObjectProvider<MongoConnectionDetails> connectionDetails) {
this.connectionDetails = connectionDetails
.getIfAvailable(() -> new PropertiesMongoConnectionDetails(properties));
}
@Bean
@ConditionalOnMissingBean(ReactiveMongoDatabaseFactory.class)
public SimpleReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory(MongoProperties properties,
MongoClient mongo) {
String database = properties.getMongoClientDatabase();
return new SimpleReactiveMongoDatabaseFactory(mongo, database);
public SimpleReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory(MongoClient mongo) {
return new SimpleReactiveMongoDatabaseFactory(mongo,
this.connectionDetails.getConnectionString().getDatabase());
}
@Bean
@@ -108,26 +118,27 @@ public class MongoReactiveDataAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ReactiveGridFsOperations.class)
public ReactiveGridFsTemplate reactiveGridFsTemplate(ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory,
MappingMongoConverter mappingMongoConverter, DataBufferFactory dataBufferFactory,
MongoProperties properties) {
MappingMongoConverter mappingMongoConverter, DataBufferFactory dataBufferFactory) {
return new ReactiveGridFsTemplate(dataBufferFactory,
new GridFsReactiveMongoDatabaseFactory(reactiveMongoDatabaseFactory, properties), mappingMongoConverter,
properties.getGridfs().getBucket());
new GridFsReactiveMongoDatabaseFactory(reactiveMongoDatabaseFactory, this.connectionDetails),
mappingMongoConverter,
(this.connectionDetails.getGridFs() != null) ? this.connectionDetails.getGridFs().getBucket() : null);
}
/**
* {@link ReactiveMongoDatabaseFactory} decorator to use {@link Gridfs#getDatabase()}
* when set.
* {@link ReactiveMongoDatabaseFactory} decorator to use {@link GridFs#getGridFs()}
* from the {@link MongoConnectionDetails} when set.
*/
static class GridFsReactiveMongoDatabaseFactory implements ReactiveMongoDatabaseFactory {
private final ReactiveMongoDatabaseFactory delegate;
private final MongoProperties properties;
private final MongoConnectionDetails connectionDetails;
GridFsReactiveMongoDatabaseFactory(ReactiveMongoDatabaseFactory delegate, MongoProperties properties) {
GridFsReactiveMongoDatabaseFactory(ReactiveMongoDatabaseFactory delegate,
MongoConnectionDetails connectionDetails) {
this.delegate = delegate;
this.properties = properties;
this.connectionDetails = connectionDetails;
}
@Override
@@ -137,13 +148,17 @@ public class MongoReactiveDataAutoConfiguration {
@Override
public Mono<MongoDatabase> getMongoDatabase() throws DataAccessException {
String gridFsDatabase = this.properties.getGridfs().getDatabase();
String gridFsDatabase = getGridFsDatabase(this.connectionDetails);
if (StringUtils.hasText(gridFsDatabase)) {
return this.delegate.getMongoDatabase(gridFsDatabase);
}
return this.delegate.getMongoDatabase();
}
private String getGridFsDatabase(MongoConnectionDetails connectionDetails) {
return (connectionDetails.getGridFs() != null) ? connectionDetails.getGridFs().getDatabase() : null;
}
@Override
public Mono<MongoDatabase> getMongoDatabase(String dbName) throws DataAccessException {
return this.delegate.getMongoDatabase(dbName);

View File

@@ -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.
@@ -42,6 +42,9 @@ import org.springframework.util.StringUtils;
*
* @author Mark Paluch
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
@@ -52,8 +55,10 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration {
JedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration) {
super(properties, standaloneConfigurationProvider, sentinelConfiguration, clusterConfiguration);
ObjectProvider<RedisClusterConfiguration> clusterConfiguration,
ObjectProvider<RedisConnectionDetails> connectionDetailsProvider) {
super(properties, standaloneConfigurationProvider, sentinelConfiguration, clusterConfiguration,
connectionDetailsProvider);
}
@Bean
@@ -90,7 +95,9 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration {
private JedisClientConfigurationBuilder applyProperties(JedisClientConfigurationBuilder builder) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(getProperties().isSsl()).whenTrue().toCall(builder::useSsl);
boolean ssl = (!(getConnectionDetails() instanceof PropertiesRedisConnectionDetails)) ? false
: getProperties().isSsl();
map.from(ssl).whenTrue().toCall(builder::useSsl);
map.from(getProperties().getTimeout()).to(builder::readTimeout);
map.from(getProperties().getConnectTimeout()).to(builder::connectTimeout);
map.from(getProperties().getClientName()).whenHasText().to(builder::clientName);
@@ -117,8 +124,7 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration {
}
private void customizeConfigurationFromUrl(JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
if (connectionInfo.isUseSsl()) {
if (urlUsesSsl()) {
builder.useSsl();
}
}

View File

@@ -52,6 +52,8 @@ import org.springframework.util.StringUtils;
*
* @author Mark Paluch
* @author Andy Wilkinson
* @author Moritz Halbritter
* @author Phillip Webb
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
@@ -61,8 +63,10 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
LettuceConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
super(properties, standaloneConfigurationProvider, sentinelConfigurationProvider, clusterConfigurationProvider);
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
ObjectProvider<RedisConnectionDetails> connectionDetailsProvider) {
super(properties, standaloneConfigurationProvider, sentinelConfigurationProvider, clusterConfigurationProvider,
connectionDetailsProvider);
}
@Bean(destroyMethod = "shutdown")
@@ -116,7 +120,7 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
private LettuceClientConfigurationBuilder applyProperties(
LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
if (getProperties().isSsl()) {
if (getConnectionDetails() instanceof PropertiesRedisConnectionDetails && getProperties().isSsl()) {
builder.useSsl();
}
if (getProperties().getTimeout() != null) {
@@ -161,8 +165,7 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
}
private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
if (connectionInfo.isUseSsl()) {
if (urlUsesSsl()) {
builder.useSsl();
}
}

View File

@@ -0,0 +1,127 @@
/*
* 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.data.redis;
import java.util.List;
import org.springframework.boot.autoconfigure.data.redis.RedisConnectionConfiguration.ConnectionInfo;
/**
* Adapts {@link RedisProperties} to {@link RedisConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class PropertiesRedisConnectionDetails implements RedisConnectionDetails {
private final RedisProperties properties;
PropertiesRedisConnectionDetails(RedisProperties properties) {
this.properties = properties;
}
@Override
public String getUsername() {
if (this.properties.getUrl() != null) {
ConnectionInfo connectionInfo = connectionInfo(this.properties.getUrl());
String userInfo = connectionInfo.getUri().getUserInfo();
int index = (userInfo != null) ? userInfo.indexOf(':') : -1;
if (index != -1) {
return userInfo.substring(0, index);
}
}
return this.properties.getUsername();
}
@Override
public String getPassword() {
if (this.properties.getUrl() != null) {
ConnectionInfo connectionInfo = connectionInfo(this.properties.getUrl());
String userInfo = connectionInfo.getUri().getUserInfo();
int index = (userInfo != null) ? userInfo.indexOf(':') : -1;
if (index != -1) {
return userInfo.substring(index + 1);
}
}
return this.properties.getPassword();
}
@Override
public Standalone getStandalone() {
if (this.properties.getUrl() != null) {
ConnectionInfo connectionInfo = connectionInfo(this.properties.getUrl());
return Standalone.of(connectionInfo.getUri().getHost(), connectionInfo.getUri().getPort(),
this.properties.getDatabase());
}
return Standalone.of(this.properties.getHost(), this.properties.getPort(), this.properties.getDatabase());
}
private ConnectionInfo connectionInfo(String url) {
return (url != null) ? RedisConnectionConfiguration.parseUrl(url) : null;
}
@Override
public Sentinel getSentinel() {
org.springframework.boot.autoconfigure.data.redis.RedisProperties.Sentinel sentinel = this.properties
.getSentinel();
if (sentinel == null) {
return null;
}
return new Sentinel() {
@Override
public int getDatabase() {
return PropertiesRedisConnectionDetails.this.properties.getDatabase();
}
@Override
public String getMaster() {
return sentinel.getMaster();
}
@Override
public List<Node> getNodes() {
return sentinel.getNodes().stream().map(PropertiesRedisConnectionDetails.this::asNode).toList();
}
@Override
public String getUsername() {
return sentinel.getUsername();
}
@Override
public String getPassword() {
return sentinel.getPassword();
}
};
}
@Override
public Cluster getCluster() {
RedisProperties.Cluster cluster = this.properties.getCluster();
List<Node> nodes = (cluster != null) ? cluster.getNodes().stream().map(this::asNode).toList() : null;
return (nodes != null) ? () -> nodes : null;
}
private Node asNode(String node) {
String[] components = node.split(":");
return new Node(components[0], Integer.parseInt(components[1]));
}
}

View File

@@ -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.
@@ -22,6 +22,9 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Cluster;
import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Node;
import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Sentinel;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
@@ -29,7 +32,6 @@ import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Base Redis connection configuration.
@@ -39,6 +41,9 @@ import org.springframework.util.StringUtils;
* @author Alen Turkovic
* @author Scott Frederick
* @author Eddú Meléndez
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
abstract class RedisConnectionConfiguration {
@@ -53,14 +58,19 @@ abstract class RedisConnectionConfiguration {
private final RedisClusterConfiguration clusterConfiguration;
private final RedisConnectionDetails connectionDetails;
protected RedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
ObjectProvider<RedisConnectionDetails> connectionDetailsProvider) {
this.properties = properties;
this.standaloneConfiguration = standaloneConfigurationProvider.getIfAvailable();
this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
this.connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesRedisConnectionDetails(properties));
}
protected final RedisStandaloneConfiguration getStandaloneConfig() {
@@ -68,20 +78,11 @@ abstract class RedisConnectionConfiguration {
return this.standaloneConfiguration;
}
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
if (StringUtils.hasText(this.properties.getUrl())) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
config.setHostName(connectionInfo.getHostName());
config.setPort(connectionInfo.getPort());
config.setUsername(connectionInfo.getUsername());
config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
}
else {
config.setHostName(this.properties.getHost());
config.setPort(this.properties.getPort());
config.setUsername(this.properties.getUsername());
config.setPassword(RedisPassword.of(this.properties.getPassword()));
}
config.setDatabase(this.properties.getDatabase());
config.setHostName(this.connectionDetails.getStandalone().getHost());
config.setPort(this.connectionDetails.getStandalone().getPort());
config.setUsername(this.connectionDetails.getUsername());
config.setPassword(RedisPassword.of(this.connectionDetails.getPassword()));
config.setDatabase(this.connectionDetails.getStandalone().getDatabase());
return config;
}
@@ -89,20 +90,21 @@ abstract class RedisConnectionConfiguration {
if (this.sentinelConfiguration != null) {
return this.sentinelConfiguration;
}
RedisProperties.Sentinel sentinelProperties = this.properties.getSentinel();
if (sentinelProperties != null) {
if (this.connectionDetails.getSentinel() != null) {
RedisSentinelConfiguration config = new RedisSentinelConfiguration();
config.master(sentinelProperties.getMaster());
config.setSentinels(createSentinels(sentinelProperties));
config.setUsername(this.properties.getUsername());
if (this.properties.getPassword() != null) {
config.setPassword(RedisPassword.of(this.properties.getPassword()));
config.master(this.connectionDetails.getSentinel().getMaster());
config.setSentinels(createSentinels(this.connectionDetails.getSentinel()));
config.setUsername(this.connectionDetails.getUsername());
String password = this.connectionDetails.getPassword();
if (password != null) {
config.setPassword(RedisPassword.of(password));
}
config.setSentinelUsername(sentinelProperties.getUsername());
if (sentinelProperties.getPassword() != null) {
config.setSentinelPassword(RedisPassword.of(sentinelProperties.getPassword()));
config.setSentinelUsername(this.connectionDetails.getSentinel().getUsername());
String sentinelPassword = this.connectionDetails.getSentinel().getPassword();
if (sentinelPassword != null) {
config.setSentinelPassword(RedisPassword.of(sentinelPassword));
}
config.setDatabase(this.properties.getDatabase());
config.setDatabase(this.connectionDetails.getSentinel().getDatabase());
return config;
}
return null;
@@ -116,19 +118,25 @@ abstract class RedisConnectionConfiguration {
if (this.clusterConfiguration != null) {
return this.clusterConfiguration;
}
if (this.properties.getCluster() == null) {
return null;
}
RedisProperties.Cluster clusterProperties = this.properties.getCluster();
RedisClusterConfiguration config = new RedisClusterConfiguration(clusterProperties.getNodes());
if (clusterProperties.getMaxRedirects() != null) {
config.setMaxRedirects(clusterProperties.getMaxRedirects());
if (this.connectionDetails.getCluster() != null) {
RedisClusterConfiguration config = new RedisClusterConfiguration(
getNodes(this.connectionDetails.getCluster()));
if (clusterProperties != null && clusterProperties.getMaxRedirects() != null) {
config.setMaxRedirects(clusterProperties.getMaxRedirects());
}
config.setUsername(this.connectionDetails.getUsername());
String password = this.connectionDetails.getPassword();
if (password != null) {
config.setPassword(RedisPassword.of(password));
}
return config;
}
config.setUsername(this.properties.getUsername());
if (this.properties.getPassword() != null) {
config.setPassword(RedisPassword.of(this.properties.getPassword()));
}
return config;
return null;
}
private List<String> getNodes(Cluster cluster) {
return cluster.getNodes().stream().map((node) -> "%s:%d".formatted(node.host(), node.port())).toList();
}
protected final RedisProperties getProperties() {
@@ -140,20 +148,23 @@ abstract class RedisConnectionConfiguration {
return (enabled != null) ? enabled : COMMONS_POOL2_AVAILABLE;
}
private List<RedisNode> createSentinels(RedisProperties.Sentinel sentinel) {
private List<RedisNode> createSentinels(Sentinel sentinel) {
List<RedisNode> nodes = new ArrayList<>();
for (String node : sentinel.getNodes()) {
try {
nodes.add(RedisNode.fromString(node));
}
catch (RuntimeException ex) {
throw new IllegalStateException("Invalid redis sentinel property '" + node + "'", ex);
}
for (Node node : sentinel.getNodes()) {
nodes.add(new RedisNode(node.host(), node.port()));
}
return nodes;
}
protected ConnectionInfo parseUrl(String url) {
protected final boolean urlUsesSsl() {
return parseUrl(this.properties.getUrl()).isUseSsl();
}
protected final RedisConnectionDetails getConnectionDetails() {
return this.connectionDetails;
}
static ConnectionInfo parseUrl(String url) {
try {
URI uri = new URI(url);
String scheme = uri.getScheme();
@@ -198,18 +209,14 @@ abstract class RedisConnectionConfiguration {
this.password = password;
}
URI getUri() {
return this.uri;
}
boolean isUseSsl() {
return this.useSsl;
}
String getHostName() {
return this.uri.getHost();
}
int getPort() {
return this.uri.getPort();
}
String getUsername() {
return this.username;
}

View File

@@ -0,0 +1,190 @@
/*
* 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.data.redis;
import java.util.List;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.util.Assert;
/**
* Details required to establish a connection to a Redis service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @since 3.1.0
*/
public interface RedisConnectionDetails extends ConnectionDetails {
/**
* Login username of the redis server.
* @return the login username of the redis server
*/
default String getUsername() {
return null;
}
/**
* Login password of the redis server.
* @return the login password of the redis server
*/
default String getPassword() {
return null;
}
/**
* Redis standalone configuration. Mutually exclusive with {@link #getSentinel()} and
* {@link #getCluster()}.
* @return the Redis standalone configuration
*/
default Standalone getStandalone() {
return null;
}
/**
* Redis sentinel configuration. Mutually exclusive with {@link #getStandalone()} and
* {@link #getCluster()}.
* @return the Redis sentinel configuration
*/
default Sentinel getSentinel() {
return null;
}
/**
* Redis cluster configuration. Mutually exclusive with {@link #getStandalone()} and
* {@link #getSentinel()}.
* @return the Redis cluster configuration
*/
default Cluster getCluster() {
return null;
}
/**
* Redis standalone configuration.
*/
interface Standalone {
/**
* Redis server host.
* @return the redis server host
*/
String getHost();
/**
* Redis server port.
* @return the redis server port
*/
int getPort();
/**
* Database index used by the connection factory.
* @return the database index used by the connection factory
*/
default int getDatabase() {
return 0;
}
static Standalone of(String host, int port) {
return of(host, port, 0);
}
static Standalone of(String host, int port, int database) {
Assert.hasLength(host, "Host must not be empty");
return new Standalone() {
@Override
public String getHost() {
return host;
}
@Override
public int getPort() {
return port;
}
@Override
public int getDatabase() {
return database;
}
};
}
}
/**
* Redis sentinel configuration.
*/
interface Sentinel {
/**
* Database index used by the connection factory.
* @return the database index used by the connection factory
*/
int getDatabase();
/**
* Name of the Redis server.
* @return the name of the Redis server
*/
String getMaster();
/**
* List of nodes.
* @return the list of nodes
*/
List<Node> getNodes();
/**
* Login username for authenticating with sentinel(s).
* @return the login username for authenticating with sentinel(s) or {@code null}
*/
String getUsername();
/**
* Password for authenticating with sentinel(s).
* @return the password for authenticating with sentinel(s) or {@code null}
*/
String getPassword();
}
/**
* Redis cluster configuration.
*/
interface Cluster {
/**
* Nodes to bootstrap from. This represents an "initial" list of cluster nodes and
* is required to have at least one entry.
* @return nodes to bootstrap from
*/
List<Node> getNodes();
}
/**
* A node in a sentinel or cluster configuration.
*
* @param host the hostname of the node
* @param port the port of the node
*/
record Node(String host, int port) {
}
}

View File

@@ -0,0 +1,135 @@
/*
* 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.elasticsearch;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to an Elasticsearch service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface ElasticsearchConnectionDetails extends ConnectionDetails {
/**
* List of the Elasticsearch nodes to use.
* @return list of the Elasticsearch nodes to use
*/
List<Node> getNodes();
/**
* Username for authentication with Elasticsearch.
* @return username for authentication with Elasticsearch or {@code null}
*/
default String getUsername() {
return null;
}
/**
* Password for authentication with Elasticsearch.
* @return password for authentication with Elasticsearch or {@code null}
*/
default String getPassword() {
return null;
}
/**
* Prefix added to the path of every request sent to Elasticsearch.
* @return prefix added to the path of every request sent to Elasticsearch or
* {@code null}
*/
default String getPathPrefix() {
return null;
}
/**
* An Elasticsearch node.
*
* @param hostname the hostname
* @param port the port
* @param protocol the protocol
* @param username the username or {@code null}
* @param password the password or {@code null}
*/
record Node(String hostname, int port, Node.Protocol protocol, String username, String password) {
public Node(String host, int port, Node.Protocol protocol) {
this(host, port, protocol, null, null);
}
URI toUri() {
try {
return new URI(this.protocol.getScheme(), userInfo(), this.hostname, this.port, null, null, null);
}
catch (URISyntaxException ex) {
throw new IllegalStateException("Can't construct URI", ex);
}
}
private String userInfo() {
if (this.username == null) {
return null;
}
return (this.password != null) ? (this.username + ":" + this.password) : this.username;
}
/**
* Connection protocol.
*/
public enum Protocol {
/**
* HTTP.
*/
HTTP("http"),
/**
* HTTPS.
*/
HTTPS("https");
private final String scheme;
Protocol(String scheme) {
this.scheme = scheme;
}
String getScheme() {
return this.scheme;
}
static Protocol forScheme(String scheme) {
for (Protocol protocol : values()) {
if (protocol.scheme.equals(scheme)) {
return protocol;
}
}
throw new IllegalArgumentException("Unknown scheme '" + scheme + "'");
}
}
}
}

View File

@@ -17,8 +17,9 @@
package org.springframework.boot.autoconfigure.elasticsearch;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.List;
import java.util.stream.Stream;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
@@ -37,6 +38,8 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node.Protocol;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -47,6 +50,9 @@ import org.springframework.util.StringUtils;
*
* @author Stephane Nicoll
* @author Filip Hrisafov
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class ElasticsearchRestClientConfigurations {
@@ -56,20 +62,27 @@ class ElasticsearchRestClientConfigurations {
private final ElasticsearchProperties properties;
RestClientBuilderConfiguration(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);
return new DefaultRestClientBuilderCustomizer(this.properties, this.connectionDetails);
}
@Bean
RestClientBuilder elasticsearchRestClientBuilder(
ObjectProvider<RestClientBuilderCustomizer> builderCustomizers) {
HttpHost[] hosts = this.properties.getUris().stream().map(this::createHttpHost).toArray(HttpHost[]::new);
RestClientBuilder builder = RestClient.builder(hosts);
RestClientBuilder builder = RestClient.builder(this.connectionDetails.getNodes()
.stream()
.map((node) -> new HttpHost(node.hostname(), node.port(), node.protocol().getScheme()))
.toArray(HttpHost[]::new));
builder.setHttpClientConfigCallback((httpClientBuilder) -> {
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder));
return httpClientBuilder;
@@ -78,36 +91,14 @@ class ElasticsearchRestClientConfigurations {
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(requestConfigBuilder));
return requestConfigBuilder;
});
if (this.properties.getPathPrefix() != null) {
builder.setPathPrefix(this.properties.getPathPrefix());
String pathPrefix = this.connectionDetails.getPathPrefix();
if (pathPrefix != null) {
builder.setPathPrefix(pathPrefix);
}
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;
}
private HttpHost createHttpHost(String uri) {
try {
return createHttpHost(URI.create(uri));
}
catch (IllegalArgumentException ex) {
return HttpHost.create(uri);
}
}
private HttpHost createHttpHost(URI uri) {
if (!StringUtils.hasLength(uri.getUserInfo())) {
return HttpHost.create(uri.toString());
}
try {
return HttpHost.create(new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(),
uri.getQuery(), uri.getFragment())
.toString());
}
catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
}
}
}
@Configuration(proxyBeanMethods = false)
@@ -146,8 +137,12 @@ class ElasticsearchRestClientConfigurations {
private final ElasticsearchProperties properties;
DefaultRestClientBuilderCustomizer(ElasticsearchProperties properties) {
private final ElasticsearchConnectionDetails connectionDetails;
DefaultRestClientBuilderCustomizer(ElasticsearchProperties properties,
ElasticsearchConnectionDetails connectionDetails) {
this.properties = properties;
this.connectionDetails = connectionDetails;
}
@Override
@@ -156,7 +151,7 @@ class ElasticsearchRestClientConfigurations {
@Override
public void customize(HttpAsyncClientBuilder builder) {
builder.setDefaultCredentialsProvider(new PropertiesCredentialsProvider(this.properties));
builder.setDefaultCredentialsProvider(new ConnectionDetailsCredentialsProvider(this.connectionDetails));
map.from(this.properties::isSocketKeepAlive)
.to((keepAlive) -> builder
.setDefaultIOReactorConfig(IOReactorConfig.custom().setSoKeepAlive(keepAlive).build()));
@@ -176,28 +171,20 @@ class ElasticsearchRestClientConfigurations {
}
private static class PropertiesCredentialsProvider extends BasicCredentialsProvider {
private static class ConnectionDetailsCredentialsProvider extends BasicCredentialsProvider {
PropertiesCredentialsProvider(ElasticsearchProperties properties) {
if (StringUtils.hasText(properties.getUsername())) {
Credentials credentials = new UsernamePasswordCredentials(properties.getUsername(),
properties.getPassword());
ConnectionDetailsCredentialsProvider(ElasticsearchConnectionDetails connectionDetails) {
String username = connectionDetails.getUsername();
if (StringUtils.hasText(username)) {
Credentials credentials = new UsernamePasswordCredentials(username, connectionDetails.getPassword());
setCredentials(AuthScope.ANY, credentials);
}
properties.getUris()
.stream()
.map(this::toUri)
.filter(this::hasUserInfo)
.forEach(this::addUserInfoCredentials);
Stream<URI> uris = getUris(connectionDetails);
uris.filter(this::hasUserInfo).forEach(this::addUserInfoCredentials);
}
private URI toUri(String uri) {
try {
return URI.create(uri);
}
catch (IllegalArgumentException ex) {
return null;
}
private Stream<URI> getUris(ElasticsearchConnectionDetails connectionDetails) {
return connectionDetails.getNodes().stream().map(Node::toUri);
}
private boolean hasUserInfo(URI uri) {
@@ -222,4 +209,59 @@ class ElasticsearchRestClientConfigurations {
}
/**
* Adapts {@link ElasticsearchProperties} to {@link ElasticsearchConnectionDetails}.
*/
private static class PropertiesElasticsearchConnectionDetails implements ElasticsearchConnectionDetails {
private final ElasticsearchProperties properties;
PropertiesElasticsearchConnectionDetails(ElasticsearchProperties properties) {
this.properties = properties;
}
@Override
public List<Node> getNodes() {
return this.properties.getUris().stream().map(this::createNode).toList();
}
@Override
public String getUsername() {
return this.properties.getUsername();
}
@Override
public String getPassword() {
return this.properties.getPassword();
}
@Override
public String getPathPrefix() {
return this.properties.getPathPrefix();
}
private Node createNode(String uri) {
if (!(uri.startsWith("http://") || uri.startsWith("https://"))) {
uri = "http://" + uri;
}
return createNode(URI.create(uri));
}
private Node createNode(URI uri) {
String userInfo = uri.getUserInfo();
Protocol protocol = Protocol.forScheme(uri.getScheme());
if (!StringUtils.hasLength(userInfo)) {
return new Node(uri.getHost(), uri.getPort(), protocol, null, null);
}
int separatorIndex = userInfo.indexOf(':');
if (separatorIndex == -1) {
return new Node(uri.getHost(), uri.getPort(), protocol, userInfo, null);
}
String[] components = userInfo.split(":");
return new Node(uri.getHost(), uri.getPort(), protocol, components[0],
(components.length > 1) ? components[1] : "");
}
}
}

View File

@@ -47,6 +47,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
@@ -86,6 +87,7 @@ import org.springframework.util.StringUtils;
* @author Semyon Danilov
* @author Chris Bono
* @author Moritz Halbritter
* @author Andy Wilkinson
* @since 1.1.0
*/
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
@@ -125,7 +127,7 @@ public class FlywayAutoConfiguration {
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
return flyway(properties, resourceLoader, dataSource, flywayDataSource, fluentConfigurationCustomizers,
javaMigrations, callbacks, new ResourceProviderCustomizer());
javaMigrations, callbacks, new ResourceProviderCustomizer(), null);
}
@Bean
@@ -133,9 +135,15 @@ public class FlywayAutoConfiguration {
@FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks,
ResourceProviderCustomizer resourceProviderCustomizer) {
ResourceProviderCustomizer resourceProviderCustomizer,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
configureDataSource(configuration, properties, flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
JdbcConnectionDetails connectionDetails = (connectionDetailsProvider != null)
? connectionDetailsProvider.getIfAvailable() : null;
connectionDetails = (connectionDetails != null) ? connectionDetails
: new FlywayPropertiesJdbcConnectionDetails(properties);
configureDataSource(configuration, flywayDataSource.getIfAvailable(), dataSource.getIfUnique(),
connectionDetails);
configureProperties(configuration, properties);
configureCallbacks(configuration, callbacks.orderedStream().toList());
configureJavaMigrations(configuration, javaMigrations.orderedStream().toList());
@@ -144,38 +152,41 @@ public class FlywayAutoConfiguration {
return configuration.load();
}
private void configureDataSource(FluentConfiguration configuration, FlywayProperties properties,
DataSource flywayDataSource, DataSource dataSource) {
DataSource migrationDataSource = getMigrationDataSource(properties, flywayDataSource, dataSource);
private void configureDataSource(FluentConfiguration configuration, DataSource flywayDataSource,
DataSource dataSource, JdbcConnectionDetails connectionDetails) {
DataSource migrationDataSource = getMigrationDataSource(flywayDataSource, dataSource, connectionDetails);
configuration.dataSource(migrationDataSource);
}
private DataSource getMigrationDataSource(FlywayProperties properties, DataSource flywayDataSource,
DataSource dataSource) {
private DataSource getMigrationDataSource(DataSource flywayDataSource, DataSource dataSource,
JdbcConnectionDetails connectionDetails) {
if (flywayDataSource != null) {
return flywayDataSource;
}
if (properties.getUrl() != null) {
String url = connectionDetails.getJdbcUrl();
if (url != null) {
DataSourceBuilder<?> builder = DataSourceBuilder.create().type(SimpleDriverDataSource.class);
builder.url(properties.getUrl());
applyCommonBuilderProperties(properties, builder);
builder.url(url);
applyConnectionDetails(connectionDetails, builder);
return builder.build();
}
if (properties.getUser() != null && dataSource != null) {
String user = connectionDetails.getUsername();
if (user != null && dataSource != null) {
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource)
.type(SimpleDriverDataSource.class);
applyCommonBuilderProperties(properties, builder);
applyConnectionDetails(connectionDetails, builder);
return builder.build();
}
Assert.state(dataSource != null, "Flyway migration DataSource missing");
return dataSource;
}
private void applyCommonBuilderProperties(FlywayProperties properties, DataSourceBuilder<?> builder) {
builder.username(properties.getUser());
builder.password(properties.getPassword());
if (StringUtils.hasText(properties.getDriverClassName())) {
builder.driverClassName(properties.getDriverClassName());
private void applyConnectionDetails(JdbcConnectionDetails connectionDetails, DataSourceBuilder<?> builder) {
builder.username(connectionDetails.getUsername());
builder.password(connectionDetails.getPassword());
String driverClassName = connectionDetails.getDriverClassName();
if (StringUtils.hasText(driverClassName)) {
builder.driverClassName(driverClassName);
}
}
@@ -373,6 +384,11 @@ public class FlywayAutoConfiguration {
}
@ConditionalOnBean(JdbcConnectionDetails.class)
private static final class JdbcConnectionDetailsCondition {
}
@ConditionalOnProperty(prefix = "spring.flyway", name = "url")
private static final class FlywayUrlCondition {
@@ -389,4 +405,37 @@ public class FlywayAutoConfiguration {
}
/**
* Adapts {@link FlywayProperties} to {@link JdbcConnectionDetails}.
*/
private static final class FlywayPropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
private final FlywayProperties properties;
private FlywayPropertiesJdbcConnectionDetails(FlywayProperties properties) {
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.getUser();
}
@Override
public String getPassword() {
return this.properties.getPassword();
}
@Override
public String getJdbcUrl() {
return this.properties.getUrl();
}
@Override
public String getDriverClassName() {
return this.properties.getDriverClassName();
}
}
}

View File

@@ -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.
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.influx;
import java.net.URI;
import okhttp3.OkHttpClient;
import org.influxdb.InfluxDB;
import org.influxdb.impl.InfluxDBImpl;
@@ -23,11 +25,15 @@ import org.influxdb.impl.InfluxDBImpl;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
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.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.Conditional;
/**
* {@link EnableAutoConfiguration Auto-configuration} for InfluxDB.
@@ -35,6 +41,9 @@ import org.springframework.context.annotation.Bean;
* @author Sergey Kuptsov
* @author Stephane Nicoll
* @author Eddú Meléndez
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 2.0.0
*/
@AutoConfiguration
@@ -44,11 +53,14 @@ public class InfluxDbAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("spring.influx.url")
@Conditional(InfluxDBCondition.class)
public InfluxDB influxDb(InfluxDbProperties properties, ObjectProvider<InfluxDbOkHttpClientBuilderProvider> builder,
ObjectProvider<InfluxDbCustomizer> customizers) {
InfluxDB influxDb = new InfluxDBImpl(properties.getUrl(), properties.getUser(), properties.getPassword(),
determineBuilder(builder.getIfAvailable()));
ObjectProvider<InfluxDbCustomizer> customizers,
ObjectProvider<InfluxDbConnectionDetails> connectionDetailsProvider) {
InfluxDbConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesInfluxDbConnectionDetails(properties));
InfluxDB influxDb = new InfluxDBImpl(connectionDetails.getUrl().toString(), connectionDetails.getUsername(),
connectionDetails.getPassword(), determineBuilder(builder.getIfAvailable()));
customizers.orderedStream().forEach((customizer) -> customizer.customize(influxDb));
return influxDb;
}
@@ -60,4 +72,54 @@ public class InfluxDbAutoConfiguration {
return new OkHttpClient.Builder();
}
/**
* {@link Condition} that matches when either {@code spring.influx.url} has been set
* or there is an {@link InfluxDbConnectionDetails} bean.
*/
static final class InfluxDBCondition extends AnyNestedCondition {
InfluxDBCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(prefix = "spring.influx", name = "url")
private static final class InfluxUrlCondition {
}
@ConditionalOnBean(InfluxDbConnectionDetails.class)
private static final class InfluxDbConnectionDetailsCondition {
}
}
/**
* Adapts {@link InfluxDbProperties} to {@link InfluxDbConnectionDetails}.
*/
static class PropertiesInfluxDbConnectionDetails implements InfluxDbConnectionDetails {
private final InfluxDbProperties properties;
PropertiesInfluxDbConnectionDetails(InfluxDbProperties properties) {
this.properties = properties;
}
@Override
public URI getUrl() {
return URI.create(this.properties.getUrl());
}
@Override
public String getUsername() {
return this.properties.getUser();
}
@Override
public String getPassword() {
return this.properties.getPassword();
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.influx;
import java.net.URI;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to an InfluxDB service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface InfluxDbConnectionDetails extends ConnectionDetails {
/**
* URL of the InfluxDB instance to which to connect.
* @return the URL of the InfluxDB instance to which to connect
*/
URI getUrl();
/**
* Login user.
* @return the login user or {@code null}
*/
String getUsername();
/**
* Login password.
* @return the login password or {@code null}
*/
String getPassword();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 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.
@@ -24,10 +24,13 @@ import com.zaxxer.hikari.HikariDataSource;
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;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -40,6 +43,8 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Stephane Nicoll
* @author Fabio Grassi
* @author Moritz Halbritter
* @author Andy Wilkinson
*/
abstract class DataSourceConfiguration {
@@ -48,6 +53,17 @@ abstract class DataSourceConfiguration {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
@SuppressWarnings("unchecked")
protected static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type,
ClassLoader classLoader) {
return (T) DataSourceBuilder.create(classLoader)
.url(connectionDetails.getJdbcUrl())
.username(connectionDetails.getUsername())
.password(connectionDetails.getPassword())
.type(type)
.build();
}
/**
* Tomcat Pool DataSource configuration.
*/
@@ -58,13 +74,26 @@ abstract class DataSourceConfiguration {
matchIfMissing = true)
static class Tomcat {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static TomcatJdbcConnectionDetailsBeanPostProcessor tomcatJdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new TomcatJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.tomcat")
org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(properties,
org.apache.tomcat.jdbc.pool.DataSource.class);
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
String validationQuery = databaseDriver.getValidationQuery();
org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
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);
String validationQuery;
String url = (connectionDetails != null) ? connectionDetails.getJdbcUrl() : properties.determineUrl();
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(url);
validationQuery = databaseDriver.getValidationQuery();
if (validationQuery != null) {
dataSource.setTestOnBorrow(true);
dataSource.setValidationQuery(validationQuery);
@@ -84,10 +113,21 @@ abstract class DataSourceConfiguration {
matchIfMissing = true)
static class Hikari {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static HikariJdbcConnectionDetailsBeanPostProcessor jdbcConnectionDetailsHikariBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new HikariJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
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);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
@@ -106,10 +146,22 @@ abstract class DataSourceConfiguration {
matchIfMissing = true)
static class Dbcp2 {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static Dbcp2JdbcConnectionDetailsBeanPostProcessor dbcp2JdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new Dbcp2JdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.dbcp2")
org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties) {
return createDataSource(properties, org.apache.commons.dbcp2.BasicDataSource.class);
org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
Class<? extends DataSource> dataSourceType = org.apache.commons.dbcp2.BasicDataSource.class;
return (connectionDetails != null)
? createDataSource(connectionDetails, dataSourceType, properties.getClassLoader())
: createDataSource(properties, dataSourceType);
}
}
@@ -124,10 +176,21 @@ abstract class DataSourceConfiguration {
matchIfMissing = true)
static class OracleUcp {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static OracleUcpJdbcConnectionDetailsBeanPostProcessor oracleUcpJdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new OracleUcpJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.oracleucp")
PoolDataSourceImpl dataSource(DataSourceProperties properties) throws SQLException {
PoolDataSourceImpl dataSource = createDataSource(properties, PoolDataSourceImpl.class);
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);
dataSource.setValidateConnectionOnBorrow(true);
if (StringUtils.hasText(properties.getName())) {
dataSource.setConnectionPoolName(properties.getName());
@@ -146,7 +209,17 @@ abstract class DataSourceConfiguration {
static class Generic {
@Bean
DataSource dataSource(DataSourceProperties properties) {
DataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
if (connectionDetails != null) {
return DataSourceBuilder.create(properties.getClassLoader())
.url(connectionDetails.getJdbcUrl())
.username(connectionDetails.getUsername())
.password(connectionDetails.getPassword())
.type(properties.getType())
.build();
}
return properties.initializeDataSourceBuilder().build();
}

View File

@@ -0,0 +1,46 @@
/*
* 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;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link BasicDataSource} and name 'dataSource' to apply the
* values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class Dbcp2JdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetailsBeanPostProcessor<BasicDataSource> {
Dbcp2JdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(BasicDataSource.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(BasicDataSource dataSource, JdbcConnectionDetails connectionDetails) {
dataSource.setUrl(connectionDetails.getJdbcUrl());
dataSource.setUsername(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setDriverClassName(connectionDetails.getDriverClassName());
return dataSource;
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link HikariDataSource} and name 'dataSource' to apply
* the values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class HikariJdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetailsBeanPostProcessor<HikariDataSource> {
HikariJdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(HikariDataSource.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(HikariDataSource dataSource, JdbcConnectionDetails connectionDetails) {
dataSource.setJdbcUrl(connectionDetails.getJdbcUrl());
dataSource.setUsername(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setDriverClassName(connectionDetails.getDriverClassName());
return dataSource;
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.boot.jdbc.DatabaseDriver;
/**
* Details required to establish a connection to an SQL service using JDBC.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface JdbcConnectionDetails extends ConnectionDetails {
/**
* Hostname 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();
}
/**
* Returns the name of the XA DataSource class. Defaults to the class name from the
* driver specified in the JDBC URL.
* @return the XA DataSource class name
* @see #getJdbcUrl()
* @see DatabaseDriver#fromJdbcUrl(String)
* @see DatabaseDriver#getXaDataSourceClassName()
*/
default String getXaDataSourceClassName() {
return DatabaseDriver.fromJdbcUrl(getJdbcUrl()).getXaDataSourceClassName();
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
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}.
*
* @param <T> type of the datasource
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
abstract class JdbcConnectionDetailsBeanPostProcessor<T> implements BeanPostProcessor, PriorityOrdered {
private final Class<T> dataSourceClass;
private final ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider;
JdbcConnectionDetailsBeanPostProcessor(Class<T> dataSourceClass,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
this.dataSourceClass = dataSourceClass;
this.connectionDetailsProvider = connectionDetailsProvider;
}
@Override
@SuppressWarnings("unchecked")
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (this.dataSourceClass.isAssignableFrom(bean.getClass()) && "dataSource".equals(beanName)) {
JdbcConnectionDetails connectionDetails = this.connectionDetailsProvider.getObject();
return processDataSource((T) bean, connectionDetails);
}
return bean;
}
protected abstract Object processDataSource(T dataSource, JdbcConnectionDetails connectionDetails);
@Override
public int getOrder() {
// Runs after ConfigurationPropertiesBindingPostProcessor
return Ordered.HIGHEST_PRECEDENCE + 2;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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;
import java.sql.SQLException;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link PoolDataSourceImpl} and name 'dataSource' to apply
* the values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class OracleUcpJdbcConnectionDetailsBeanPostProcessor
extends JdbcConnectionDetailsBeanPostProcessor<PoolDataSourceImpl> {
OracleUcpJdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(PoolDataSourceImpl.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(PoolDataSourceImpl dataSource, JdbcConnectionDetails connectionDetails) {
try {
dataSource.setURL(connectionDetails.getJdbcUrl());
dataSource.setUser(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setConnectionFactoryClassName(connectionDetails.getDriverClassName());
return dataSource;
}
catch (SQLException ex) {
throw new RuntimeException("Failed to set URL / user / password of datasource", ex);
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link DataSource} and name 'dataSource' to apply the
* values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class TomcatJdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetailsBeanPostProcessor<DataSource> {
TomcatJdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(DataSource.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(DataSource dataSource, JdbcConnectionDetails connectionDetails) {
dataSource.setUrl(connectionDetails.getJdbcUrl());
dataSource.setUsername(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setDriverClassName(connectionDetails.getDriverClassName());
return dataSource;
}
}

View File

@@ -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.
@@ -40,7 +40,6 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyN
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.XADataSourceWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@@ -54,6 +53,8 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Josh Long
* @author Madhura Bhave
* @author Moritz Halbritter
* @author Andy Wilkinson
* @since 1.2.0
*/
@AutoConfiguration(before = DataSourceAutoConfiguration.class)
@@ -67,8 +68,10 @@ public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
@Bean
public DataSource dataSource(XADataSourceWrapper wrapper, DataSourceProperties properties,
ObjectProvider<XADataSource> xaDataSource) throws Exception {
return wrapper.wrapDataSource(xaDataSource.getIfAvailable(() -> createXaDataSource(properties)));
ObjectProvider<JdbcConnectionDetails> connectionDetails, ObjectProvider<XADataSource> xaDataSource)
throws Exception {
return wrapper.wrapDataSource(xaDataSource.getIfAvailable(() -> createXaDataSource(properties,
connectionDetails.getIfAvailable(() -> new PropertiesJdbcConnectionDetails(properties)))));
}
@Override
@@ -76,14 +79,11 @@ public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
this.classLoader = classLoader;
}
private XADataSource createXaDataSource(DataSourceProperties properties) {
String className = properties.getXa().getDataSourceClassName();
if (!StringUtils.hasLength(className)) {
className = DatabaseDriver.fromJdbcUrl(properties.determineUrl()).getXaDataSourceClassName();
}
private XADataSource createXaDataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
String className = connectionDetails.getXaDataSourceClassName();
Assert.state(StringUtils.hasLength(className), "No XA DataSource class name specified");
XADataSource dataSource = createXaDataSourceInstance(className);
bindXaProperties(dataSource, properties);
bindXaProperties(dataSource, properties, connectionDetails);
return dataSource;
}
@@ -99,18 +99,19 @@ public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
}
}
private void bindXaProperties(XADataSource target, DataSourceProperties dataSourceProperties) {
Binder binder = new Binder(getBinderSource(dataSourceProperties));
private void bindXaProperties(XADataSource target, DataSourceProperties dataSourceProperties,
JdbcConnectionDetails connectionDetails) {
Binder binder = new Binder(getBinderSource(dataSourceProperties, connectionDetails));
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(target));
}
private ConfigurationPropertySource getBinderSource(DataSourceProperties dataSourceProperties) {
Map<Object, Object> properties = new HashMap<>();
properties.putAll(dataSourceProperties.getXa().getProperties());
properties.computeIfAbsent("user", (key) -> dataSourceProperties.determineUsername());
properties.computeIfAbsent("password", (key) -> dataSourceProperties.determinePassword());
private ConfigurationPropertySource getBinderSource(DataSourceProperties dataSourceProperties,
JdbcConnectionDetails connectionDetails) {
Map<Object, Object> properties = new HashMap<>(dataSourceProperties.getXa().getProperties());
properties.computeIfAbsent("user", (key) -> connectionDetails.getUsername());
properties.computeIfAbsent("password", (key) -> connectionDetails.getPassword());
try {
properties.computeIfAbsent("url", (key) -> dataSourceProperties.determineUrl());
properties.computeIfAbsent("url", (key) -> connectionDetails.getJdbcUrl());
}
catch (DataSourceBeanCreationException ex) {
// Continue as not all XA DataSource's require a URL
@@ -121,4 +122,45 @@ 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();
}
}
}

View File

@@ -18,6 +18,12 @@ package org.springframework.boot.autoconfigure.kafka;
import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -26,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.kafka.KafkaConnectionDetails.Node;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties.Jaas;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties.Retry.Topic;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -56,6 +63,9 @@ import org.springframework.retry.backoff.SleepingBackOffPolicy;
* @author Eddú Meléndez
* @author Nakul Mishra
* @author Tomaz Fernandes
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 1.5.0
*/
@AutoConfiguration
@@ -66,8 +76,12 @@ public class KafkaAutoConfiguration {
private final KafkaProperties properties;
public KafkaAutoConfiguration(KafkaProperties properties) {
private final KafkaConnectionDetails connectionDetails;
KafkaAutoConfiguration(KafkaProperties properties, ObjectProvider<KafkaConnectionDetails> connectionDetails) {
this.properties = properties;
this.connectionDetails = connectionDetails
.getIfAvailable(() -> new PropertiesKafkaConnectionDetails(properties));
}
@Bean
@@ -94,8 +108,9 @@ public class KafkaAutoConfiguration {
@ConditionalOnMissingBean(ConsumerFactory.class)
public DefaultKafkaConsumerFactory<?, ?> kafkaConsumerFactory(
ObjectProvider<DefaultKafkaConsumerFactoryCustomizer> customizers) {
DefaultKafkaConsumerFactory<Object, Object> factory = new DefaultKafkaConsumerFactory<>(
this.properties.buildConsumerProperties());
Map<String, Object> properties = this.properties.buildConsumerProperties();
applyKafkaConnectionDetailsForConsumer(properties);
DefaultKafkaConsumerFactory<Object, Object> factory = new DefaultKafkaConsumerFactory<>(properties);
customizers.orderedStream().forEach((customizer) -> customizer.customize(factory));
return factory;
}
@@ -104,8 +119,9 @@ public class KafkaAutoConfiguration {
@ConditionalOnMissingBean(ProducerFactory.class)
public DefaultKafkaProducerFactory<?, ?> kafkaProducerFactory(
ObjectProvider<DefaultKafkaProducerFactoryCustomizer> customizers) {
DefaultKafkaProducerFactory<?, ?> factory = new DefaultKafkaProducerFactory<>(
this.properties.buildProducerProperties());
Map<String, Object> properties = this.properties.buildProducerProperties();
applyKafkaConnectionDetailsForProducer(properties);
DefaultKafkaProducerFactory<?, ?> factory = new DefaultKafkaProducerFactory<>(properties);
String transactionIdPrefix = this.properties.getProducer().getTransactionIdPrefix();
if (transactionIdPrefix != null) {
factory.setTransactionIdPrefix(transactionIdPrefix);
@@ -140,7 +156,9 @@ public class KafkaAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public KafkaAdmin kafkaAdmin() {
KafkaAdmin kafkaAdmin = new KafkaAdmin(this.properties.buildAdminProperties());
Map<String, Object> properties = this.properties.buildAdminProperties();
applyKafkaConnectionDetailsForAdmin(properties);
KafkaAdmin kafkaAdmin = new KafkaAdmin(properties);
KafkaProperties.Admin admin = this.properties.getAdmin();
if (admin.getCloseTimeout() != null) {
kafkaAdmin.setCloseTimeout((int) admin.getCloseTimeout().getSeconds());
@@ -168,6 +186,34 @@ public class KafkaAutoConfiguration {
return builder.create(kafkaTemplate);
}
private void applyKafkaConnectionDetailsForConsumer(Map<String, Object> properties) {
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
nodesToStringList(this.connectionDetails.getConsumerBootstrapNodes()));
if (!(this.connectionDetails instanceof PropertiesKafkaConnectionDetails)) {
properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
}
}
private void applyKafkaConnectionDetailsForProducer(Map<String, Object> properties) {
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
nodesToStringList(this.connectionDetails.getProducerBootstrapNodes()));
if (!(this.connectionDetails instanceof PropertiesKafkaConnectionDetails)) {
properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
}
}
private void applyKafkaConnectionDetailsForAdmin(Map<String, Object> properties) {
properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
nodesToStringList(this.connectionDetails.getAdminBootstrapNodes()));
if (!(this.connectionDetails instanceof PropertiesKafkaConnectionDetails)) {
properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
}
}
private List<String> nodesToStringList(List<Node> nodes) {
return nodes.stream().map((node) -> node.host() + ":" + node.port()).toList();
}
private static void setBackOffPolicy(RetryTopicConfigurationBuilder builder, Topic retryTopic) {
long delay = (retryTopic.getDelay() != null) ? retryTopic.getDelay().toMillis() : 0;
if (delay > 0) {

View File

@@ -0,0 +1,81 @@
/*
* 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.kafka;
import java.util.List;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to a Kafka service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface KafkaConnectionDetails extends ConnectionDetails {
/**
* Returns the list of bootstrap nodes.
* @return the list of bootstrap nodes
*/
List<Node> getBootstrapNodes();
/**
* Returns the list of bootstrap nodes used for consumers.
* @return the list of bootstrap nodes used for consumers
*/
default List<Node> getConsumerBootstrapNodes() {
return getBootstrapNodes();
}
/**
* Returns the list of bootstrap nodes used for producers.
* @return the list of bootstrap nodes used for producers
*/
default List<Node> getProducerBootstrapNodes() {
return getBootstrapNodes();
}
/**
* Returns the list of bootstrap nodes used for the admin.
* @return the list of bootstrap nodes used for the admin
*/
default List<Node> getAdminBootstrapNodes() {
return getBootstrapNodes();
}
/**
* Returns the list of bootstrap nodes used for Kafka Streams.
* @return the list of bootstrap nodes used for Kafka Streams
*/
default List<Node> getStreamsBootstrapNodes() {
return getBootstrapNodes();
}
/**
* A Kafka node.
*
* @param host the hostname
* @param port the port
*/
record Node(String host, int port) {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 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.
@@ -16,8 +16,11 @@
package org.springframework.boot.autoconfigure.kafka;
import java.util.List;
import java.util.Map;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
@@ -27,6 +30,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
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.kafka.KafkaConnectionDetails.Node;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -42,6 +46,8 @@ import org.springframework.kafka.core.CleanupConfig;
* @author Gary Russell
* @author Stephane Nicoll
* @author Eddú Meléndez
* @author Moritz Halbritter
* @author Andy Wilkinson
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(StreamsBuilder.class)
@@ -56,17 +62,21 @@ class KafkaStreamsAnnotationDrivenConfiguration {
@ConditionalOnMissingBean
@Bean(KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
KafkaStreamsConfiguration defaultKafkaStreamsConfig(Environment environment) {
Map<String, Object> streamsProperties = this.properties.buildStreamsProperties();
KafkaStreamsConfiguration defaultKafkaStreamsConfig(Environment environment,
ObjectProvider<KafkaConnectionDetails> connectionDetailsProvider) {
KafkaConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesKafkaConnectionDetails(this.properties));
Map<String, Object> properties = this.properties.buildStreamsProperties();
applyKafkaConnectionDetailsForStreams(connectionDetails, properties);
if (this.properties.getStreams().getApplicationId() == null) {
String applicationName = environment.getProperty("spring.application.name");
if (applicationName == null) {
throw new InvalidConfigurationPropertyValueException("spring.kafka.streams.application-id", null,
"This property is mandatory and fallback 'spring.application.name' is not set either.");
}
streamsProperties.put(StreamsConfig.APPLICATION_ID_CONFIG, applicationName);
properties.put(StreamsConfig.APPLICATION_ID_CONFIG, applicationName);
}
return new KafkaStreamsConfiguration(streamsProperties);
return new KafkaStreamsConfiguration(properties);
}
@Bean
@@ -77,6 +87,19 @@ class KafkaStreamsAnnotationDrivenConfiguration {
return new KafkaStreamsFactoryBeanConfigurer(this.properties, factoryBean);
}
private void applyKafkaConnectionDetailsForStreams(KafkaConnectionDetails connectionDetails,
Map<String, Object> properties) {
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
nodesToStringList(connectionDetails.getStreamsBootstrapNodes()));
if (!(connectionDetails instanceof PropertiesKafkaConnectionDetails)) {
properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
}
}
private List<String> nodesToStringList(List<Node> nodes) {
return nodes.stream().map((node) -> node.host() + ":" + node.port()).toList();
}
// Separate class required to avoid BeanCurrentlyInCreationException
static class KafkaStreamsFactoryBeanConfigurer implements InitializingBean {

View File

@@ -0,0 +1,75 @@
/*
* 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.kafka;
import java.util.List;
/**
* Adapts {@link KafkaProperties} to {@link KafkaConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class PropertiesKafkaConnectionDetails implements KafkaConnectionDetails {
private final int DEFAULT_PORT = 9092;
private final KafkaProperties properties;
PropertiesKafkaConnectionDetails(KafkaProperties properties) {
this.properties = properties;
}
@Override
public List<Node> getBootstrapNodes() {
return asNodes(this.properties.getBootstrapServers());
}
@Override
public List<Node> getConsumerBootstrapNodes() {
return bootstrapNodes(this.properties.getConsumer().getBootstrapServers());
}
@Override
public List<Node> getProducerBootstrapNodes() {
return bootstrapNodes(this.properties.getProducer().getBootstrapServers());
}
@Override
public List<Node> getStreamsBootstrapNodes() {
return bootstrapNodes(this.properties.getStreams().getBootstrapServers());
}
private List<Node> bootstrapNodes(List<String> bootstrapServers) {
return (bootstrapServers != null) ? asNodes(bootstrapServers) : getBootstrapNodes();
}
private List<Node> asNodes(List<String> bootstrapServers) {
return bootstrapServers.stream().map(this::asNode).toList();
}
private Node asNode(String bootstrapNode) {
int separatorIndex = bootstrapNode.indexOf(':');
if (separatorIndex == -1) {
return new Node(bootstrapNode, this.DEFAULT_PORT);
}
return new Node(bootstrapNode.substring(0, separatorIndex),
Integer.parseInt(bootstrapNode.substring(separatorIndex + 1)));
}
}

View File

@@ -32,6 +32,7 @@ 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.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseAutoConfigurationRuntimeHints;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseDataSourceCondition;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
@@ -61,6 +62,7 @@ import org.springframework.util.StringUtils;
* @author András Deák
* @author Ferenc Gratzer
* @author Evgeniy Cheban
* @author Moritz Halbritter
* @since 1.1.0
*/
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
@@ -83,38 +85,34 @@ public class LiquibaseAutoConfiguration {
@EnableConfigurationProperties(LiquibaseProperties.class)
public static class LiquibaseConfiguration {
private final LiquibaseProperties properties;
public LiquibaseConfiguration(LiquibaseProperties properties) {
this.properties = properties;
}
@Bean
public SpringLiquibase liquibase(ObjectProvider<DataSource> dataSource,
@LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource) {
@LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource, LiquibaseProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetails) {
SpringLiquibase liquibase = createSpringLiquibase(liquibaseDataSource.getIfAvailable(),
dataSource.getIfUnique());
liquibase.setChangeLog(this.properties.getChangeLog());
liquibase.setClearCheckSums(this.properties.isClearChecksums());
liquibase.setContexts(this.properties.getContexts());
liquibase.setDefaultSchema(this.properties.getDefaultSchema());
liquibase.setLiquibaseSchema(this.properties.getLiquibaseSchema());
liquibase.setLiquibaseTablespace(this.properties.getLiquibaseTablespace());
liquibase.setDatabaseChangeLogTable(this.properties.getDatabaseChangeLogTable());
liquibase.setDatabaseChangeLogLockTable(this.properties.getDatabaseChangeLogLockTable());
liquibase.setDropFirst(this.properties.isDropFirst());
liquibase.setShouldRun(this.properties.isEnabled());
liquibase.setLabelFilter(this.properties.getLabelFilter());
liquibase.setChangeLogParameters(this.properties.getParameters());
liquibase.setRollbackFile(this.properties.getRollbackFile());
liquibase.setTestRollbackOnUpdate(this.properties.isTestRollbackOnUpdate());
liquibase.setTag(this.properties.getTag());
dataSource.getIfUnique(),
connectionDetails.getIfAvailable(() -> new LiquibasePropertiesJdbcConnectionDetails(properties)));
liquibase.setChangeLog(properties.getChangeLog());
liquibase.setClearCheckSums(properties.isClearChecksums());
liquibase.setContexts(properties.getContexts());
liquibase.setDefaultSchema(properties.getDefaultSchema());
liquibase.setLiquibaseSchema(properties.getLiquibaseSchema());
liquibase.setLiquibaseTablespace(properties.getLiquibaseTablespace());
liquibase.setDatabaseChangeLogTable(properties.getDatabaseChangeLogTable());
liquibase.setDatabaseChangeLogLockTable(properties.getDatabaseChangeLogLockTable());
liquibase.setDropFirst(properties.isDropFirst());
liquibase.setShouldRun(properties.isEnabled());
liquibase.setLabelFilter(properties.getLabelFilter());
liquibase.setChangeLogParameters(properties.getParameters());
liquibase.setRollbackFile(properties.getRollbackFile());
liquibase.setTestRollbackOnUpdate(properties.isTestRollbackOnUpdate());
liquibase.setTag(properties.getTag());
return liquibase;
}
private SpringLiquibase createSpringLiquibase(DataSource liquibaseDataSource, DataSource dataSource) {
LiquibaseProperties properties = this.properties;
DataSource migrationDataSource = getMigrationDataSource(liquibaseDataSource, dataSource, properties);
private SpringLiquibase createSpringLiquibase(DataSource liquibaseDataSource, DataSource dataSource,
JdbcConnectionDetails connectionDetails) {
DataSource migrationDataSource = getMigrationDataSource(liquibaseDataSource, dataSource, connectionDetails);
SpringLiquibase liquibase = (migrationDataSource == liquibaseDataSource
|| migrationDataSource == dataSource) ? new SpringLiquibase()
: new DataSourceClosingSpringLiquibase();
@@ -123,31 +121,34 @@ public class LiquibaseAutoConfiguration {
}
private DataSource getMigrationDataSource(DataSource liquibaseDataSource, DataSource dataSource,
LiquibaseProperties properties) {
JdbcConnectionDetails connectionDetails) {
if (liquibaseDataSource != null) {
return liquibaseDataSource;
}
if (properties.getUrl() != null) {
String url = connectionDetails.getJdbcUrl();
if (url != null) {
DataSourceBuilder<?> builder = DataSourceBuilder.create().type(SimpleDriverDataSource.class);
builder.url(properties.getUrl());
applyCommonBuilderProperties(properties, builder);
builder.url(url);
applyConnectionDetails(connectionDetails, builder);
return builder.build();
}
if (properties.getUser() != null && dataSource != null) {
String user = connectionDetails.getUsername();
if (user != null && dataSource != null) {
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource)
.type(SimpleDriverDataSource.class);
applyCommonBuilderProperties(properties, builder);
applyConnectionDetails(connectionDetails, builder);
return builder.build();
}
Assert.state(dataSource != null, "Liquibase migration DataSource missing");
return dataSource;
}
private void applyCommonBuilderProperties(LiquibaseProperties properties, DataSourceBuilder<?> builder) {
builder.username(properties.getUser());
builder.password(properties.getPassword());
if (StringUtils.hasText(properties.getDriverClassName())) {
builder.driverClassName(properties.getDriverClassName());
private void applyConnectionDetails(JdbcConnectionDetails connectionDetails, DataSourceBuilder<?> builder) {
builder.username(connectionDetails.getUsername());
builder.password(connectionDetails.getPassword());
String driverClassName = connectionDetails.getDriverClassName();
if (StringUtils.hasText(driverClassName)) {
builder.driverClassName(driverClassName);
}
}
@@ -164,6 +165,11 @@ public class LiquibaseAutoConfiguration {
}
@ConditionalOnBean(JdbcConnectionDetails.class)
private static final class JdbcConnectionDetailsCondition {
}
@ConditionalOnProperty(prefix = "spring.liquibase", name = "url")
private static final class LiquibaseUrlCondition {
@@ -180,4 +186,37 @@ public class LiquibaseAutoConfiguration {
}
/**
* Adapts {@link LiquibaseProperties} to {@link JdbcConnectionDetails}.
*/
private static final class LiquibasePropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
private final LiquibaseProperties properties;
private LiquibasePropertiesJdbcConnectionDetails(LiquibaseProperties properties) {
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.getUser();
}
@Override
public String getPassword() {
return this.properties.getPassword();
}
@Override
public String getJdbcUrl() {
return this.properties.getUrl();
}
@Override
public String getDriverClassName() {
return this.properties.getDriverClassName();
}
}
}

View File

@@ -62,8 +62,12 @@ public class MongoAutoConfiguration {
}
@Bean
MongoPropertiesClientSettingsBuilderCustomizer mongoPropertiesCustomizer(MongoProperties properties) {
return new MongoPropertiesClientSettingsBuilderCustomizer(properties);
StandardMongoClientSettingsBuilderCustomizer standardMongoSettingsCustomizer(MongoProperties properties,
ObjectProvider<MongoConnectionDetails> connectionDetailsProvider) {
MongoConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesMongoConnectionDetails(properties));
return new StandardMongoClientSettingsBuilderCustomizer(connectionDetails.getConnectionString(),
properties.getUuidRepresentation());
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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.mongo;
import com.mongodb.ConnectionString;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to a MongoDB service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface MongoConnectionDetails extends ConnectionDetails {
/**
* The {@link ConnectionString} for MongoDB.
* @return the connection string
*/
ConnectionString getConnectionString();
/**
* GridFS configuration.
* @return the GridFS configuration or {@code null}
*/
default GridFs getGridFs() {
return null;
}
/**
* GridFS configuration.
*/
interface GridFs {
/**
* GridFS database name.
* @return the GridFS database name or {@code null}
*/
String getDatabase();
/**
* GridFS bucket name.
* @return the GridFS bucket name or {@code null}
*/
String getBucket();
/**
* Factory method to create a new {@link GridFs} instance.
* @param database the database
* @param bucket the bucket name
* @return a new {@link GridFs} instance
*/
static GridFs of(String database, String bucket) {
return new GridFs() {
@Override
public String getDatabase() {
return database;
}
@Override
public String getBucket() {
return bucket;
}
};
}
}
}

View File

@@ -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.
@@ -34,7 +34,10 @@ import org.springframework.util.CollectionUtils;
* @author Scott Frederick
* @author Safeer Ansari
* @since 2.4.0
* @deprecated since 3.1.0 in favor of
* {@link StandardMongoClientSettingsBuilderCustomizer}
*/
@Deprecated(since = "3.1.0", forRemoval = true)
public class MongoPropertiesClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer, Ordered {
private final MongoProperties properties;

View File

@@ -69,8 +69,12 @@ public class MongoReactiveAutoConfiguration {
}
@Bean
MongoPropertiesClientSettingsBuilderCustomizer mongoPropertiesCustomizer(MongoProperties properties) {
return new MongoPropertiesClientSettingsBuilderCustomizer(properties);
StandardMongoClientSettingsBuilderCustomizer standardMongoSettingsCustomizer(MongoProperties properties,
ObjectProvider<MongoConnectionDetails> connectionDetailsProvider) {
MongoConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesMongoConnectionDetails(properties));
return new StandardMongoClientSettingsBuilderCustomizer(connectionDetails.getConnectionString(),
properties.getUuidRepresentation());
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.mongo;
import com.mongodb.ConnectionString;
/**
* Adapts {@link MongoProperties} to {@link MongoConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public class PropertiesMongoConnectionDetails implements MongoConnectionDetails {
private final MongoProperties properties;
public PropertiesMongoConnectionDetails(MongoProperties properties) {
this.properties = properties;
}
@Override
public ConnectionString getConnectionString() {
// mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database.collection][?options]]
if (this.properties.getUri() != null) {
return new ConnectionString(this.properties.getUri());
}
StringBuilder builder = new StringBuilder("mongodb://");
if (this.properties.getUsername() != null) {
builder.append(this.properties.getUsername());
builder.append(":");
builder.append(this.properties.getPassword());
builder.append("@");
}
builder.append((this.properties.getHost() != null) ? this.properties.getHost() : "localhost");
if (this.properties.getPort() != null) {
builder.append(":");
builder.append(this.properties.getPort());
}
if (this.properties.getAdditionalHosts() != null) {
builder.append(String.join(",", this.properties.getAdditionalHosts()));
}
if (this.properties.getMongoClientDatabase() != null || this.properties.getReplicaSetName() != null
|| this.properties.getAuthenticationDatabase() != null) {
builder.append("/");
if (this.properties.getMongoClientDatabase() != null) {
builder.append(this.properties.getMongoClientDatabase());
}
else if (this.properties.getAuthenticationDatabase() != null) {
builder.append(this.properties.getAuthenticationDatabase());
}
if (this.properties.getReplicaSetName() != null) {
builder.append("?");
builder.append("repliceSet=");
builder.append(this.properties.getReplicaSetName());
}
}
return new ConnectionString(builder.toString());
}
@Override
public GridFs getGridFs() {
return GridFs.of(PropertiesMongoConnectionDetails.this.properties.getGridfs().getDatabase(),
PropertiesMongoConnectionDetails.this.properties.getGridfs().getBucket());
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.mongo;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import org.bson.UuidRepresentation;
import org.springframework.core.Ordered;
/**
* A {@link MongoClientSettingsBuilderCustomizer} that applies standard settings to a
* {@link MongoClientSettings}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public class StandardMongoClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer, Ordered {
private final ConnectionString connectionString;
private final UuidRepresentation uuidRepresentation;
private int order = 0;
public StandardMongoClientSettingsBuilderCustomizer(ConnectionString connectionString,
UuidRepresentation uuidRepresentation) {
this.connectionString = connectionString;
this.uuidRepresentation = uuidRepresentation;
}
@Override
public void customize(MongoClientSettings.Builder settingsBuilder) {
settingsBuilder.uuidRepresentation(this.uuidRepresentation);
settingsBuilder.applyConnectionString(this.connectionString);
}
@Override
public int getOrder() {
return this.order;
}
/**
* Set the order value of this object.
* @param order the new order value
* @see #getOrder()
*/
public void setOrder(int order) {
this.order = order;
}
}

View File

@@ -36,12 +36,14 @@ 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.neo4j.Neo4jProperties.Authentication;
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Pool;
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Security;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -49,6 +51,9 @@ import org.springframework.util.StringUtils;
*
* @author Michael J. Simons
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 2.4.0
*/
@AutoConfiguration
@@ -56,50 +61,24 @@ import org.springframework.util.StringUtils;
@EnableConfigurationProperties(Neo4jProperties.class)
public class Neo4jAutoConfiguration {
private static final URI DEFAULT_SERVER_URI = URI.create("bolt://localhost:7687");
@Bean
@ConditionalOnMissingBean
public Driver neo4jDriver(Neo4jProperties properties, Environment environment,
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers) {
AuthToken authToken = mapAuthToken(properties.getAuthentication(), environment);
Config config = mapDriverConfig(properties, configBuilderCustomizers.orderedStream().toList());
URI serverUri = determineServerUri(properties, environment);
return GraphDatabase.driver(serverUri, authToken, config);
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers,
ObjectProvider<Neo4jConnectionDetails> connectionDetailsProvider) {
Neo4jConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesNeo4jConnectionDetails(properties));
AuthToken authToken = connectionDetails.getAuthToken();
Config config = mapDriverConfig(properties, connectionDetails,
configBuilderCustomizers.orderedStream().toList());
return GraphDatabase.driver(connectionDetails.getUri(), authToken, config);
}
URI determineServerUri(Neo4jProperties properties, Environment environment) {
URI uri = properties.getUri();
return (uri != null) ? uri : DEFAULT_SERVER_URI;
}
AuthToken mapAuthToken(Neo4jProperties.Authentication authentication, Environment environment) {
String username = authentication.getUsername();
String password = authentication.getPassword();
String kerberosTicket = authentication.getKerberosTicket();
String realm = authentication.getRealm();
boolean hasUsername = StringUtils.hasText(username);
boolean hasPassword = StringUtils.hasText(password);
boolean hasKerberosTicket = StringUtils.hasText(kerberosTicket);
if (hasUsername && hasKerberosTicket) {
throw new IllegalStateException(String
.format("Cannot specify both username ('%s') and kerberos ticket ('%s')", username, kerberosTicket));
}
if (hasUsername && hasPassword) {
return AuthTokens.basic(username, password, realm);
}
if (hasKerberosTicket) {
return AuthTokens.kerberos(kerberosTicket);
}
return AuthTokens.none();
}
Config mapDriverConfig(Neo4jProperties properties, List<ConfigBuilderCustomizer> customizers) {
Config mapDriverConfig(Neo4jProperties properties, Neo4jConnectionDetails connectionDetails,
List<ConfigBuilderCustomizer> customizers) {
Config.ConfigBuilder builder = Config.builder();
configurePoolSettings(builder, properties.getPool());
URI uri = properties.getUri();
URI uri = connectionDetails.getUri();
String scheme = (uri != null) ? uri.getScheme() : "bolt";
configureDriverSettings(builder, properties, isSimpleScheme(scheme));
builder.withLogging(new Neo4jSpringJclLogging());
@@ -191,4 +170,43 @@ public class Neo4jAutoConfiguration {
}
}
/**
* Adapts {@link Neo4jProperties} to {@link Neo4jConnectionDetails}.
*/
static class PropertiesNeo4jConnectionDetails implements Neo4jConnectionDetails {
private final Neo4jProperties properties;
PropertiesNeo4jConnectionDetails(Neo4jProperties properties) {
this.properties = properties;
}
@Override
public URI getUri() {
URI uri = this.properties.getUri();
return (uri != null) ? uri : Neo4jConnectionDetails.super.getUri();
}
@Override
public AuthToken getAuthToken() {
Authentication authentication = this.properties.getAuthentication();
String username = authentication.getUsername();
String kerberosTicket = authentication.getKerberosTicket();
boolean hasUsername = StringUtils.hasText(username);
boolean hasKerberosTicket = StringUtils.hasText(kerberosTicket);
Assert.state(!(hasUsername && hasKerberosTicket),
() -> "Cannot specify both username ('%s') and kerberos ticket ('%s')".formatted(username,
kerberosTicket));
String password = authentication.getPassword();
if (hasUsername && StringUtils.hasText(password)) {
return AuthTokens.basic(username, password, authentication.getRealm());
}
if (hasKerberosTicket) {
return AuthTokens.kerberos(kerberosTicket);
}
return AuthTokens.none();
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.neo4j;
import java.net.URI;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to a Neo4j service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface Neo4jConnectionDetails extends ConnectionDetails {
/**
* Returns the URI of the Neo4j server. Defaults to {@code bolt://localhost:7687"}.
* @return the Neo4j server URI
*/
default URI getUri() {
return URI.create("bolt://localhost:7687");
}
/**
* Returns the token to use for authentication. Defaults to {@link AuthTokens#none()}.
* @return the auth token
*/
default AuthToken getAuthToken() {
return AuthTokens.none();
}
}

View File

@@ -53,7 +53,7 @@ class ConnectionFactoryBeanCreationFailureAnalyzer
private String getDescription(ConnectionFactoryBeanCreationException cause) {
StringBuilder description = new StringBuilder();
description.append("Failed to configure a ConnectionFactory: ");
if (!StringUtils.hasText(cause.getProperties().getUrl())) {
if (!StringUtils.hasText(cause.getUrl())) {
description.append("'url' attribute is not specified and ");
}
description.append(String.format("no embedded database could be configured.%n"));

View File

@@ -51,14 +51,18 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch
* @author Stephane Nicoll
* @author Rodolpho S. Couto
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
abstract class ConnectionFactoryConfigurations {
protected static ConnectionFactory createConnectionFactory(R2dbcProperties properties, ClassLoader classLoader,
protected static ConnectionFactory createConnectionFactory(R2dbcProperties properties,
R2dbcConnectionDetails connectionDetails, ClassLoader classLoader,
List<ConnectionFactoryOptionsBuilderCustomizer> optionsCustomizers) {
try {
return org.springframework.boot.r2dbc.ConnectionFactoryBuilder
.withOptions(new ConnectionFactoryOptionsInitializer().initialize(properties,
.withOptions(new ConnectionFactoryOptionsInitializer().initialize(properties, connectionDetails,
() -> EmbeddedDatabaseConnection.get(classLoader)))
.configure((options) -> {
for (ConnectionFactoryOptionsBuilderCustomizer optionsCustomizer : optionsCustomizers) {
@@ -87,10 +91,12 @@ abstract class ConnectionFactoryConfigurations {
static class PooledConnectionFactoryConfiguration {
@Bean(destroyMethod = "dispose")
ConnectionPool connectionFactory(R2dbcProperties properties, ResourceLoader resourceLoader,
ConnectionPool connectionFactory(R2dbcProperties properties,
ObjectProvider<R2dbcConnectionDetails> connectionDetails, ResourceLoader resourceLoader,
ObjectProvider<ConnectionFactoryOptionsBuilderCustomizer> customizers) {
ConnectionFactory connectionFactory = createConnectionFactory(properties,
resourceLoader.getClassLoader(), customizers.orderedStream().toList());
connectionDetails.getIfAvailable(), resourceLoader.getClassLoader(),
customizers.orderedStream().toList());
R2dbcProperties.Pool pool = properties.getPool();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory);
@@ -116,10 +122,11 @@ abstract class ConnectionFactoryConfigurations {
static class GenericConfiguration {
@Bean
ConnectionFactory connectionFactory(R2dbcProperties properties, ResourceLoader resourceLoader,
ConnectionFactory connectionFactory(R2dbcProperties properties,
ObjectProvider<R2dbcConnectionDetails> connectionDetails, ResourceLoader resourceLoader,
ObjectProvider<ConnectionFactoryOptionsBuilderCustomizer> customizers) {
return createConnectionFactory(properties, resourceLoader.getClassLoader(),
customizers.orderedStream().toList());
return createConnectionFactory(properties, connectionDetails.getIfAvailable(),
resourceLoader.getClassLoader(), customizers.orderedStream().toList());
}
}

View File

@@ -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.
@@ -16,12 +16,10 @@
package org.springframework.boot.autoconfigure.r2dbc;
import java.util.function.Predicate;
import java.util.function.Supplier;
import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.ConnectionFactoryOptions.Builder;
import io.r2dbc.spi.Option;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
@@ -31,6 +29,9 @@ import org.springframework.util.StringUtils;
* Initialize a {@link Builder} based on {@link R2dbcProperties}.
*
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class ConnectionFactoryOptionsInitializer {
@@ -38,45 +39,31 @@ class ConnectionFactoryOptionsInitializer {
* Initialize a {@link Builder ConnectionFactoryOptions.Builder} using the specified
* properties.
* @param properties the properties to use to initialize the builder
* @param connectionDetails the connection details to use to initialize the builder
* @param embeddedDatabaseConnection the embedded connection to use as a fallback
* @return an initialized builder
* @throws ConnectionFactoryBeanCreationException if no suitable connection could be
* determined
*/
ConnectionFactoryOptions.Builder initialize(R2dbcProperties properties,
ConnectionFactoryOptions.Builder initialize(R2dbcProperties properties, R2dbcConnectionDetails connectionDetails,
Supplier<EmbeddedDatabaseConnection> embeddedDatabaseConnection) {
if (StringUtils.hasText(properties.getUrl())) {
return initializeRegularOptions(properties);
if (connectionDetails != null) {
return connectionDetails.getConnectionFactoryOptions().mutate();
}
EmbeddedDatabaseConnection embeddedConnection = embeddedDatabaseConnection.get();
if (embeddedConnection != EmbeddedDatabaseConnection.NONE) {
return initializeEmbeddedOptions(properties, embeddedConnection);
}
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL", properties,
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL", null,
embeddedConnection);
}
private ConnectionFactoryOptions.Builder initializeRegularOptions(R2dbcProperties properties) {
ConnectionFactoryOptions urlOptions = ConnectionFactoryOptions.parse(properties.getUrl());
Builder optionsBuilder = urlOptions.mutate();
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.USER, properties::getUsername,
StringUtils::hasText);
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.PASSWORD, properties::getPassword,
StringUtils::hasText);
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.DATABASE,
() -> determineDatabaseName(properties), StringUtils::hasText);
if (properties.getProperties() != null) {
properties.getProperties().forEach((key, value) -> optionsBuilder.option(Option.valueOf(key), value));
}
return optionsBuilder;
}
private Builder initializeEmbeddedOptions(R2dbcProperties properties,
EmbeddedDatabaseConnection embeddedDatabaseConnection) {
String url = embeddedDatabaseConnection.getUrl(determineEmbeddedDatabaseName(properties));
if (url == null) {
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL",
properties, embeddedDatabaseConnection);
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL", url,
embeddedDatabaseConnection);
}
Builder builder = ConnectionFactoryOptions.parse(url).mutate();
String username = determineEmbeddedUsername(properties);
@@ -89,6 +76,11 @@ class ConnectionFactoryOptionsInitializer {
return builder;
}
private String determineEmbeddedDatabaseName(R2dbcProperties properties) {
String databaseName = determineDatabaseName(properties);
return (databaseName != null) ? databaseName : "testdb";
}
private String determineDatabaseName(R2dbcProperties properties) {
if (properties.isGenerateUniqueName()) {
return properties.determineUniqueName();
@@ -99,30 +91,14 @@ class ConnectionFactoryOptionsInitializer {
return null;
}
private String determineEmbeddedDatabaseName(R2dbcProperties properties) {
String databaseName = determineDatabaseName(properties);
return (databaseName != null) ? databaseName : "testdb";
}
private String determineEmbeddedUsername(R2dbcProperties properties) {
String username = ifHasText(properties.getUsername());
return (username != null) ? username : "sa";
}
private <T extends CharSequence> void configureIf(Builder optionsBuilder, ConnectionFactoryOptions originalOptions,
Option<T> option, Supplier<T> valueSupplier, Predicate<T> setIf) {
if (originalOptions.hasOption(option)) {
return;
}
T value = valueSupplier.get();
if (setIf.test(value)) {
optionsBuilder.option(option, value);
}
}
private ConnectionFactoryBeanCreationException connectionFactoryBeanCreationException(String message,
R2dbcProperties properties, EmbeddedDatabaseConnection embeddedDatabaseConnection) {
return new ConnectionFactoryBeanCreationException(message, properties, embeddedDatabaseConnection);
String r2dbcUrl, EmbeddedDatabaseConnection embeddedDatabaseConnection) {
return new ConnectionFactoryBeanCreationException(message, r2dbcUrl, embeddedDatabaseConnection);
}
private String ifHasText(String candidate) {
@@ -131,25 +107,25 @@ class ConnectionFactoryOptionsInitializer {
static class ConnectionFactoryBeanCreationException extends BeanCreationException {
private final R2dbcProperties properties;
private final String url;
private final EmbeddedDatabaseConnection embeddedDatabaseConnection;
ConnectionFactoryBeanCreationException(String message, R2dbcProperties properties,
ConnectionFactoryBeanCreationException(String message, String url,
EmbeddedDatabaseConnection embeddedDatabaseConnection) {
super(message);
this.properties = properties;
this.url = url;
this.embeddedDatabaseConnection = embeddedDatabaseConnection;
}
String getUrl() {
return this.url;
}
EmbeddedDatabaseConnection getEmbeddedDatabaseConnection() {
return this.embeddedDatabaseConnection;
}
R2dbcProperties getProperties() {
return this.properties;
}
}
}

View File

@@ -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.
@@ -16,16 +16,26 @@
package org.springframework.boot.autoconfigure.r2dbc;
import java.util.function.Predicate;
import java.util.function.Supplier;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.ConnectionFactoryOptions.Builder;
import io.r2dbc.spi.Option;
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.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for R2DBC.
@@ -42,4 +52,63 @@ import org.springframework.context.annotation.Import;
ConnectionFactoryConfigurations.GenericConfiguration.class, ConnectionFactoryDependentConfiguration.class })
public class R2dbcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(R2dbcConnectionDetails.class)
@ConditionalOnProperty("spring.r2dbc.url")
PropertiesR2dbcConnectionDetails propertiesR2dbcConnectionDetails(R2dbcProperties properties) {
return new PropertiesR2dbcConnectionDetails(properties);
}
/**
* Adapts {@link R2dbcProperties} to {@link R2dbcConnectionDetails}.
*/
static class PropertiesR2dbcConnectionDetails implements R2dbcConnectionDetails {
private final R2dbcProperties properties;
PropertiesR2dbcConnectionDetails(R2dbcProperties properties) {
this.properties = properties;
}
@Override
public ConnectionFactoryOptions getConnectionFactoryOptions() {
ConnectionFactoryOptions urlOptions = ConnectionFactoryOptions.parse(this.properties.getUrl());
Builder optionsBuilder = urlOptions.mutate();
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.USER, this.properties::getUsername,
StringUtils::hasText);
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.PASSWORD, this.properties::getPassword,
StringUtils::hasText);
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.DATABASE,
() -> determineDatabaseName(this.properties), StringUtils::hasText);
if (this.properties.getProperties() != null) {
this.properties.getProperties()
.forEach((key, value) -> optionsBuilder.option(Option.valueOf(key), value));
}
return optionsBuilder.build();
}
private <T extends CharSequence> void configureIf(Builder optionsBuilder,
ConnectionFactoryOptions originalOptions, Option<T> option, Supplier<T> valueSupplier,
Predicate<T> setIf) {
if (originalOptions.hasOption(option)) {
return;
}
T value = valueSupplier.get();
if (setIf.test(value)) {
optionsBuilder.option(option, value);
}
}
private String determineDatabaseName(R2dbcProperties properties) {
if (properties.isGenerateUniqueName()) {
return properties.determineUniqueName();
}
if (StringUtils.hasLength(properties.getName())) {
return properties.getName();
}
return null;
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.r2dbc;
import io.r2dbc.spi.ConnectionFactoryOptions;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to an SQL service using R2DBC.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface R2dbcConnectionDetails extends ConnectionDetails {
/**
* Connection factory options for connecting to the database.
* @return the connection factory options
*/
ConnectionFactoryOptions getConnectionFactoryOptions();
}

View File

@@ -0,0 +1,35 @@
/*
* 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.service.connection;
import org.springframework.boot.origin.OriginProvider;
/**
* Base interface for types that provide the details required to establish a connection to
* a remote service.
* <p>
* Implementation classes can also implement {@link OriginProvider} in order to provide
* origin information.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface ConnectionDetails {
}

View File

@@ -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.
*/
/**
* Support for service connections that affect auto-configuration.
*/
package org.springframework.boot.autoconfigure.service.connection;

View File

@@ -96,6 +96,9 @@ import static org.mockito.Mockito.mock;
* @author Gary Russell
* @author HaiTao Zhang
* @author Franjo Zilic
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
@ExtendWith(OutputCaptureExtension.class)
class RabbitAutoConfigurationTests {
@@ -169,6 +172,26 @@ class RabbitAutoConfigurationTests {
});
}
@Test
@SuppressWarnings("unchecked")
void testConnectionFactoryWithOverridesWhenUsingConnectionDetails() {
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) -> {
CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class);
assertThat(connectionFactory.getHost()).isEqualTo("rabbit.example.com");
assertThat(connectionFactory.getPort()).isEqualTo(12345);
assertThat(connectionFactory.getVirtualHost()).isEqualTo("/vhost-1");
assertThat(connectionFactory.getUsername()).isEqualTo("user-1");
assertThat(connectionFactory.getRabbitConnectionFactory().getPassword()).isEqualTo("password-1");
List<Address> addresses = (List<Address>) ReflectionTestUtils.getField(connectionFactory, "addresses");
assertThat(addresses).containsExactly(new Address("rabbit.example.com", 12345),
new Address("rabbit2.example.com", 23456));
});
}
@Test
@SuppressWarnings("unchecked")
void testConnectionFactoryWithCustomConnectionNameStrategy() {
@@ -1218,6 +1241,38 @@ class RabbitAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
RabbitConnectionDetails rabbitConnectionDetails() {
return new RabbitConnectionDetails() {
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
@Override
public String getVirtualHost() {
return "/vhost-1";
}
@Override
public List<Address> getAddresses() {
return List.of(new Address("rabbit.example.com", 12345), new Address("rabbit2.example.com", 23456));
}
};
}
}
static class TestListener {
@RabbitListener(queues = "test", autoStartup = "false")

View File

@@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.cassandra;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
@@ -45,6 +46,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Ittay Stern
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class CassandraAutoConfigurationTests {
@@ -90,6 +94,26 @@ class CassandraAutoConfigurationTests {
});
}
@Test
void shouldUseConnectionDetails() {
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);
DriverExecutionProfile configuration = context.getBean(DriverConfigLoader.class)
.getInitialConfig()
.getDefaultProfile();
assertThat(configuration.getStringList(DefaultDriverOption.CONTACT_POINTS))
.containsOnly("cassandra.example.com:9042");
assertThat(configuration.getString(DefaultDriverOption.AUTH_PROVIDER_USER_NAME)).isEqualTo("user-1");
assertThat(configuration.getString(DefaultDriverOption.AUTH_PROVIDER_PASSWORD)).isEqualTo("secret-1");
assertThat(configuration.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER))
.isEqualTo("datacenter-1");
});
}
@Test
void driverConfigLoaderWithContactPointAndNoPort() {
this.contextRunner
@@ -310,6 +334,32 @@ class CassandraAutoConfigurationTests {
});
}
private CassandraConnectionDetails cassandraConnectionDetails() {
return new CassandraConnectionDetails() {
@Override
public List<Node> getContactPoints() {
return List.of(new Node("cassandra.example.com", 9042));
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "secret-1";
}
@Override
public String getLocalDatacenter() {
return "datacenter-1";
}
};
}
@Configuration(proxyBeanMethods = false)
static class SimpleDriverConfigLoaderBuilderCustomizerConfig {

View File

@@ -48,6 +48,9 @@ import static org.mockito.Mockito.mock;
*
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class CouchbaseAutoConfigurationTests {
@@ -60,6 +63,19 @@ class CouchbaseAutoConfigurationTests {
.doesNotHaveBean(Cluster.class));
}
@Test
void shouldUseConnectionDetails() {
this.contextRunner.withBean(CouchbaseConnectionDetails.class, this::couchbaseConnectionDetails)
.run((context) -> {
assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class);
Cluster cluster = context.getBean(Cluster.class);
assertThat(cluster.core()).extracting("connectionString.hosts")
.asList()
.extractingResultOf("host")
.containsExactly("couchbase.example.com");
});
}
@Test
void connectionStringCreateEnvironmentAndCluster() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
@@ -71,6 +87,21 @@ class CouchbaseAutoConfigurationTests {
});
}
@Test
void connectionDetailsShouldOverrideProperties() {
this.contextRunner.withBean(CouchbaseConnectionDetails.class, this::couchbaseConnectionDetails)
.withPropertyValues("spring.couchbase.connection-string=localhost", "spring.couchbase.username=a-user",
"spring.couchbase.password=a-password")
.run((context) -> {
assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class);
Cluster cluster = context.getBean(Cluster.class);
assertThat(cluster.core()).extracting("connectionString.hosts")
.asList()
.extractingResultOf("host")
.containsExactly("couchbase.example.com");
});
}
@Test
void whenObjectMapperBeanIsDefinedThenClusterEnvironmentObjectMapperIsDerivedFromIt() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
@@ -176,6 +207,27 @@ class CouchbaseAutoConfigurationTests {
});
}
private CouchbaseConnectionDetails couchbaseConnectionDetails() {
return new CouchbaseConnectionDetails() {
@Override
public String getConnectionString() {
return "couchbase.example.com";
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
};
}
@Configuration(proxyBeanMethods = false)
static class ClusterEnvironmentCustomizerConfiguration {

View File

@@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.data.mongo;
import java.time.LocalDateTime;
import java.util.Arrays;
import com.mongodb.ConnectionString;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.junit.jupiter.api.Test;
@@ -31,6 +32,7 @@ import org.springframework.boot.autoconfigure.data.mongo.city.City;
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.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -58,6 +60,9 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Josh Long
* @author Oliver Gierke
* @author Mark Paluch
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class MongoDataAutoConfigurationTests {
@@ -80,6 +85,17 @@ class MongoDataAutoConfigurationTests {
});
}
@Test
void usesMongoConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(GridFsTemplate.class);
GridFsTemplate template = context.getBean(GridFsTemplate.class);
assertThat(template).hasFieldOrPropertyWithValue("bucket", "connection-details-bucket");
MongoDatabaseFactory factory = (MongoDatabaseFactory) ReflectionTestUtils.getField(template, "dbFactory");
assertThat(factory.getMongoDatabase().getName()).isEqualTo("grid-database-1");
});
}
@Test
void whenGridFsBucketIsConfiguredThenGridFsTemplateIsAutoConfiguredAndUsesIt() {
this.contextRunner.withPropertyValues("spring.data.mongodb.gridfs.bucket:test-bucket").run((context) -> {
@@ -250,6 +266,28 @@ class MongoDataAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
MongoConnectionDetails mongoConnectionDetails() {
return new MongoConnectionDetails() {
@Override
public ConnectionString getConnectionString() {
return new ConnectionString("mongodb://localhost/db");
}
@Override
public GridFs getGridFs() {
return GridFs.of("grid-database-1", "connection-details-bucket");
}
};
}
}
static class MyConverter implements Converter<MongoClient, Boolean> {
@Override

View File

@@ -16,13 +16,17 @@
package org.springframework.boot.autoconfigure.data.mongo;
import com.mongodb.ConnectionString;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails;
import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.gridfs.ReactiveGridFsTemplate;
@@ -35,6 +39,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Mark Paluch
* @author Artsiom Yudovin
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class MongoReactiveDataAutoConfigurationTests {
@@ -58,6 +65,15 @@ class MongoReactiveDataAutoConfigurationTests {
.run((context) -> assertThat(grisFsTemplateDatabaseName(context)).isEqualTo("grid"));
}
@Test
void usesMongoConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> {
assertThat(grisFsTemplateDatabaseName(context)).isEqualTo("grid-database-1");
ReactiveGridFsTemplate template = context.getBean(ReactiveGridFsTemplate.class);
assertThat(template).hasFieldOrPropertyWithValue("bucket", "connection-details-bucket");
});
}
@Test
void whenGridFsBucketIsConfiguredThenGridFsTemplateUsesIt() {
this.contextRunner.withPropertyValues("spring.data.mongodb.gridfs.bucket:test-bucket").run((context) -> {
@@ -82,4 +98,38 @@ class MongoReactiveDataAutoConfigurationTests {
return factory.getMongoDatabase().block().getName();
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
MongoConnectionDetails mongoConnectionDetails() {
return new MongoConnectionDetails() {
@Override
public ConnectionString getConnectionString() {
return new ConnectionString("mongodb://localhost/db");
}
@Override
public GridFs getGridFs() {
return new GridFs() {
@Override
public String getDatabase() {
return "grid-database-1";
}
@Override
public String getBucket() {
return "connection-details-bucket";
}
};
}
};
}
}
}

View File

@@ -39,6 +39,9 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Mark Paluch
* @author Stephane Nicoll
* @author Weix Sun
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
@ClassPathExclusions("lettuce-core-*.jar")
class RedisAutoConfigurationJedisTests {
@@ -79,6 +82,14 @@ class RedisAutoConfigurationJedisTests {
});
}
@Test
void usesConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> {
JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
assertThat(cf.isUseSsl()).isFalse();
});
}
@Test
void testRedisUrlConfiguration() {
this.contextRunner
@@ -240,6 +251,35 @@ class RedisAutoConfigurationJedisTests {
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
RedisConnectionDetails redisConnectionDetails() {
return new RedisConnectionDetails() {
@Override
public Standalone getStandalone() {
return new Standalone() {
@Override
public String getHost() {
return "localhost";
}
@Override
public int getPort() {
return 6379;
}
};
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class JedisConnectionFactoryCaptorConfiguration {

View File

@@ -42,6 +42,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
@@ -71,6 +72,9 @@ import static org.mockito.Mockito.mock;
* @author Alen Turkovic
* @author Scott Frederick
* @author Weix Sun
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class RedisAutoConfigurationTests {
@@ -490,6 +494,51 @@ class RedisAutoConfigurationTests {
(options) -> assertThat(options.getTopologyRefreshOptions().useDynamicRefreshSources()).isTrue()));
}
@Test
void usesStandaloneFromConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsStandaloneConfiguration.class).run((context) -> {
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.isUseSsl()).isFalse();
RedisStandaloneConfiguration configuration = cf.getStandaloneConfiguration();
assertThat(configuration.getHostName()).isEqualTo("redis.example.com");
assertThat(configuration.getPort()).isEqualTo(16379);
assertThat(configuration.getDatabase()).isOne();
assertThat(configuration.getUsername()).isEqualTo("user-1");
assertThat(configuration.getPassword()).isEqualTo(RedisPassword.of("password-1"));
});
}
@Test
void usesSentinelFromConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsSentinelConfiguration.class).run((context) -> {
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.isUseSsl()).isFalse();
RedisSentinelConfiguration configuration = cf.getSentinelConfiguration();
assertThat(configuration).isNotNull();
assertThat(configuration.getSentinelUsername()).isEqualTo("sentinel-1");
assertThat(configuration.getSentinelPassword().get()).isEqualTo("secret-1".toCharArray());
assertThat(configuration.getSentinels()).containsExactly(new RedisNode("node-1", 12345));
assertThat(configuration.getUsername()).isEqualTo("user-1");
assertThat(configuration.getPassword()).isEqualTo(RedisPassword.of("password-1"));
assertThat(configuration.getDatabase()).isOne();
assertThat(configuration.getMaster().getName()).isEqualTo("master.redis.example.com");
});
}
@Test
void usesClusterFromConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsClusterConfiguration.class).run((context) -> {
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.isUseSsl()).isFalse();
RedisClusterConfiguration configuration = cf.getClusterConfiguration();
assertThat(configuration).isNotNull();
assertThat(configuration.getUsername()).isEqualTo("user-1");
assertThat(configuration.getPassword().get()).isEqualTo("password-1".toCharArray());
assertThat(configuration.getClusterNodes()).containsExactly(new RedisNode("node-1", 12345),
new RedisNode("node-2", 23456));
});
}
private <T extends ClientOptions> ContextConsumer<AssertableApplicationContext> assertClientOptions(
Class<T> expectedType, Consumer<T> options) {
return (context) -> {
@@ -532,4 +581,136 @@ class RedisAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsStandaloneConfiguration {
@Bean
RedisConnectionDetails redisConnectionDetails() {
return new RedisConnectionDetails() {
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
@Override
public Standalone getStandalone() {
return new Standalone() {
@Override
public int getDatabase() {
return 1;
}
@Override
public String getHost() {
return "redis.example.com";
}
@Override
public int getPort() {
return 16379;
}
};
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsSentinelConfiguration {
@Bean
RedisConnectionDetails redisConnectionDetails() {
return new RedisConnectionDetails() {
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
@Override
public Sentinel getSentinel() {
return new Sentinel() {
@Override
public int getDatabase() {
return 1;
}
@Override
public String getMaster() {
return "master.redis.example.com";
}
@Override
public List<Node> getNodes() {
return List.of(new Node("node-1", 12345));
}
@Override
public String getUsername() {
return "sentinel-1";
}
@Override
public String getPassword() {
return "secret-1";
}
};
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsClusterConfiguration {
@Bean
RedisConnectionDetails redisConnectionDetails() {
return new RedisConnectionDetails() {
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
@Override
public Cluster getCluster() {
return new Cluster() {
@Override
public List<Node> getNodes() {
return List.of(new Node("node-1", 12345), new Node("node-2", 23456));
}
};
}
};
}
}
}

View File

@@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.elasticsearch;
import java.time.Duration;
import java.util.List;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
@@ -32,6 +33,7 @@ import org.elasticsearch.client.sniff.Sniffer;
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.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@@ -49,6 +51,8 @@ import static org.mockito.Mockito.mock;
* @author Evgeniy Cheban
* @author Filip Hrisafov
* @author Andy Wilkinson
* @author Moritz Halbritter
* @author Phillip Webb
*/
class ElasticsearchRestClientAutoConfigurationTests {
@@ -243,6 +247,65 @@ class ElasticsearchRestClientAutoConfigurationTests {
});
}
@Test
void connectionDetailsAreUsedIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(RestClient.class);
RestClient restClient = context.getBean(RestClient.class);
assertThat(restClient).hasFieldOrPropertyWithValue("pathPrefix", "/some-path");
assertThat(restClient.getNodes().stream().map(Node::getHost).map(HttpHost::toString))
.containsExactly("http://elastic.example.com:9200");
assertThat(restClient)
.extracting("client.credentialsProvider", InstanceOfAssertFactories.type(CredentialsProvider.class))
.satisfies((credentialsProvider) -> {
Credentials uriCredentials = credentialsProvider
.getCredentials(new AuthScope("any.elastic.example.com", 80));
assertThat(uriCredentials.getUserPrincipal().getName()).isEqualTo("user-1");
assertThat(uriCredentials.getPassword()).isEqualTo("password-1");
})
.satisfies((credentialsProvider) -> {
Credentials uriCredentials = credentialsProvider
.getCredentials(new AuthScope("elastic.example.com", 9200));
assertThat(uriCredentials.getUserPrincipal().getName()).isEqualTo("node-user-1");
assertThat(uriCredentials.getPassword()).isEqualTo("node-password-1");
});
});
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
ElasticsearchConnectionDetails elasticsearchConnectionDetails() {
return new ElasticsearchConnectionDetails() {
@Override
public List<Node> getNodes() {
return List
.of(new Node("elastic.example.com", 9200, Protocol.HTTP, "node-user-1", "node-password-1"));
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
@Override
public String getPathPrefix() {
return "/some-path";
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class BuilderCustomizerConfiguration {

View File

@@ -49,6 +49,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.SchemaManagement;
@@ -120,6 +121,16 @@ class FlywayAutoConfigurationTests {
});
}
@Test
void createsDataSourceWithNoDataSourceBeanAndJdbcConnectionDetails() {
this.contextRunner
.withUserConfiguration(JdbcConnectionDetailsConfiguration.class, MockFlywayMigrationStrategy.class)
.run((context) -> {
assertThat(context).hasSingleBean(Flyway.class);
assertThat(context.getBean(Flyway.class).getConfiguration().getDataSource()).isNotNull();
});
}
@Test
void backsOffWithFlywayUrlAndNoSpringJdbc() {
this.contextRunner.withPropertyValues("spring.flyway.url:jdbc:hsqldb:mem:" + UUID.randomUUID())
@@ -137,6 +148,28 @@ class FlywayAutoConfigurationTests {
});
}
@Test
void createDataSourceWithJdbcConnectionDetails() {
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: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);
});
}
@Test
void createDataSourceWithUser() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
@@ -200,6 +233,19 @@ class FlywayAutoConfigurationTests {
});
}
@Test
void flywayDataSourceIsUsedWhenJdbcConnectionDetailsIsAvailable() {
this.contextRunner
.withUserConfiguration(FlywayDataSourceConfiguration.class, EmbeddedDataSourceConfiguration.class,
JdbcConnectionDetailsConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.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) -> {
@@ -1036,4 +1082,31 @@ class FlywayAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class JdbcConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new JdbcConnectionDetails() {
@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";
}
};
}
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.influx;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
@@ -38,6 +39,9 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Sergey Kuptsov
* @author Stephane Nicoll
* @author Eddú Meléndez
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class InfluxDbAutoConfigurationTests {
@@ -49,6 +53,27 @@ class InfluxDbAutoConfigurationTests {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(InfluxDB.class));
}
@Test
void shouldUseConnectionDetails() {
this.contextRunner.withBean(InfluxDbConnectionDetails.class, this::influxDbConnectionDetails).run((context) -> {
assertThat(context).hasSingleBean(InfluxDB.class);
InfluxDB influxDb = context.getBean(InfluxDB.class);
assertThat(influxDb).hasFieldOrPropertyWithValue("hostName", "localhost");
});
}
@Test
void connectionDetailsOverwriteProperties() {
this.contextRunner.withBean(InfluxDbConnectionDetails.class, this::influxDbConnectionDetails)
.withPropertyValues("spring.influx.url=http://some-other-host", "spring.influx.user=user",
"spring.influx.password=password")
.run((context) -> {
assertThat(context).hasSingleBean(InfluxDB.class);
InfluxDB influxDb = context.getBean(InfluxDB.class);
assertThat(influxDb).hasFieldOrPropertyWithValue("hostName", "localhost");
});
}
@Test
void influxDbCanBeCustomized() {
this.contextRunner
@@ -95,6 +120,27 @@ class InfluxDbAutoConfigurationTests {
return callFactory.readTimeoutMillis();
}
private InfluxDbConnectionDetails influxDbConnectionDetails() {
return new InfluxDbConnectionDetails() {
@Override
public URI getUrl() {
return URI.create("http://localhost");
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
};
}
@Configuration(proxyBeanMethods = false)
static class CustomOkHttpClientBuilderProviderConfig {

View File

@@ -36,6 +36,7 @@ import com.zaxxer.hikari.HikariDataSource;
import io.r2dbc.spi.ConnectionFactory;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.apache.commons.dbcp2.BasicDataSource;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
@@ -60,6 +61,9 @@ import static org.mockito.Mockito.mock;
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class DataSourceAutoConfigurationTests {
@@ -244,6 +248,41 @@ class DataSourceAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(DataSourceScriptDatabaseInitializer.class));
}
@Test
void dbcp2UsesJdbcConnectionDetailsIfAvailable() {
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) -> {
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).asInstanceOf(InstanceOfAssertFactories.type(BasicDataSource.class))
.satisfies((dbcp2) -> {
assertThat(dbcp2.getUsername()).isEqualTo("user-1");
assertThat(dbcp2.getPassword()).isEqualTo("password-1");
assertThat(dbcp2.getDriverClassName()).isEqualTo("org.postgresql.Driver");
assertThat(dbcp2.getUrl()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
});
});
}
@Test
void genericUsesJdbcConnectionDetailsIfAvailable() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.type=" + TestDataSource.class.getName())
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class));
runner.withUserConfiguration(JdbcConnectionDetailsConfiguration.class).run((context) -> {
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(TestDataSource.class);
TestDataSource source = (TestDataSource) dataSource;
assertThat(source.getUsername()).isEqualTo("user-1");
assertThat(source.getPassword()).isEqualTo("password-1");
assertThat(source.getDriver().getClass().getName()).isEqualTo("org.postgresql.Driver");
assertThat(source.getUrl()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
});
}
private static Function<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() {
return (runner) -> runner.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari",
"org.apache.commons.dbcp2", "oracle.ucp.jdbc", "com.mchange"));
@@ -259,6 +298,16 @@ class DataSourceAutoConfigurationTests {
});
}
@Configuration(proxyBeanMethods = false)
static class JdbcConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails sqlJdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
@Configuration(proxyBeanMethods = false)
static class TestDataSourceConfiguration {

View File

@@ -0,0 +1,50 @@
/*
* 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;
import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Dbcp2JdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class Dbcp2JdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordAndUrl() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("will-be-overwritten");
dataSource.setUsername("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setDriverClassName("will-be-overwritten");
new Dbcp2JdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getUrl()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("password-1");
assertThat(dataSource.getDriverClassName()).isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@@ -19,10 +19,13 @@ package org.springframework.boot.autoconfigure.jdbc;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
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;
@@ -31,9 +34,14 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class HikariDataSourceConfigurationTests {
private static final String PREFIX = "spring.datasource.hikari.";
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName());
@@ -49,8 +57,7 @@ class HikariDataSourceConfigurationTests {
@Test
void testDataSourcePropertiesOverridden() {
this.contextRunner
.withPropertyValues("spring.datasource.hikari.jdbc-url=jdbc:foo//bar/spam",
"spring.datasource.hikari.max-lifetime=1234")
.withPropertyValues(PREFIX + "jdbc-url=jdbc:foo//bar/spam", "spring.datasource.hikari.max-lifetime=1234")
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getJdbcUrl()).isEqualTo("jdbc:foo//bar/spam");
@@ -61,8 +68,7 @@ class HikariDataSourceConfigurationTests {
@Test
void testDataSourceGenericPropertiesOverridden() {
this.contextRunner
.withPropertyValues(
"spring.datasource.hikari.data-source-properties.dataSourceClassName=org.h2.JDBCDataSource")
.withPropertyValues(PREFIX + "data-source-properties.dataSourceClassName=org.h2.JDBCDataSource")
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getDataSourceProperties().getProperty("dataSourceClassName"))
@@ -90,12 +96,38 @@ class HikariDataSourceConfigurationTests {
@Test
void poolNameTakesPrecedenceOverName() {
this.contextRunner
.withPropertyValues("spring.datasource.name=myDS", "spring.datasource.hikari.pool-name=myHikariDS")
this.contextRunner.withPropertyValues("spring.datasource.name=myDS", PREFIX + "pool-name=myHikariDS")
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getPoolName()).isEqualTo("myHikariDS");
});
}
@Test
void usesConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).asInstanceOf(InstanceOfAssertFactories.type(HikariDataSource.class))
.satisfies((hikari) -> {
assertThat(hikari.getUsername()).isEqualTo("user-1");
assertThat(hikari.getPassword()).isEqualTo("password-1");
assertThat(hikari.getDriverClassName()).isEqualTo("org.postgresql.Driver");
assertThat(hikari.getJdbcUrl())
.isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
});
});
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails sqlConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HikariJdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class HikariJdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordAndUrl() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("will-be-overwritten");
dataSource.setUsername("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setDriverClassName(DatabaseDriver.H2.getDriverClassName());
new HikariJdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getJdbcUrl()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("password-1");
assertThat(dataSource.getDriverClassName()).isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@@ -22,10 +22,13 @@ import javax.sql.DataSource;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import oracle.ucp.util.OpaqueString;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
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;
@@ -34,9 +37,14 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Fabio Grassi
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class OracleUcpDataSourceConfigurationTests {
private static final String PREFIX = "spring.datasource.oracleucp.";
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.type=" + PoolDataSource.class.getName());
@@ -54,9 +62,7 @@ class OracleUcpDataSourceConfigurationTests {
@Test
void testDataSourcePropertiesOverridden() {
this.contextRunner
.withPropertyValues("spring.datasource.oracleucp.url=jdbc:foo//bar/spam",
"spring.datasource.oracleucp.max-idle-time=1234")
this.contextRunner.withPropertyValues(PREFIX + "url=jdbc:foo//bar/spam", PREFIX + "max-idle-time=1234")
.run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getURL()).isEqualTo("jdbc:foo//bar/spam");
@@ -66,11 +72,10 @@ class OracleUcpDataSourceConfigurationTests {
@Test
void testDataSourceConnectionPropertiesOverridden() {
this.contextRunner.withPropertyValues("spring.datasource.oracleucp.connection-properties.autoCommit=false")
.run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false");
});
this.contextRunner.withPropertyValues(PREFIX + "connection-properties.autoCommit=false").run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false");
});
}
@Test
@@ -100,12 +105,38 @@ class OracleUcpDataSourceConfigurationTests {
@Test
void poolNameTakesPrecedenceOverName() {
this.contextRunner
.withPropertyValues("spring.datasource.name=myDS",
"spring.datasource.oracleucp.connection-pool-name=myOracleUcpDS")
.withPropertyValues("spring.datasource.name=myDS", PREFIX + "connection-pool-name=myOracleUcpDS")
.run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionPoolName()).isEqualTo("myOracleUcpDS");
});
}
@Test
void usesJdbcConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(PoolDataSourceImpl.class);
PoolDataSourceImpl oracleUcp = (PoolDataSourceImpl) dataSource;
assertThat(oracleUcp.getUser()).isEqualTo("user-1");
assertThat(oracleUcp).extracting("password")
.extracting((o) -> ((OpaqueString) o).get())
.isEqualTo("password-1");
assertThat(oracleUcp.getConnectionFactoryClassName()).isEqualTo("org.postgresql.Driver");
assertThat(oracleUcp.getURL()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
});
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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;
import java.sql.SQLException;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import oracle.ucp.util.OpaqueString;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OracleUcpJdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class OracleUcpJdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordAndUrl() throws SQLException {
PoolDataSourceImpl dataSource = new PoolDataSourceImpl();
dataSource.setURL("will-be-overwritten");
dataSource.setUser("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setConnectionFactoryClassName("will-be-overwritten");
new OracleUcpJdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getURL()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUser()).isEqualTo("user-1");
assertThat(dataSource).extracting("password")
.extracting((password) -> ((OpaqueString) password).get())
.isEqualTo("password-1");
assertThat(dataSource.getConnectionFactoryClassName())
.isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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;
/**
* {@link JdbcConnectionDetails} used in tests.
*
* @author Moritz Halbritter
*/
class TestJdbcConnectionDetails implements JdbcConnectionDetails {
@Override
public String getJdbcUrl() {
return "jdbc:postgresql://postgres.example.com:12345/database-1";
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
}

View File

@@ -24,9 +24,11 @@ import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -41,6 +43,9 @@ import static org.junit.jupiter.api.Assertions.fail;
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class TomcatDataSourceConfigurationTests {
@@ -48,6 +53,10 @@ class TomcatDataSourceConfigurationTests {
private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.type=" + org.apache.tomcat.jdbc.pool.DataSource.class.getName());
@BeforeEach
void init() {
TestPropertyValues.of(PREFIX + "initialize:false").applyTo(this.context);
@@ -106,6 +115,32 @@ class TomcatDataSourceConfigurationTests {
assertThat(ds.getValidationInterval()).isEqualTo(3000L);
}
@Test
void usesJdbcConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
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;
assertThat(tomcat.getPoolProperties().getUsername()).isEqualTo("user-1");
assertThat(tomcat.getPoolProperties().getPassword()).isEqualTo("password-1");
assertThat(tomcat.getPoolProperties().getDriverClassName()).isEqualTo("org.postgresql.Driver");
assertThat(tomcat.getPoolProperties().getUrl())
.isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
});
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@EnableMBeanExport

View File

@@ -0,0 +1,51 @@
/*
* 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;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link TomcatJdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class TomcatJdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordAndUrl() {
DataSource dataSource = new DataSource();
dataSource.setUrl("will-be-overwritten");
dataSource.setUsername("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setDriverClassName("will-be-overwritten");
new TomcatJdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getUrl()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPoolProperties().getPassword()).isEqualTo("password-1");
assertThat(dataSource.getPoolProperties().getDriverClassName())
.isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@@ -22,6 +22,7 @@ import javax.sql.XADataSource;
import com.ibm.db2.jcc.DB2XADataSource;
import org.hsqldb.jdbc.pool.JDBCXADataSource;
import org.junit.jupiter.api.Test;
import org.postgresql.xa.PGXADataSource;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.XADataSourceWrapper;
@@ -40,6 +41,8 @@ import static org.mockito.Mockito.mock;
* Tests for {@link XADataSourceAutoConfiguration}.
*
* @author Phillip Webb
* @author Moritz Halbritter
* @author Andy Wilkinson
*/
class XADataSourceAutoConfigurationTests {
@@ -90,6 +93,20 @@ class XADataSourceAutoConfigurationTests {
assertThat(dataSource.getLoginTimeout()).isEqualTo(123);
}
@Test
void shouldUseConnectionDetailsIfAvailable() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(XADataSourceAutoConfiguration.class))
.withUserConfiguration(FromProperties.class, JdbcConnectionDetailsConfiguration.class)
.run((context) -> {
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
PGXADataSource dataSource = (PGXADataSource) wrapper.getXaDataSource();
assertThat(dataSource).isNotNull();
assertThat(dataSource.getUrl()).startsWith("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUser()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("password-1");
});
}
private ApplicationContext createContext(Class<?> configuration, String... env) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of(env).applyTo(context);
@@ -98,6 +115,16 @@ class XADataSourceAutoConfigurationTests {
return context;
}
@Configuration(proxyBeanMethods = false)
static class JdbcConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
@Configuration(proxyBeanMethods = false)
static class WrapExisting {

View File

@@ -99,6 +99,9 @@ import static org.mockito.Mockito.never;
* @author Nakul Mishra
* @author Tomaz Fernandes
* @author Thomas Kåsene
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class KafkaAutoConfigurationTests {
@@ -161,6 +164,24 @@ class KafkaAutoConfigurationTests {
});
}
@Test
void connectionDetailsAreAppliedToConsumer() {
this.contextRunner
.withPropertyValues("spring.kafka.bootstrap-servers=foo:1234",
"spring.kafka.consumer.bootstrap-servers=foo:1234", "spring.kafka.security.protocol=SSL",
"spring.kafka.consumer.security.protocol=SSL")
.withBean(KafkaConnectionDetails.class, this::kafkaConnectionDetails)
.run((context) -> {
DefaultKafkaConsumerFactory<?, ?> consumerFactory = context.getBean(DefaultKafkaConsumerFactory.class);
Map<String, Object> configs = consumerFactory.getConfigurationProperties();
assertThat(configs).containsEntry(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
Collections.singletonList("kafka.example.com:12345"));
assertThat(configs).containsEntry(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
Collections.singletonList("kafka.example.com:12345"));
assertThat(configs).containsEntry(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
});
}
@Test
void producerProperties() {
this.contextRunner.withPropertyValues("spring.kafka.clientId=cid",
@@ -211,6 +232,24 @@ class KafkaAutoConfigurationTests {
});
}
@Test
void connectionDetailsAreAppliedToProducer() {
this.contextRunner
.withPropertyValues("spring.kafka.bootstrap-servers=foo:1234",
"spring.kafka.producer.bootstrap-servers=foo:1234", "spring.kafka.security.protocol=SSL",
"spring.kafka.producer.security.protocol=SSL")
.withBean(KafkaConnectionDetails.class, this::kafkaConnectionDetails)
.run((context) -> {
DefaultKafkaProducerFactory<?, ?> producerFactory = context.getBean(DefaultKafkaProducerFactory.class);
Map<String, Object> configs = producerFactory.getConfigurationProperties();
assertThat(configs).containsEntry(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
Collections.singletonList("kafka.example.com:12345"));
assertThat(configs).containsEntry(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
Collections.singletonList("kafka.example.com:12345"));
assertThat(configs).containsEntry(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
});
}
@Test
void adminProperties() {
this.contextRunner
@@ -252,6 +291,24 @@ class KafkaAutoConfigurationTests {
});
}
@Test
void connectionDetailsAreAppliedToAdmin() {
this.contextRunner
.withPropertyValues("spring.kafka.bootstrap-servers=foo:1234", "spring.kafka.security.protocol=SSL",
"spring.kafka.admin.security.protocol=SSL")
.withBean(KafkaConnectionDetails.class, this::kafkaConnectionDetails)
.run((context) -> {
KafkaAdmin admin = context.getBean(KafkaAdmin.class);
Map<String, Object> configs = admin.getConfigurationProperties();
assertThat(configs).containsEntry(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
Collections.singletonList("kafka.example.com:12345"));
assertThat(configs).containsEntry(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,
Collections.singletonList("kafka.example.com:12345"));
assertThat(configs).containsEntry(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
assertThat(configs).containsEntry(AdminClientConfig.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
});
}
@SuppressWarnings("unchecked")
@Test
void streamsProperties() {
@@ -298,6 +355,27 @@ class KafkaAutoConfigurationTests {
});
}
@Test
void connectionDetailsAreAppliedToStreams() {
this.contextRunner.withUserConfiguration(EnableKafkaStreamsConfiguration.class)
.withPropertyValues("spring.kafka.streams.auto-startup=false", "spring.kafka.streams.application-id=test",
"spring.kafka.bootstrap-servers=foo:1234", "spring.kafka.streams.bootstrap-servers=foo:1234",
"spring.kafka.security.protocol=SSL", "spring.kafka.streams.security.protocol=SSL")
.withBean(KafkaConnectionDetails.class, this::kafkaConnectionDetails)
.run((context) -> {
Properties configs = context
.getBean(KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME,
KafkaStreamsConfiguration.class)
.asProperties();
assertThat(configs).containsEntry(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
Collections.singletonList("kafka.example.com:12345"));
assertThat(configs).containsEntry(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,
Collections.singletonList("kafka.example.com:12345"));
assertThat(configs).containsEntry(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
assertThat(configs).containsEntry(StreamsConfig.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
});
}
@SuppressWarnings("deprecation")
@Deprecated(since = "3.1.0", forRemoval = true)
void streamsCacheMaxSizeBuffering() {
@@ -744,6 +822,17 @@ class KafkaAutoConfigurationTests {
});
}
private KafkaConnectionDetails kafkaConnectionDetails() {
return new KafkaConnectionDetails() {
@Override
public List<Node> getBootstrapNodes() {
return List.of(new Node("kafka.example.com", 12345));
}
};
}
@Configuration(proxyBeanMethods = false)
static class MessageConverterConfiguration {

View File

@@ -28,6 +28,7 @@ import java.util.function.Consumer;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import liquibase.integration.spring.SpringLiquibase;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -39,6 +40,7 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseAutoConfigurationRuntimeHints;
@@ -75,6 +77,8 @@ import static org.assertj.core.api.Assertions.contentOf;
* @author Andrii Hrytsiuk
* @author Ferenc Gratzer
* @author Evgeniy Cheban
* @author Moritz Halbritter
* @author Phillip Webb
*/
@ExtendWith(OutputCaptureExtension.class)
class LiquibaseAutoConfigurationTests {
@@ -97,6 +101,18 @@ class LiquibaseAutoConfigurationTests {
}));
}
@Test
void createsDataSourceWithNoDataSourceBeanAndJdbcConnectionDetails() {
this.contextRunner.withSystemProperties("shouldRun=false")
.withUserConfiguration(JdbcConnectionDetailsConfiguration.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 backsOffWithLiquibaseUrlAndNoSpringJdbc() {
this.contextRunner.withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:" + UUID.randomUUID())
@@ -116,6 +132,30 @@ class LiquibaseAutoConfigurationTests {
}));
}
@Test
void jdbcConnectionDetailsAreUsedIfAvailable() {
this.contextRunner.withSystemProperties("shouldRun=false")
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JdbcConnectionDetailsConfiguration.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 liquibaseDataSourceIsUsedOverJdbcConnectionDetails() {
this.contextRunner
.withUserConfiguration(LiquibaseDataSourceConfiguration.class, JdbcConnectionDetailsConfiguration.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 changelogXml() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
@@ -509,6 +549,33 @@ class LiquibaseAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class JdbcConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new JdbcConnectionDetails() {
@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 {
}

View File

@@ -43,6 +43,7 @@ import static org.mockito.Mockito.mock;
* @author Mark Paluch
* @author Artsiom Yudovin
* @author Scott Frederick
* @author Mortiz Halbritter
*/
abstract class MongoClientFactorySupportTests<T> {
@@ -110,10 +111,6 @@ abstract class MongoClientFactorySupportTests<T> {
assertThat(properties.isAutoIndexCreation()).isTrue();
}
protected T createMongoClient() {
return createMongoClient(null, MongoClientSettings.builder().build());
}
protected T createMongoClient(MongoClientSettings settings) {
return createMongoClient(null, settings);
}

View File

@@ -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.
@@ -32,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Scott Frederick
*/
@Deprecated(since = "3.1.0", forRemoval = true)
class MongoPropertiesClientSettingsBuilderCustomizerTests {
private final MongoProperties properties = new MongoProperties();
@@ -188,6 +189,7 @@ class MongoPropertiesClientSettingsBuilderCustomizerTests {
assertThat(settings.getRetryWrites()).isFalse();
}
@SuppressWarnings("removal")
private MongoClientSettings customizeSettings() {
MongoClientSettings.Builder settings = MongoClientSettings.builder();
new MongoPropertiesClientSettingsBuilderCustomizer(this.properties).customize(settings);

View File

@@ -26,20 +26,19 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Config;
import org.neo4j.driver.Config.ConfigBuilder;
import org.neo4j.driver.Driver;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration.PropertiesNeo4jConnectionDetails;
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Authentication;
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Security.TrustStrategy;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.context.annotation.Bean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -50,6 +49,9 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*
* @author Michael J. Simons
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class Neo4jAutoConfigurationTests {
@@ -103,6 +105,22 @@ class Neo4jAutoConfigurationTests {
.hasMessageContaining("'%s' is not a supported scheme.", invalidScheme));
}
@Bean
void usesCustomConnectionDetails() {
this.contextRunner.withBean(Neo4jConnectionDetails.class, () -> new Neo4jConnectionDetails() {
@Override
public URI getUri() {
return URI.create("bolt+ssc://localhost:12345");
}
}).run((context) -> {
assertThat(context).hasSingleBean(Driver.class);
Driver driver = context.getBean(Driver.class);
assertThat(driver.isEncrypted()).isTrue();
});
}
@Test
void connectionTimeout() {
Neo4jProperties properties = new Neo4jProperties();
@@ -118,8 +136,8 @@ class Neo4jAutoConfigurationTests {
}
@Test
void determineServerUriShouldDefaultToLocalhost() {
assertThat(determineServerUri(new Neo4jProperties(), new MockEnvironment()))
void uriShouldDefaultToLocalhost() {
assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties()).getUri())
.isEqualTo(URI.create("bolt://localhost:7687"));
}
@@ -128,44 +146,52 @@ class Neo4jAutoConfigurationTests {
URI customUri = URI.create("bolt://localhost:4242");
Neo4jProperties properties = new Neo4jProperties();
properties.setUri(customUri);
assertThat(determineServerUri(properties, new MockEnvironment())).isEqualTo(customUri);
assertThat(new PropertiesNeo4jConnectionDetails(properties).getUri()).isEqualTo(customUri);
}
@Test
void authenticationShouldDefaultToNone() {
assertThat(mapAuthToken(new Authentication())).isEqualTo(AuthTokens.none());
assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties()).getAuthToken())
.isEqualTo(AuthTokens.none());
}
@Test
void authenticationWithUsernameShouldEnableBasicAuth() {
Authentication authentication = new Authentication();
authentication.setUsername("Farin");
authentication.setPassword("Urlaub");
assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.basic("Farin", "Urlaub"));
Neo4jProperties properties = new Neo4jProperties();
properties.getAuthentication().setUsername("Farin");
properties.getAuthentication().setPassword("Urlaub");
assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
.isEqualTo(AuthTokens.basic("Farin", "Urlaub"));
}
@Test
void authenticationWithUsernameAndRealmShouldEnableBasicAuth() {
Authentication authentication = new Authentication();
Neo4jProperties properties = new Neo4jProperties();
Authentication authentication = properties.getAuthentication();
authentication.setUsername("Farin");
authentication.setPassword("Urlaub");
authentication.setRealm("Test Realm");
assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm"));
assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
.isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm"));
}
@Test
void authenticationWithKerberosTicketShouldEnableKerberos() {
Authentication authentication = new Authentication();
Neo4jProperties properties = new Neo4jProperties();
Authentication authentication = properties.getAuthentication();
authentication.setKerberosTicket("AABBCCDDEE");
assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.kerberos("AABBCCDDEE"));
assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
.isEqualTo(AuthTokens.kerberos("AABBCCDDEE"));
}
@Test
void authenticationWithBothUsernameAndKerberosShouldNotBeAllowed() {
Authentication authentication = new Authentication();
Neo4jProperties properties = new Neo4jProperties();
Authentication authentication = properties.getAuthentication();
authentication.setUsername("Farin");
authentication.setKerberosTicket("AABBCCDDEE");
assertThatIllegalStateException().isThrownBy(() -> mapAuthToken(authentication))
assertThatIllegalStateException()
.isThrownBy(() -> new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
.withMessage("Cannot specify both username ('Farin') and kerberos ticket ('AABBCCDDEE')");
}
@@ -279,20 +305,9 @@ class Neo4jAutoConfigurationTests {
assertThat(mapDriverConfig(new Neo4jProperties()).logging()).isInstanceOf(Neo4jSpringJclLogging.class);
}
private URI determineServerUri(Neo4jProperties properties, Environment environment) {
return new Neo4jAutoConfiguration().determineServerUri(properties, environment);
}
private AuthToken mapAuthToken(Authentication authentication, Environment environment) {
return new Neo4jAutoConfiguration().mapAuthToken(authentication, environment);
}
private AuthToken mapAuthToken(Authentication authentication) {
return mapAuthToken(authentication, new MockEnvironment());
}
private Config mapDriverConfig(Neo4jProperties properties, ConfigBuilderCustomizer... customizers) {
return new Neo4jAutoConfiguration().mapDriverConfig(properties, Arrays.asList(customizers));
return new Neo4jAutoConfiguration().mapDriverConfig(properties,
new PropertiesNeo4jConnectionDetails(properties), Arrays.asList(customizers));
}
}

View File

@@ -27,6 +27,7 @@ import io.r2dbc.h2.H2ConnectionFactory;
import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.pool.PoolMetrics;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.ConnectionFactoryProvider;
import io.r2dbc.spi.Option;
import io.r2dbc.spi.Wrapped;
@@ -54,6 +55,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Mark Paluch
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class R2dbcAutoConfigurationTests {
@@ -68,7 +72,7 @@ class R2dbcAutoConfigurationTests {
assertThat(context.getBean(ConnectionPool.class)).extracting(ConnectionPool::unwrap)
.satisfies((connectionFactory) -> assertThat(connectionFactory)
.asInstanceOf(type(OptionsCapableConnectionFactory.class))
.extracting(Wrapped<ConnectionFactory>::unwrap)
.extracting(Wrapped::unwrap)
.isExactlyInstanceOf(H2ConnectionFactory.class));
});
}
@@ -306,6 +310,64 @@ class R2dbcAutoConfigurationTests {
.doesNotHaveBean(DatabaseClient.class));
}
@Test
void shouldUseCustomConnectionDetailsIfAvailable() {
this.contextRunner.withPropertyValues("spring.r2dbc.pool.enabled=false")
.withUserConfiguration(ConnectionDetailsConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class);
OptionsCapableConnectionFactory connectionFactory = context
.getBean(OptionsCapableConnectionFactory.class);
ConnectionFactoryOptions options = connectionFactory.getOptions();
assertThat(options.getValue(ConnectionFactoryOptions.DRIVER)).isEqualTo("postgresql");
assertThat(options.getValue(ConnectionFactoryOptions.HOST)).isEqualTo("postgres.example.com");
assertThat(options.getValue(ConnectionFactoryOptions.PORT)).isEqualTo(12345);
assertThat(options.getValue(ConnectionFactoryOptions.DATABASE)).isEqualTo("database-1");
assertThat(options.getValue(ConnectionFactoryOptions.USER)).isEqualTo("user-1");
assertThat(options.getValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("password-1");
});
}
@Test
void configureWithUsernamePasswordAndUrlWithoutUserInfoUsesUsernameAndPassword() {
this.contextRunner
.withPropertyValues("spring.r2dbc.pool.enabled=false",
"spring.r2dbc.url:r2dbc:postgresql://postgres.example.com:4321/db", "spring.r2dbc.username=alice",
"spring.r2dbc.password=secret")
.run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class);
OptionsCapableConnectionFactory connectionFactory = context
.getBean(OptionsCapableConnectionFactory.class);
ConnectionFactoryOptions options = connectionFactory.getOptions();
assertThat(options.getValue(ConnectionFactoryOptions.DRIVER)).isEqualTo("postgresql");
assertThat(options.getValue(ConnectionFactoryOptions.HOST)).isEqualTo("postgres.example.com");
assertThat(options.getValue(ConnectionFactoryOptions.PORT)).isEqualTo(4321);
assertThat(options.getValue(ConnectionFactoryOptions.DATABASE)).isEqualTo("db");
assertThat(options.getValue(ConnectionFactoryOptions.USER)).isEqualTo("alice");
assertThat(options.getValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret");
});
}
@Test
void configureWithUsernamePasswordAndUrlWithUserInfoUsesUserInfo() {
this.contextRunner
.withPropertyValues("spring.r2dbc.pool.enabled=false",
"spring.r2dbc.url:r2dbc:postgresql://bob:password@postgres.example.com:9876/db",
"spring.r2dbc.username=alice", "spring.r2dbc.password=secret")
.run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class);
OptionsCapableConnectionFactory connectionFactory = context
.getBean(OptionsCapableConnectionFactory.class);
ConnectionFactoryOptions options = connectionFactory.getOptions();
assertThat(options.getValue(ConnectionFactoryOptions.DRIVER)).isEqualTo("postgresql");
assertThat(options.getValue(ConnectionFactoryOptions.HOST)).isEqualTo("postgres.example.com");
assertThat(options.getValue(ConnectionFactoryOptions.PORT)).isEqualTo(9876);
assertThat(options.getValue(ConnectionFactoryOptions.DATABASE)).isEqualTo("db");
assertThat(options.getValue(ConnectionFactoryOptions.USER)).isEqualTo("bob");
assertThat(options.getValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("password");
});
}
private <T> InstanceOfAssertFactory<T, ObjectAssert<T>> type(Class<T> type) {
return InstanceOfAssertFactories.type(type);
}
@@ -342,4 +404,22 @@ class R2dbcAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
R2dbcConnectionDetails r2dbcConnectionDetails() {
return new R2dbcConnectionDetails() {
@Override
public ConnectionFactoryOptions getConnectionFactoryOptions() {
return ConnectionFactoryOptions
.parse("r2dbc:postgresql://user-1:password-1@postgres.example.com:12345/database-1");
}
};
}
}
}