From d549e6001a42b528b7458467ce19469649ea98af Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 22 Aug 2018 13:55:28 +0200 Subject: [PATCH] Add support for com.mongodb.client.MongoClient Next to com.mongodb.MongoClient the MongoDB Java driver offers the com.mongodb.client.MongoClient as entry point for database and collection operations. Spring Data MongoDB supports c.m.client.MongoClient via its MongoDbFactory using SimpleMongoClientDbFactory. The MongoAutoConfiguration now backs off if any of those two clients is already defined in the Application context allowing MongoDataAutoConfiguration to pick up the users driver implementation of choice. See gh-14176 --- .../mongo/MongoDataAutoConfiguration.java | 112 +++++++++++++++++- .../mongo/MongoAutoConfiguration.java | 3 +- .../MongoDataAutoConfigurationTests.java | 23 ++++ .../mongo/MongoAutoConfigurationTests.java | 23 ++++ 4 files changed, 155 insertions(+), 6 deletions(-) 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 9ff49965b95..58cda155f06 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 659f709aa47..f93a7edb36f 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 74ba12f9f9b..a9e6f0b5cf6 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 884ccd8331a..c2eb05a29b4 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(); + } + + } + }