diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfiguration.java index 9ff49965b9..58cda155f0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfiguration.java @@ -16,27 +16,35 @@ package org.springframework.boot.autoconfigure.data.mongo; +import java.util.Arrays; +import java.util.List; + import com.mongodb.ClientSessionOptions; import com.mongodb.DB; import com.mongodb.MongoClient; import com.mongodb.client.ClientSession; import com.mongodb.client.MongoDatabase; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; 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.data.mongo.MongoDataAutoConfiguration.AnySyncMongoClientAvailable; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoProperties; 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.context.annotation.Import; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory; import org.springframework.data.mongodb.core.SimpleMongoDbFactory; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; @@ -63,11 +71,12 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author EddĂș MelĂ©ndez * @author Stephane Nicoll + * @author Christoph Strobl * @since 1.1.0 */ @Configuration @ConditionalOnClass({ MongoClient.class, MongoTemplate.class }) -@ConditionalOnBean(MongoClient.class) +@Conditional(AnySyncMongoClientAvailable.class) @EnableConfigurationProperties(MongoProperties.class) @Import(MongoDataConfiguration.class) @AutoConfigureAfter(MongoAutoConfiguration.class) @@ -75,15 +84,22 @@ public class MongoDataAutoConfiguration { private final MongoProperties properties; - public MongoDataAutoConfiguration(MongoProperties properties) { + private final MongoDbFactoryFactory dbFactoryFactory; + + public MongoDataAutoConfiguration(ObjectProvider mongoClientProvider, + ObjectProvider mongoClientClientProvider, + MongoProperties properties) { + this.properties = properties; + this.dbFactoryFactory = new MongoDbFactoryFactory(mongoClientProvider, + mongoClientClientProvider); } @Bean + @Conditional(AnySyncMongoClientAvailable.class) @ConditionalOnMissingBean(MongoDbFactory.class) - public SimpleMongoDbFactory mongoDbFactory(MongoClient mongo) { - String database = this.properties.getMongoClientDatabase(); - return new SimpleMongoDbFactory(mongo, database); + public MongoDbFactory mongoDbFactory() { + return this.dbFactoryFactory.getFor(this.properties.getMongoClientDatabase()); } @Bean @@ -166,4 +182,90 @@ public class MongoDataAutoConfiguration { } + /** + * Check if either {@link com.mongodb.MongoClient} or + * {@link com.mongodb.client.MongoClient} is already defined in the + * {@link org.springframework.context.ApplicationContext}. + * + * @author Christoph Strobl + * @since 2.1 + */ + static class AnySyncMongoClientAvailable extends AnyNestedCondition { + + AnySyncMongoClientAvailable() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnBean(com.mongodb.MongoClient.class) + static class MongoClientPreferred { + + } + + @ConditionalOnBean(com.mongodb.client.MongoClient.class) + static class MongoClientClientPreferred { + + } + + } + + /** + * Encapsulation of {@link MongoDbFactory} creation depending on available beans + * {@link com.mongodb.MongoClient} or {@link com.mongodb.client.MongoClient} expressed + * via the given {@link ObjectProvider ObjectProviders}. Prefers the first available + * MongoDB client creating a suitable instance of {@link MongoDbFactory} for it. + * + * @author Christoph Strobl + * @since 2.1 + */ + static class MongoDbFactoryFactory { + + private final List> clientProviders; + + /** + * Create new instance of {@link MongoDbFactoryFactory}. + * @param clientProviders order matters here, as we choose the first available + * one. + */ + MongoDbFactoryFactory(ObjectProvider... clientProviders) { + this.clientProviders = Arrays.asList(clientProviders); + } + + /** + * Get the {@link MongoDbFactory} suitable for the first available MongoDB client. + * @param database the name of the default database to return on + * {@link MongoDbFactory#getDb()}. + * @return new instance of {@link MongoDbFactory} suitable for the first available + * MongoDB client. + */ + MongoDbFactory getFor(String database) { + + Object client = findAvailableClientProvider(); + + if (client instanceof MongoClient) { + return new SimpleMongoDbFactory(MongoClient.class.cast(client), database); + } + + if (client instanceof com.mongodb.client.MongoClient) { + return new SimpleMongoClientDbFactory( + com.mongodb.client.MongoClient.class.cast(client), database); + } + + return null; + } + + private Object findAvailableClientProvider() { + + for (ObjectProvider provider : this.clientProviders) { + Object client = provider.getIfAvailable(); + if (client != null) { + return client; + } + } + + throw new IllegalStateException( + "Expected to find at least one MongoDB client."); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java index 659f709aa4..f93a7edb36 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java @@ -65,7 +65,8 @@ public class MongoAutoConfiguration { } @Bean - @ConditionalOnMissingBean + @ConditionalOnMissingBean(type = { "com.mongodb.MongoClient", + "com.mongodb.client.MongoClient" }) public MongoClient mongo() { this.mongo = this.factory.createMongoClient(this.options); return this.mongo; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java index 74ba12f9f9..a9e6f0b5cf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Set; import com.mongodb.MongoClient; +import com.mongodb.client.MongoClients; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; @@ -39,7 +40,9 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy; import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; +import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory; import org.springframework.data.mongodb.core.convert.MongoCustomConversions; import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; @@ -173,6 +176,16 @@ public class MongoDataAutoConfigurationTests { .doesNotHaveBean(MongoDataAutoConfiguration.class)); } + @Test + public void createsMongoDbFactoryForMongoClientClientWhenBeanPresent() { + + this.contextRunner.withUserConfiguration(WithMongoClientClientConfiguration.class) + .run((context) -> { + MongoDbFactory dbFactory = context.getBean(MongoDbFactory.class); + assertThat(dbFactory).isInstanceOf(SimpleMongoClientDbFactory.class); + }); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) private static void assertDomainTypesDiscovered(MongoMappingContext mappingContext, Class... types) { @@ -197,6 +210,16 @@ public class MongoDataAutoConfigurationTests { } + @Configuration + static class WithMongoClientClientConfiguration { + + @Bean + com.mongodb.client.MongoClient mongoClient() { + return MongoClients.create(); + } + + } + private static class MyConverter implements Converter { @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java index 884ccd8331..c2eb05a29b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java @@ -20,14 +20,17 @@ import javax.net.SocketFactory; import com.mongodb.MongoClient; import com.mongodb.MongoClientOptions; +import com.mongodb.client.MongoClients; import org.junit.Test; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; 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; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.mock; /** @@ -78,6 +81,17 @@ public class MongoAutoConfigurationTests { }); } + @Test + public void doesNotCreateMongoClientWhenAlreadyDefined() { + + this.contextRunner + .withPropertyValues("spring.data.mongodb.uri:mongodb://localhost/test") + .withUserConfiguration(ConfigurationWithClientMongoClient.class) + .run((context) -> assertThatExceptionOfType( + NoSuchBeanDefinitionException.class) + .isThrownBy(() -> context.getBean(MongoClient.class))); + } + @Configuration static class OptionsConfig { @@ -104,4 +118,13 @@ public class MongoAutoConfigurationTests { } + static class ConfigurationWithClientMongoClient { + + @Bean + com.mongodb.client.MongoClient mongoClient() { + return MongoClients.create(); + } + + } + }