Create spring-boot-mongodb module

This commit is contained in:
Andy Wilkinson
2025-03-26 12:11:30 +00:00
committed by Phillip Webb
parent 65a50949d8
commit 254f901f14
57 changed files with 151 additions and 111 deletions

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import org.springframework.beans.factory.ObjectProvider;
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.context.properties.EnableConfigurationProperties;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Mongo.
*
* @author Dave Syer
* @author Oliver Gierke
* @author Phillip Webb
* @author Mark Paluch
* @author Stephane Nicoll
* @author Scott Frederick
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass(MongoClient.class)
@EnableConfigurationProperties(MongoProperties.class)
@ConditionalOnMissingBean(type = "org.springframework.data.mongodb.MongoDatabaseFactory")
public class MongoAutoConfiguration {
@Bean
@ConditionalOnMissingBean(MongoConnectionDetails.class)
PropertiesMongoConnectionDetails mongoConnectionDetails(MongoProperties properties,
ObjectProvider<SslBundles> sslBundles) {
return new PropertiesMongoConnectionDetails(properties, sslBundles.getIfAvailable());
}
@Bean
@ConditionalOnMissingBean
public MongoClient mongo(ObjectProvider<MongoClientSettingsBuilderCustomizer> builderCustomizers,
MongoClientSettings settings) {
return new MongoClientFactory(builderCustomizers.orderedStream().toList()).createMongoClient(settings);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(MongoClientSettings.class)
static class MongoClientSettingsConfiguration {
@Bean
MongoClientSettings mongoClientSettings() {
return MongoClientSettings.builder().build();
}
@Bean
StandardMongoClientSettingsBuilderCustomizer standardMongoSettingsCustomizer(MongoProperties properties,
MongoConnectionDetails connectionDetails) {
return new StandardMongoClientSettingsBuilderCustomizer(connectionDetails,
properties.getUuidRepresentation());
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import java.util.List;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
/**
* A factory for a blocking {@link MongoClient}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Josh Long
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Nasko Vasilev
* @author Mark Paluch
* @author Scott Frederick
* @since 4.0.0
*/
public class MongoClientFactory extends MongoClientFactorySupport<MongoClient> {
/**
* Construct a factory for creating a blocking {@link MongoClient}.
* @param builderCustomizers a list of configuration settings customizers
*/
public MongoClientFactory(List<MongoClientSettingsBuilderCustomizer> builderCustomizers) {
super(builderCustomizers, MongoClients::create);
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.mongodb.autoconfigure;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoClientSettings.Builder;
import com.mongodb.MongoDriverInformation;
/**
* Base class for setup that is common to MongoDB client factories.
*
* @param <T> the mongo client type
* @author Christoph Strobl
* @author Scott Frederick
* @since 4.0.0
*/
public abstract class MongoClientFactorySupport<T> {
private final List<MongoClientSettingsBuilderCustomizer> builderCustomizers;
private final BiFunction<MongoClientSettings, MongoDriverInformation, T> clientCreator;
protected MongoClientFactorySupport(List<MongoClientSettingsBuilderCustomizer> builderCustomizers,
BiFunction<MongoClientSettings, MongoDriverInformation, T> clientCreator) {
this.builderCustomizers = (builderCustomizers != null) ? builderCustomizers : Collections.emptyList();
this.clientCreator = clientCreator;
}
public T createMongoClient(MongoClientSettings settings) {
Builder targetSettings = MongoClientSettings.builder(settings);
customize(targetSettings);
return this.clientCreator.apply(targetSettings.build(), driverInformation());
}
private void customize(Builder builder) {
for (MongoClientSettingsBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
}
private MongoDriverInformation driverInformation() {
return MongoDriverInformation.builder(MongoDriverInformation.builder().build())
.driverName("spring-boot")
.build();
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.mongodb.autoconfigure;
import com.mongodb.MongoClientSettings.Builder;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link com.mongodb.MongoClientSettings} through a {@link Builder
* MongoClientSettings.Builder} whilst retaining default auto-configuration.
*
* @author Mark Paluch
* @since 4.0.0
*/
@FunctionalInterface
public interface MongoClientSettingsBuilderCustomizer {
/**
* Customize the {@link Builder}.
* @param clientSettingsBuilder the builder to customize
*/
void customize(Builder clientSettingsBuilder);
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import com.mongodb.ConnectionString;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.boot.ssl.SslBundle;
/**
* Details required to establish a connection to a MongoDB service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 4.0.0
*/
public interface MongoConnectionDetails extends ConnectionDetails {
/**
* The {@link ConnectionString} for MongoDB.
* @return the connection string
*/
ConnectionString getConnectionString();
/**
* SSL bundle to use.
* @return the SSL bundle to use
* @since 3.5.0
*/
default SslBundle getSslBundle() {
return null;
}
/**
* 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

@@ -0,0 +1,309 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import java.util.List;
import com.mongodb.ConnectionString;
import org.bson.UuidRepresentation;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for Mongo.
*
* @author Dave Syer
* @author Phillip Webb
* @author Josh Long
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Nasko Vasilev
* @author Mark Paluch
* @author Artsiom Yudovin
* @author Safeer Ansari
* @since 4.0.0
*/
@ConfigurationProperties("spring.data.mongodb")
public class MongoProperties {
/**
* Default port used when the configured port is {@code null}.
*/
public static final int DEFAULT_PORT = 27017;
/**
* Default URI used when the configured URI is {@code null}.
*/
public static final String DEFAULT_URI = "mongodb://localhost/test";
/**
* Protocol to be used for the MongoDB connection. Ignored if 'uri' is set.
*/
private String protocol = "mongodb";
/**
* Mongo server host. Ignored if 'uri' is set.
*/
private String host;
/**
* Mongo server port. Ignored if 'uri' is set.
*/
private Integer port = null;
/**
* Additional server hosts. Ignored if 'uri' is set or if 'host' is omitted.
* Additional hosts will use the default mongo port of 27017. If you want to use a
* different port you can use the "host:port" syntax.
*/
private List<String> additionalHosts;
/**
* Mongo database URI. Overrides host, port, username, and password.
*/
private String uri;
/**
* Database name. Overrides database in URI.
*/
private String database;
/**
* Authentication database name.
*/
private String authenticationDatabase;
private final Gridfs gridfs = new Gridfs();
/**
* Login user of the mongo server. Ignored if 'uri' is set.
*/
private String username;
/**
* Login password of the mongo server. Ignored if 'uri' is set.
*/
private char[] password;
/**
* Required replica set name for the cluster. Ignored if 'uri' is set.
*/
private String replicaSetName;
/**
* Fully qualified name of the FieldNamingStrategy to use.
*/
private Class<?> fieldNamingStrategy;
/**
* Representation to use when converting a UUID to a BSON binary value.
*/
private UuidRepresentation uuidRepresentation = UuidRepresentation.JAVA_LEGACY;
private final Ssl ssl = new Ssl();
/**
* Whether to enable auto-index creation.
*/
private Boolean autoIndexCreation;
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getProtocol() {
return this.protocol;
}
public String getHost() {
return this.host;
}
public void setHost(String host) {
this.host = host;
}
public String getDatabase() {
return this.database;
}
public void setDatabase(String database) {
this.database = database;
}
public String getAuthenticationDatabase() {
return this.authenticationDatabase;
}
public void setAuthenticationDatabase(String authenticationDatabase) {
this.authenticationDatabase = authenticationDatabase;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public char[] getPassword() {
return this.password;
}
public void setPassword(char[] password) {
this.password = password;
}
public String getReplicaSetName() {
return this.replicaSetName;
}
public void setReplicaSetName(String replicaSetName) {
this.replicaSetName = replicaSetName;
}
public Class<?> getFieldNamingStrategy() {
return this.fieldNamingStrategy;
}
public void setFieldNamingStrategy(Class<?> fieldNamingStrategy) {
this.fieldNamingStrategy = fieldNamingStrategy;
}
public UuidRepresentation getUuidRepresentation() {
return this.uuidRepresentation;
}
public void setUuidRepresentation(UuidRepresentation uuidRepresentation) {
this.uuidRepresentation = uuidRepresentation;
}
public String getUri() {
return this.uri;
}
public String determineUri() {
return (this.uri != null) ? this.uri : DEFAULT_URI;
}
public void setUri(String uri) {
this.uri = uri;
}
public Integer getPort() {
return this.port;
}
public void setPort(Integer port) {
this.port = port;
}
public Gridfs getGridfs() {
return this.gridfs;
}
public String getMongoClientDatabase() {
if (this.database != null) {
return this.database;
}
return new ConnectionString(determineUri()).getDatabase();
}
public Boolean isAutoIndexCreation() {
return this.autoIndexCreation;
}
public void setAutoIndexCreation(Boolean autoIndexCreation) {
this.autoIndexCreation = autoIndexCreation;
}
public List<String> getAdditionalHosts() {
return this.additionalHosts;
}
public void setAdditionalHosts(List<String> additionalHosts) {
this.additionalHosts = additionalHosts;
}
public Ssl getSsl() {
return this.ssl;
}
public static class Gridfs {
/**
* GridFS database name.
*/
private String database;
/**
* GridFS bucket name.
*/
private String bucket;
public String getDatabase() {
return this.database;
}
public void setDatabase(String database) {
this.database = database;
}
public String getBucket() {
return this.bucket;
}
public void setBucket(String bucket) {
this.bucket = bucket;
}
}
public static class Ssl {
/**
* Whether to enable SSL support. Enabled automatically if "bundle" is provided
* unless specified otherwise.
*/
private Boolean enabled;
/**
* SSL bundle name.
*/
private String bundle;
public boolean isEnabled() {
return (this.enabled != null) ? this.enabled : this.bundle != null;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getBundle() {
return this.bundle;
}
public void setBundle(String bundle) {
this.bundle = bundle;
}
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoClientSettings.Builder;
import com.mongodb.connection.TransportSettings;
import com.mongodb.reactivestreams.client.MongoClient;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import reactor.core.publisher.Flux;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ObjectProvider;
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.context.properties.EnableConfigurationProperties;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Reactive Mongo.
*
* @author Mark Paluch
* @author Stephane Nicoll
* @author Scott Frederick
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass({ MongoClient.class, Flux.class })
@EnableConfigurationProperties(MongoProperties.class)
public class MongoReactiveAutoConfiguration {
@Bean
@ConditionalOnMissingBean(MongoConnectionDetails.class)
PropertiesMongoConnectionDetails mongoConnectionDetails(MongoProperties properties,
ObjectProvider<SslBundles> sslBundles) {
return new PropertiesMongoConnectionDetails(properties, sslBundles.getIfAvailable());
}
@Bean
@ConditionalOnMissingBean
public MongoClient reactiveStreamsMongoClient(
ObjectProvider<MongoClientSettingsBuilderCustomizer> builderCustomizers, MongoClientSettings settings) {
ReactiveMongoClientFactory factory = new ReactiveMongoClientFactory(
builderCustomizers.orderedStream().toList());
return factory.createMongoClient(settings);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(MongoClientSettings.class)
static class MongoClientSettingsConfiguration {
@Bean
MongoClientSettings mongoClientSettings() {
return MongoClientSettings.builder().build();
}
@Bean
StandardMongoClientSettingsBuilderCustomizer standardMongoSettingsCustomizer(MongoProperties properties,
MongoConnectionDetails connectionDetails) {
return new StandardMongoClientSettingsBuilderCustomizer(connectionDetails,
properties.getUuidRepresentation());
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SocketChannel.class, NioEventLoopGroup.class })
static class NettyDriverConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
NettyDriverMongoClientSettingsBuilderCustomizer nettyDriverCustomizer(
ObjectProvider<MongoClientSettings> settings) {
return new NettyDriverMongoClientSettingsBuilderCustomizer(settings);
}
}
/**
* {@link MongoClientSettingsBuilderCustomizer} to apply Mongo client settings.
*/
static final class NettyDriverMongoClientSettingsBuilderCustomizer
implements MongoClientSettingsBuilderCustomizer, DisposableBean {
private final ObjectProvider<MongoClientSettings> settings;
private volatile EventLoopGroup eventLoopGroup;
NettyDriverMongoClientSettingsBuilderCustomizer(ObjectProvider<MongoClientSettings> settings) {
this.settings = settings;
}
@Override
public void customize(Builder builder) {
if (!isCustomTransportConfiguration(this.settings.getIfAvailable())) {
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
this.eventLoopGroup = eventLoopGroup;
builder.transportSettings(TransportSettings.nettyBuilder().eventLoopGroup(eventLoopGroup).build());
}
}
@Override
public void destroy() {
EventLoopGroup eventLoopGroup = this.eventLoopGroup;
if (eventLoopGroup != null) {
eventLoopGroup.shutdownGracefully().awaitUninterruptibly();
this.eventLoopGroup = null;
}
}
private boolean isCustomTransportConfiguration(MongoClientSettings settings) {
return settings != null && settings.getTransportSettings() != null;
}
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import com.mongodb.ConnectionString;
import org.springframework.boot.mongodb.autoconfigure.MongoProperties.Ssl;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Adapts {@link MongoProperties} to {@link MongoConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @author Scott Frederick
* @since 4.0.0
*/
public class PropertiesMongoConnectionDetails implements MongoConnectionDetails {
private final MongoProperties properties;
private final SslBundles sslBundles;
public PropertiesMongoConnectionDetails(MongoProperties properties, SslBundles sslBundles) {
this.properties = properties;
this.sslBundles = sslBundles;
}
@Override
public ConnectionString getConnectionString() {
// protocol://[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(getProtocol()).append("://");
if (this.properties.getUsername() != null) {
builder.append(encode(this.properties.getUsername()));
builder.append(":");
if (this.properties.getPassword() != null) {
builder.append(encode(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(",");
builder.append(String.join(",", this.properties.getAdditionalHosts()));
}
builder.append("/");
builder.append(this.properties.getMongoClientDatabase());
List<String> options = getOptions();
if (!options.isEmpty()) {
builder.append("?");
builder.append(String.join("&", options));
}
return new ConnectionString(builder.toString());
}
private String getProtocol() {
String protocol = this.properties.getProtocol();
if (StringUtils.hasText(protocol)) {
return protocol;
}
return "mongodb";
}
private String encode(String input) {
return URLEncoder.encode(input, StandardCharsets.UTF_8);
}
private char[] encode(char[] input) {
return URLEncoder.encode(new String(input), StandardCharsets.UTF_8).toCharArray();
}
@Override
public GridFs getGridFs() {
return GridFs.of(PropertiesMongoConnectionDetails.this.properties.getGridfs().getDatabase(),
PropertiesMongoConnectionDetails.this.properties.getGridfs().getBucket());
}
@Override
public SslBundle getSslBundle() {
Ssl ssl = this.properties.getSsl();
if (!ssl.isEnabled()) {
return null;
}
if (StringUtils.hasLength(ssl.getBundle())) {
Assert.notNull(this.sslBundles, "SSL bundle name has been set but no SSL bundles found in context");
return this.sslBundles.getBundle(ssl.getBundle());
}
return SslBundle.systemDefault();
}
private List<String> getOptions() {
List<String> options = new ArrayList<>();
if (StringUtils.hasText(this.properties.getReplicaSetName())) {
options.add("replicaSet=" + this.properties.getReplicaSetName());
}
if (this.properties.getUsername() != null && this.properties.getAuthenticationDatabase() != null) {
options.add("authSource=" + this.properties.getAuthenticationDatabase());
}
return options;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import java.util.List;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
/**
* A factory for a reactive {@link MongoClient}.
*
* @author Mark Paluch
* @author Stephane Nicoll
* @author Scott Frederick
* @since 4.0.0
*/
public class ReactiveMongoClientFactory extends MongoClientFactorySupport<MongoClient> {
/**
* Construct a factory for creating a {@link MongoClient}.
* @param builderCustomizers a list of configuration settings customizers
*/
public ReactiveMongoClientFactory(List<MongoClientSettingsBuilderCustomizer> builderCustomizers) {
super(builderCustomizers, MongoClients::create);
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.connection.SslSettings;
import org.bson.UuidRepresentation;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
/**
* A {@link MongoClientSettingsBuilderCustomizer} that applies standard settings to a
* {@link MongoClientSettings}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 4.0.0
*/
public class StandardMongoClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer, Ordered {
private final ConnectionString connectionString;
private final UuidRepresentation uuidRepresentation;
private final MongoConnectionDetails connectionDetails;
private final MongoProperties.Ssl ssl;
private final SslBundles sslBundles;
private int order = 0;
/**
* Create a new instance.
* @param connectionString the connection string
* @param uuidRepresentation the uuid representation
* @param ssl the ssl properties
* @param sslBundles the ssl bundles
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link #StandardMongoClientSettingsBuilderCustomizer(MongoConnectionDetails, UuidRepresentation)}
*/
@Deprecated(forRemoval = true, since = "3.5.0")
public StandardMongoClientSettingsBuilderCustomizer(ConnectionString connectionString,
UuidRepresentation uuidRepresentation, MongoProperties.Ssl ssl, SslBundles sslBundles) {
this.connectionDetails = null;
this.connectionString = connectionString;
this.uuidRepresentation = uuidRepresentation;
this.ssl = ssl;
this.sslBundles = sslBundles;
}
public StandardMongoClientSettingsBuilderCustomizer(MongoConnectionDetails connectionDetails,
UuidRepresentation uuidRepresentation) {
this.connectionString = null;
this.ssl = null;
this.sslBundles = null;
this.connectionDetails = connectionDetails;
this.uuidRepresentation = uuidRepresentation;
}
@Override
public void customize(MongoClientSettings.Builder settingsBuilder) {
settingsBuilder.uuidRepresentation(this.uuidRepresentation);
if (this.connectionDetails != null) {
settingsBuilder.applyConnectionString(this.connectionDetails.getConnectionString());
settingsBuilder.applyToSslSettings(this::configureSslIfNeeded);
}
else {
settingsBuilder.uuidRepresentation(this.uuidRepresentation);
settingsBuilder.applyConnectionString(this.connectionString);
if (this.ssl.isEnabled()) {
settingsBuilder.applyToSslSettings(this::configureSsl);
}
}
}
private void configureSsl(SslSettings.Builder settings) {
settings.enabled(true);
if (this.ssl.getBundle() != null) {
SslBundle sslBundle = this.sslBundles.getBundle(this.ssl.getBundle());
Assert.state(!sslBundle.getOptions().isSpecified(), "SSL options cannot be specified with MongoDB");
settings.context(sslBundle.createSslContext());
}
}
private void configureSslIfNeeded(SslSettings.Builder settings) {
SslBundle sslBundle = this.connectionDetails.getSslBundle();
if (sslBundle != null) {
settings.enabled(true);
Assert.state(!sslBundle.getOptions().isSpecified(), "SSL options cannot be specified with MongoDB");
settings.context(sslBundle.createSslContext());
}
}
@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

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for MongoDB.
*/
package org.springframework.boot.mongodb.autoconfigure;

View File

@@ -0,0 +1,23 @@
{
"groups": [],
"properties": [
{
"name": "spring.data.mongodb.grid-fs-database",
"type": "java.lang.String",
"deprecation": {
"replacement": "spring.data.mongodb.gridfs.database",
"level": "error"
}
},
{
"name": "spring.data.mongodb.repositories.type",
"type": "org.springframework.boot.autoconfigure.data.RepositoryType",
"description": "Type of Mongo repositories to enable.",
"defaultValue": "auto"
},
{
"name": "spring.data.mongodb.uri",
"defaultValue": "mongodb://localhost/test"
}
]
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration
org.springframework.boot.mongodb.autoconfigure.MongoReactiveAutoConfiguration

View File

@@ -0,0 +1,317 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import java.util.concurrent.TimeUnit;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.internal.MongoClientImpl;
import com.mongodb.connection.ClusterConnectionMode;
import com.mongodb.connection.SslSettings;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MongoAutoConfiguration}.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Scott Frederick
*/
class MongoAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, SslAutoConfiguration.class));
@Test
void clientExists() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(MongoClient.class));
}
@Test
void settingsAdded() {
this.contextRunner.withUserConfiguration(SettingsConfig.class)
.run((context) -> assertThat(
getSettings(context).getSocketSettings().getConnectTimeout(TimeUnit.MILLISECONDS))
.isEqualTo(300));
}
@Test
void settingsAddedButNoHost() {
this.contextRunner.withUserConfiguration(SettingsConfig.class)
.run((context) -> assertThat(
getSettings(context).getSocketSettings().getConnectTimeout(TimeUnit.MILLISECONDS))
.isEqualTo(300));
}
@Test
void settingsSslConfig() {
this.contextRunner.withUserConfiguration(SslSettingsConfig.class)
.run((context) -> assertThat(getSettings(context).getSslSettings().isEnabled()).isTrue());
}
@Test
void configuresSslWhenEnabled() {
this.contextRunner.withPropertyValues("spring.data.mongodb.ssl.enabled=true").run((context) -> {
SslSettings sslSettings = getSettings(context).getSslSettings();
assertThat(sslSettings.isEnabled()).isTrue();
assertThat(sslSettings.getContext()).isNotNull();
});
}
@Test
@WithPackageResources("test.jks")
void configuresSslWithBundle() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.ssl.bundle=test-bundle",
"spring.ssl.bundle.jks.test-bundle.keystore.location=classpath:test.jks",
"spring.ssl.bundle.jks.test-bundle.keystore.password=secret",
"spring.ssl.bundle.jks.test-bundle.key.password=password")
.run((context) -> {
SslSettings sslSettings = getSettings(context).getSslSettings();
assertThat(sslSettings.isEnabled()).isTrue();
assertThat(sslSettings.getContext()).isNotNull();
});
}
@Test
void configuresProtocol() {
this.contextRunner.withPropertyValues("spring.data.mongodb.protocol=mongodb+srv").run((context) -> {
MongoClientSettings settings = getSettings(context);
assertThat(settings.getClusterSettings().getMode()).isEqualTo(ClusterConnectionMode.MULTIPLE);
});
}
@Test
void defaultProtocol() {
this.contextRunner.run((context) -> {
MongoClientSettings settings = getSettings(context);
assertThat(settings.getClusterSettings().getMode()).isEqualTo(ClusterConnectionMode.SINGLE);
});
}
@Test
void configuresWithoutSslWhenDisabledWithBundle() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.ssl.enabled=false", "spring.data.mongodb.ssl.bundle=test-bundle")
.run((context) -> {
SslSettings sslSettings = getSettings(context).getSslSettings();
assertThat(sslSettings.isEnabled()).isFalse();
});
}
@Test
void doesNotConfigureCredentialsWithoutUsername() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.password=secret",
"spring.data.mongodb.authentication-database=authdb")
.run((context) -> assertThat(getSettings(context).getCredential()).isNull());
}
@Test
void configuresCredentialsFromPropertiesWithDefaultDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("test");
});
}
@Test
void configuresCredentialsFromPropertiesWithDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret",
"spring.data.mongodb.database=mydb")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("mydb");
});
}
@Test
void configuresCredentialsFromPropertiesWithAuthDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret",
"spring.data.mongodb.database=mydb", "spring.data.mongodb.authentication-database=authdb")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("authdb");
});
}
@Test
void configuresCredentialsFromPropertiesWithSpecialCharacters() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=us:er", "spring.data.mongodb.password=sec@ret")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("us:er");
assertThat(credential.getPassword()).isEqualTo("sec@ret".toCharArray());
assertThat(credential.getSource()).isEqualTo("test");
});
}
@Test
void doesNotConfigureCredentialsWithoutUsernameInUri() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri=mongodb://localhost/mydb?authSource=authdb")
.run((context) -> assertThat(getSettings(context).getCredential()).isNull());
}
@Test
void configuresCredentialsFromUriPropertyWithDefaultDatabase() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("admin");
});
}
@Test
void configuresCredentialsFromUriPropertyWithDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/mydb",
"spring.data.mongodb.database=notused", "spring.data.mongodb.authentication-database=notused")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("mydb");
});
}
@Test
void configuresCredentialsFromUriPropertyWithAuthDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/mydb?authSource=authdb",
"spring.data.mongodb.database=notused", "spring.data.mongodb.authentication-database=notused")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("authdb");
});
}
@Test
void configuresSingleClient() {
this.contextRunner.withUserConfiguration(FallbackMongoClientConfig.class)
.run((context) -> assertThat(context).hasSingleBean(MongoClient.class));
}
@Test
void customizerOverridesAutoConfig() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri:mongodb://localhost/test?appname=auto-config")
.withUserConfiguration(SimpleCustomizerConfig.class)
.run((context) -> assertThat(getSettings(context).getApplicationName()).isEqualTo("overridden-name"));
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesMongoConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(MongoConnectionDetails.class, () -> new MongoConnectionDetails() {
@Override
public ConnectionString getConnectionString() {
return new ConnectionString("mongodb://localhost");
}
})
.run((context) -> assertThat(context).hasSingleBean(MongoConnectionDetails.class)
.doesNotHaveBean(PropertiesMongoConnectionDetails.class));
}
@Test
void uuidRepresentationDefaultsAreAligned() {
this.contextRunner.run((context) -> assertThat(getSettings(context).getUuidRepresentation())
.isEqualTo(new MongoProperties().getUuidRepresentation()));
}
private MongoClientSettings getSettings(AssertableApplicationContext context) {
assertThat(context).hasSingleBean(MongoClient.class);
MongoClientImpl client = (MongoClientImpl) context.getBean(MongoClient.class);
return client.getSettings();
}
@Configuration(proxyBeanMethods = false)
static class SettingsConfig {
@Bean
MongoClientSettings mongoClientSettings() {
return MongoClientSettings.builder()
.applyToSocketSettings((socketSettings) -> socketSettings.connectTimeout(300, TimeUnit.MILLISECONDS))
.build();
}
}
@Configuration(proxyBeanMethods = false)
static class SslSettingsConfig {
@Bean
MongoClientSettings mongoClientSettings() {
return MongoClientSettings.builder().applyToSslSettings((ssl) -> ssl.enabled(true)).build();
}
}
@Configuration(proxyBeanMethods = false)
static class FallbackMongoClientConfig {
@Bean
MongoClient fallbackMongoClient() {
return MongoClients.create();
}
}
@Configuration(proxyBeanMethods = false)
static class SimpleCustomizerConfig {
@Bean
MongoClientSettingsBuilderCustomizer customizer() {
return (clientSettingsBuilder) -> clientSettingsBuilder.applicationName("overridden-name");
}
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.mongodb.MongoClientSettings;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link MongoClientFactorySupport}.
*
* @param <T> the mongo client type
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Mark Paluch
* @author Artsiom Yudovin
* @author Scott Frederick
* @author Moritz Halbritter
*/
abstract class MongoClientFactorySupportTests<T> {
@Test
void canBindCharArrayPassword() {
// gh-1572
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.data.mongodb.password:word").applyTo(context);
context.register(Config.class);
context.refresh();
MongoProperties properties = context.getBean(MongoProperties.class);
assertThat(properties.getPassword()).isEqualTo("word".toCharArray());
}
@Test
void allMongoClientSettingsCanBeSet() {
MongoClientSettings.Builder builder = MongoClientSettings.builder();
builder.applyToSocketSettings((settings) -> {
settings.connectTimeout(1000, TimeUnit.MILLISECONDS);
settings.readTimeout(1000, TimeUnit.MILLISECONDS);
}).applyToServerSettings((settings) -> {
settings.heartbeatFrequency(10001, TimeUnit.MILLISECONDS);
settings.minHeartbeatFrequency(501, TimeUnit.MILLISECONDS);
}).applyToConnectionPoolSettings((settings) -> {
settings.maxWaitTime(120001, TimeUnit.MILLISECONDS);
settings.maxConnectionLifeTime(60000, TimeUnit.MILLISECONDS);
settings.maxConnectionIdleTime(60000, TimeUnit.MILLISECONDS);
}).applyToSslSettings((settings) -> settings.enabled(true)).applicationName("test");
MongoClientSettings settings = builder.build();
T client = createMongoClient(settings);
MongoClientSettings wrapped = getClientSettings(client);
assertThat(wrapped.getSocketSettings().getConnectTimeout(TimeUnit.MILLISECONDS))
.isEqualTo(settings.getSocketSettings().getConnectTimeout(TimeUnit.MILLISECONDS));
assertThat(wrapped.getSocketSettings().getReadTimeout(TimeUnit.MILLISECONDS))
.isEqualTo(settings.getSocketSettings().getReadTimeout(TimeUnit.MILLISECONDS));
assertThat(wrapped.getServerSettings().getHeartbeatFrequency(TimeUnit.MILLISECONDS))
.isEqualTo(settings.getServerSettings().getHeartbeatFrequency(TimeUnit.MILLISECONDS));
assertThat(wrapped.getServerSettings().getMinHeartbeatFrequency(TimeUnit.MILLISECONDS))
.isEqualTo(settings.getServerSettings().getMinHeartbeatFrequency(TimeUnit.MILLISECONDS));
assertThat(wrapped.getApplicationName()).isEqualTo(settings.getApplicationName());
assertThat(wrapped.getConnectionPoolSettings().getMaxWaitTime(TimeUnit.MILLISECONDS))
.isEqualTo(settings.getConnectionPoolSettings().getMaxWaitTime(TimeUnit.MILLISECONDS));
assertThat(wrapped.getConnectionPoolSettings().getMaxConnectionLifeTime(TimeUnit.MILLISECONDS))
.isEqualTo(settings.getConnectionPoolSettings().getMaxConnectionLifeTime(TimeUnit.MILLISECONDS));
assertThat(wrapped.getConnectionPoolSettings().getMaxConnectionIdleTime(TimeUnit.MILLISECONDS))
.isEqualTo(settings.getConnectionPoolSettings().getMaxConnectionIdleTime(TimeUnit.MILLISECONDS));
assertThat(wrapped.getSslSettings().isEnabled()).isEqualTo(settings.getSslSettings().isEnabled());
}
@Test
void customizerIsInvoked() {
MongoClientSettingsBuilderCustomizer customizer = mock(MongoClientSettingsBuilderCustomizer.class);
createMongoClient(customizer);
then(customizer).should().customize(any(MongoClientSettings.Builder.class));
}
@Test
void canBindAutoIndexCreation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.data.mongodb.autoIndexCreation:true").applyTo(context);
context.register(Config.class);
context.refresh();
MongoProperties properties = context.getBean(MongoProperties.class);
assertThat(properties.isAutoIndexCreation()).isTrue();
}
protected T createMongoClient(MongoClientSettings settings) {
return createMongoClient(null, settings);
}
protected void createMongoClient(MongoClientSettingsBuilderCustomizer... customizers) {
createMongoClient((customizers != null) ? Arrays.asList(customizers) : null,
MongoClientSettings.builder().build());
}
protected abstract T createMongoClient(List<MongoClientSettingsBuilderCustomizer> customizers,
MongoClientSettings settings);
protected abstract MongoClientSettings getClientSettings(T client);
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(MongoProperties.class)
static class Config {
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import java.util.List;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.internal.MongoClientImpl;
/**
* Tests for {@link MongoClientFactory}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Mark Paluch
* @author Scott Frederick
*/
class MongoClientFactoryTests extends MongoClientFactorySupportTests<MongoClient> {
@Override
protected MongoClient createMongoClient(List<MongoClientSettingsBuilderCustomizer> customizers,
MongoClientSettings settings) {
return new MongoClientFactory(customizers).createMongoClient(settings);
}
@Override
protected MongoClientSettings getClientSettings(MongoClient client) {
return ((MongoClientImpl) client).getSettings();
}
}

View File

@@ -0,0 +1,312 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.ReadPreference;
import com.mongodb.connection.NettyTransportSettings;
import com.mongodb.connection.SslSettings;
import com.mongodb.connection.TransportSettings;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.internal.MongoClientImpl;
import io.netty.channel.EventLoopGroup;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MongoReactiveAutoConfiguration}.
*
* @author Mark Paluch
* @author Stephane Nicoll
*/
class MongoReactiveAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MongoReactiveAutoConfiguration.class, SslAutoConfiguration.class));
@Test
void clientExists() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(MongoClient.class));
}
@Test
void settingsAdded() {
this.contextRunner.withPropertyValues("spring.data.mongodb.host:localhost")
.withUserConfiguration(SettingsConfig.class)
.run((context) -> assertThat(getSettings(context).getSocketSettings().getReadTimeout(TimeUnit.SECONDS))
.isEqualTo(300));
}
@Test
void settingsAddedButNoHost() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri:mongodb://localhost/test")
.withUserConfiguration(SettingsConfig.class)
.run((context) -> assertThat(getSettings(context).getReadPreference()).isEqualTo(ReadPreference.nearest()));
}
@Test
void settingsSslConfig() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri:mongodb://localhost/test")
.withUserConfiguration(SslSettingsConfig.class)
.run((context) -> {
assertThat(context).hasSingleBean(MongoClient.class);
MongoClientSettings settings = getSettings(context);
assertThat(settings.getApplicationName()).isEqualTo("test-config");
assertThat(settings.getTransportSettings()).isSameAs(context.getBean("myTransportSettings"));
});
}
@Test
void configuresSslWhenEnabled() {
this.contextRunner.withPropertyValues("spring.data.mongodb.ssl.enabled=true").run((context) -> {
SslSettings sslSettings = getSettings(context).getSslSettings();
assertThat(sslSettings.isEnabled()).isTrue();
assertThat(sslSettings.getContext()).isNotNull();
});
}
@Test
@WithPackageResources("test.jks")
void configuresSslWithBundle() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.ssl.bundle=test-bundle",
"spring.ssl.bundle.jks.test-bundle.keystore.location=classpath:test.jks",
"spring.ssl.bundle.jks.test-bundle.keystore.password=secret",
"spring.ssl.bundle.jks.test-bundle.key.password=password")
.run((context) -> {
SslSettings sslSettings = getSettings(context).getSslSettings();
assertThat(sslSettings.isEnabled()).isTrue();
assertThat(sslSettings.getContext()).isNotNull();
});
}
@Test
void configuresWithoutSslWhenDisabledWithBundle() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.ssl.enabled=false", "spring.data.mongodb.ssl.bundle=test-bundle")
.run((context) -> {
SslSettings sslSettings = getSettings(context).getSslSettings();
assertThat(sslSettings.isEnabled()).isFalse();
});
}
@Test
void doesNotConfigureCredentialsWithoutUsername() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.password=secret",
"spring.data.mongodb.authentication-database=authdb")
.run((context) -> assertThat(getSettings(context).getCredential()).isNull());
}
@Test
void configuresCredentialsFromPropertiesWithDefaultDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("test");
});
}
@Test
void configuresCredentialsFromPropertiesWithDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret",
"spring.data.mongodb.database=mydb")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("mydb");
});
}
@Test
void configuresCredentialsFromPropertiesWithAuthDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret",
"spring.data.mongodb.database=mydb", "spring.data.mongodb.authentication-database=authdb")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("authdb");
});
}
@Test
void doesNotConfigureCredentialsWithoutUsernameInUri() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri=mongodb://localhost/mydb?authSource=authdb")
.run((context) -> assertThat(getSettings(context).getCredential()).isNull());
}
@Test
void configuresCredentialsFromUriPropertyWithDefaultDatabase() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("admin");
});
}
@Test
void configuresCredentialsFromUriPropertyWithDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/mydb",
"spring.data.mongodb.database=notused", "spring.data.mongodb.authentication-database=notused")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("mydb");
});
}
@Test
void configuresCredentialsFromUriPropertyWithAuthDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/mydb?authSource=authdb",
"spring.data.mongodb.database=notused", "spring.data.mongodb.authentication-database=notused")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("authdb");
});
}
@Test
void nettyTransportSettingsAreConfiguredAutomatically() {
AtomicReference<EventLoopGroup> eventLoopGroupReference = new AtomicReference<>();
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(MongoClient.class);
TransportSettings transportSettings = getSettings(context).getTransportSettings();
assertThat(transportSettings).isInstanceOf(NettyTransportSettings.class);
EventLoopGroup eventLoopGroup = ((NettyTransportSettings) transportSettings).getEventLoopGroup();
assertThat(eventLoopGroup.isShutdown()).isFalse();
eventLoopGroupReference.set(eventLoopGroup);
});
assertThat(eventLoopGroupReference.get().isShutdown()).isTrue();
}
@Test
@SuppressWarnings("deprecation")
void customizerWithTransportSettingsOverridesAutoConfig() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri:mongodb://localhost/test?appname=auto-config")
.withUserConfiguration(SimpleTransportSettingsCustomizerConfig.class)
.run((context) -> {
assertThat(context).hasSingleBean(MongoClient.class);
MongoClientSettings settings = getSettings(context);
assertThat(settings.getApplicationName()).isEqualTo("custom-transport-settings");
assertThat(settings.getTransportSettings())
.isSameAs(SimpleTransportSettingsCustomizerConfig.transportSettings);
});
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesMongoConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(MongoConnectionDetails.class, () -> new MongoConnectionDetails() {
@Override
public ConnectionString getConnectionString() {
return new ConnectionString("mongodb://localhost");
}
})
.run((context) -> assertThat(context).hasSingleBean(MongoConnectionDetails.class)
.doesNotHaveBean(PropertiesMongoConnectionDetails.class));
}
@Test
void uuidRepresentationDefaultsAreAligned() {
this.contextRunner.run((context) -> assertThat(getSettings(context).getUuidRepresentation())
.isEqualTo(new MongoProperties().getUuidRepresentation()));
}
private MongoClientSettings getSettings(ApplicationContext context) {
MongoClientImpl client = (MongoClientImpl) context.getBean(MongoClient.class);
return client.getSettings();
}
@Configuration(proxyBeanMethods = false)
static class SettingsConfig {
@Bean
MongoClientSettings mongoClientSettings() {
return MongoClientSettings.builder()
.readPreference(ReadPreference.nearest())
.applyToSocketSettings((socket) -> socket.readTimeout(300, TimeUnit.SECONDS))
.build();
}
}
@Configuration(proxyBeanMethods = false)
static class SslSettingsConfig {
@Bean
MongoClientSettings mongoClientSettings(TransportSettings transportSettings) {
return MongoClientSettings.builder()
.applicationName("test-config")
.transportSettings(transportSettings)
.build();
}
@Bean
TransportSettings myTransportSettings() {
return TransportSettings.nettyBuilder().build();
}
}
@Configuration(proxyBeanMethods = false)
static class SimpleTransportSettingsCustomizerConfig {
private static final TransportSettings transportSettings = TransportSettings.nettyBuilder().build();
@Bean
MongoClientSettingsBuilderCustomizer customizer() {
return (clientSettingsBuilder) -> clientSettingsBuilder.applicationName("custom-transport-settings")
.transportSettings(transportSettings);
}
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import java.util.List;
import com.mongodb.ConnectionString;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
import org.springframework.boot.ssl.SslBundle;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link PropertiesMongoConnectionDetails}.
*
* @author Christoph Dreis
* @author Scott Frederick
* @author Moritz Halbritter
*/
class PropertiesMongoConnectionDetailsTests {
private MongoProperties properties;
private DefaultSslBundleRegistry sslBundleRegistry;
private PropertiesMongoConnectionDetails connectionDetails;
@BeforeEach
void setUp() {
this.properties = new MongoProperties();
this.sslBundleRegistry = new DefaultSslBundleRegistry();
this.connectionDetails = new PropertiesMongoConnectionDetails(this.properties, this.sslBundleRegistry);
}
@Test
void credentialsCanBeConfiguredWithUsername() {
this.properties.setUsername("user");
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getUsername()).isEqualTo("user");
assertThat(connectionString.getPassword()).isEmpty();
assertThat(connectionString.getCredential().getUserName()).isEqualTo("user");
assertThat(connectionString.getCredential().getPassword()).isEmpty();
assertThat(connectionString.getCredential().getSource()).isEqualTo("test");
}
@Test
void credentialsCanBeConfiguredWithUsernameAndPassword() {
this.properties.setUsername("user");
this.properties.setPassword("secret".toCharArray());
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getUsername()).isEqualTo("user");
assertThat(connectionString.getPassword()).isEqualTo("secret".toCharArray());
assertThat(connectionString.getCredential().getUserName()).isEqualTo("user");
assertThat(connectionString.getCredential().getPassword()).isEqualTo("secret".toCharArray());
assertThat(connectionString.getCredential().getSource()).isEqualTo("test");
}
@Test
void databaseCanBeConfigured() {
this.properties.setDatabase("db");
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getDatabase()).isEqualTo("db");
}
@Test
void databaseHasDefaultWhenNotConfigured() {
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getDatabase()).isEqualTo("test");
}
@Test
void protocolCanBeConfigured() {
this.properties.setProtocol("mongodb+srv");
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getConnectionString()).startsWith("mongodb+srv://");
}
@Test
void authenticationDatabaseCanBeConfigured() {
this.properties.setUsername("user");
this.properties.setDatabase("db");
this.properties.setAuthenticationDatabase("authdb");
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getDatabase()).isEqualTo("db");
assertThat(connectionString.getCredential().getSource()).isEqualTo("authdb");
assertThat(connectionString.getCredential().getUserName()).isEqualTo("user");
}
@Test
void authenticationDatabaseIsNotConfiguredWhenUsernameIsNotConfigured() {
this.properties.setAuthenticationDatabase("authdb");
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getCredential()).isNull();
}
@Test
void replicaSetCanBeConfigured() {
this.properties.setReplicaSetName("test");
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getRequiredReplicaSetName()).isEqualTo("test");
}
@Test
void replicaSetCanBeConfiguredWithDatabase() {
this.properties.setUsername("user");
this.properties.setDatabase("db");
this.properties.setReplicaSetName("test");
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getDatabase()).isEqualTo("db");
assertThat(connectionString.getRequiredReplicaSetName()).isEqualTo("test");
}
@Test
void replicaSetCanBeNull() {
this.properties.setReplicaSetName(null);
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getRequiredReplicaSetName()).isNull();
}
@Test
void replicaSetCanBeBlank() {
this.properties.setReplicaSetName("");
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getRequiredReplicaSetName()).isNull();
}
@Test
void whenAdditionalHostsAreConfiguredThenTheyAreIncludedInHostsOfConnectionString() {
this.properties.setHost("mongo1.example.com");
this.properties.setAdditionalHosts(List.of("mongo2.example.com", "mongo3.example.com"));
ConnectionString connectionString = this.connectionDetails.getConnectionString();
assertThat(connectionString.getHosts()).containsExactly("mongo1.example.com", "mongo2.example.com",
"mongo3.example.com");
}
@Test
void shouldReturnSslBundle() {
SslBundle bundle1 = mock(SslBundle.class);
this.sslBundleRegistry.registerBundle("bundle-1", bundle1);
this.properties.getSsl().setBundle("bundle-1");
SslBundle sslBundle = this.connectionDetails.getSslBundle();
assertThat(sslBundle).isSameAs(bundle1);
}
@Test
void shouldReturnSystemDefaultBundleIfSslIsEnabledButBundleNotSet() {
this.properties.getSsl().setEnabled(true);
SslBundle sslBundle = this.connectionDetails.getSslBundle();
assertThat(sslBundle).isNotNull();
}
@Test
void shouldReturnNullIfSslIsNotEnabled() {
this.properties.getSsl().setEnabled(false);
SslBundle sslBundle = this.connectionDetails.getSslBundle();
assertThat(sslBundle).isNull();
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.mongodb.autoconfigure;
import java.util.List;
import com.mongodb.MongoClientSettings;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.internal.MongoClientImpl;
/**
* Tests for {@link ReactiveMongoClientFactory}.
*
* @author Mark Paluch
* @author Stephane Nicoll
* @author Scott Frederick
*/
class ReactiveMongoClientFactoryTests extends MongoClientFactorySupportTests<MongoClient> {
@Override
protected MongoClient createMongoClient(List<MongoClientSettingsBuilderCustomizer> customizers,
MongoClientSettings settings) {
return new ReactiveMongoClientFactory(customizers).createMongoClient(settings);
}
@Override
protected MongoClientSettings getClientSettings(MongoClient client) {
return ((MongoClientImpl) client).getSettings();
}
}