From 48b0f1577bdbcd68285ea052c759492137330c8c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 8 Feb 2017 09:42:41 +0100 Subject: [PATCH 1/2] Provide a starter for reactive Spring Data MongoDB Add autoconfiguration to bootstrap MongoDB Reactive Streams driver components, reactive Spring Data MongoDB and reactive repositories. Add bean dependency processor for flapdoodle so embedded MongoDB instances are configured before bootstraping the reactive MongoDB client. Add Spring Data MongoDB Reactive starter with blocking and non-blocking dependencies. MongoDB requires a separate driver that is used in the `ReactiveMongoTemplate` while `MappingMongoConverter` (shared amongst blocking/reactive Template API) requires the blocking driver to resolve DBRefs. See gh-8230 --- spring-boot-autoconfigure/pom.xml | 10 ++ .../ReactiveMongoDataAutoConfiguration.java | 76 ++++++++ ...iveMongoRepositoriesAutoConfiguration.java | 59 ++++++ ...ngoRepositoriesAutoConfigureRegistrar.java | 57 ++++++ ...ientDependsOnBeanFactoryPostProcessor.java | 45 +++++ .../mongo/MongoAutoConfiguration.java | 8 +- .../mongo/MongoClientFactory.java | 121 +++++++++++++ .../autoconfigure/mongo/MongoProperties.java | 89 +--------- .../mongo/ReactiveMongoAutoConfiguration.java | 76 ++++++++ .../mongo/ReactiveMongoClientFactory.java | 163 +++++++++++++++++ .../EmbeddedMongoAutoConfiguration.java | 18 ++ .../mongo/ReactiveCityMongoDbRepository.java | 24 +++ ...ngoRepositoriesAutoConfigurationTests.java | 102 +++++++++++ ...activeMongoDataAutoConfigurationTests.java | 61 +++++++ ...ngoRepositoriesAutoConfigurationTests.java | 134 ++++++++++++++ .../mongo/city/ReactiveCityRepository.java | 27 +++ .../mongo/MongoClientFactoryTests.java | 168 ++++++++++++++++++ .../mongo/MongoPropertiesTests.java | 123 +------------ .../ReactiveMongoAutoConfigurationTests.java | 136 ++++++++++++++ .../ReactiveMongoClientFactoryTests.java | 163 +++++++++++++++++ spring-boot-dependencies/pom.xml | 5 + spring-boot-starters/pom.xml | 1 + .../pom.xml | 53 ++++++ .../main/resources/META-INF/spring.provides | 1 + 24 files changed, 1511 insertions(+), 209 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigureRegistrar.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactory.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactory.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/mongo/ReactiveCityMongoDbRepository.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveAndBlockingMongoRepositoriesAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/city/ReactiveCityRepository.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactoryTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactoryTests.java create mode 100644 spring-boot-starters/spring-boot-starter-data-mongodb-reactive/pom.xml create mode 100644 spring-boot-starters/spring-boot-starter-data-mongodb-reactive/src/main/resources/META-INF/spring.provides diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 45ef4defb21..f618ce593f9 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -100,6 +100,16 @@ de.flapdoodle.embed.mongo true + + org.mongodb + mongodb-driver-async + true + + + org.mongodb + mongodb-driver-reactivestreams + true + javax.cache cache-api diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfiguration.java new file mode 100644 index 00000000000..f5371feda26 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfiguration.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.mongo; + +import com.mongodb.reactivestreams.client.MongoClient; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.mongo.MongoProperties; +import org.springframework.boot.autoconfigure.mongo.ReactiveMongoAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory; +import org.springframework.data.mongodb.core.convert.MongoConverter; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's reactive mongo + * support. + *

+ * Registers a {@link ReactiveMongoTemplate} bean if no other bean of the same type is + * configured. + *

+ * Honors the {@literal spring.data.mongodb.database} property if set, otherwise connects + * to the {@literal test} database. + * + * @author Mark Paluch + * @since 2.0.0 + */ +@Configuration +@ConditionalOnClass({ MongoClient.class, ReactiveMongoTemplate.class }) +@EnableConfigurationProperties(MongoProperties.class) +@AutoConfigureAfter(ReactiveMongoAutoConfiguration.class) +public class ReactiveMongoDataAutoConfiguration { + + private final MongoProperties properties; + + public ReactiveMongoDataAutoConfiguration(MongoProperties properties) { + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean(ReactiveMongoDatabaseFactory.class) + public SimpleReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory( + MongoClient mongo) throws Exception { + String database = this.properties.getMongoClientDatabase(); + return new SimpleReactiveMongoDatabaseFactory(mongo, database); + } + + @Bean + @ConditionalOnMissingBean + public ReactiveMongoTemplate reactiveMongoTemplate( + ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory, + MongoConverter converter) { + return new ReactiveMongoTemplate(reactiveMongoDatabaseFactory, converter); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfiguration.java new file mode 100644 index 00000000000..5b97327c85a --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.mongo; + +import com.mongodb.reactivestreams.client.MongoClient; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories; +import org.springframework.data.mongodb.repository.config.ReactiveMongoRepositoryConfigurationExtension; +import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Mongo Reactive + * Repositories. + *

+ * Activates when there is no bean of type + * {@link org.springframework.data.mongodb.repository.support.ReactiveMongoRepositoryFactoryBean} + * configured in the context, the Spring Data Mongo {@link ReactiveMongoRepository} type + * is on the classpath, the ReactiveStreams Mongo client driver API is on the classpath, + * and there is no other configured {@link ReactiveMongoRepository}. + *

+ * Once in effect, the auto-configuration is the equivalent of enabling Mongo repositories + * using the {@link EnableReactiveMongoRepositories} annotation. + * + * @author Mark Paluch + * @since 2.0.0 + * @see EnableReactiveMongoRepositories + */ +@Configuration +@ConditionalOnClass({ MongoClient.class, ReactiveMongoRepository.class }) +@ConditionalOnMissingBean({ MongoRepositoryFactoryBean.class, + ReactiveMongoRepositoryConfigurationExtension.class }) +@ConditionalOnProperty(prefix = "spring.data.mongodb.reactive-repositories", name = "enabled", havingValue = "true", matchIfMissing = true) +@Import(ReactiveMongoRepositoriesAutoConfigureRegistrar.class) +@AutoConfigureAfter(ReactiveMongoDataAutoConfiguration.class) +public class ReactiveMongoRepositoriesAutoConfiguration { + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigureRegistrar.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigureRegistrar.java new file mode 100644 index 00000000000..08a53b6338b --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigureRegistrar.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.mongo; + +import java.lang.annotation.Annotation; + +import org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories; +import org.springframework.data.mongodb.repository.config.ReactiveMongoRepositoryConfigurationExtension; +import org.springframework.data.repository.config.RepositoryConfigurationExtension; + +/** + * {@link ImportBeanDefinitionRegistrar} used to auto-configure Spring Data Mongo Reactive + * Repositories. + * + * @author Mark Paluch + * @since 2.0.0 + */ +class ReactiveMongoRepositoriesAutoConfigureRegistrar extends + AbstractRepositoryConfigurationSourceSupport { + + @Override + protected Class getAnnotation() { + return EnableReactiveMongoRepositories.class; + } + + @Override + protected Class getConfiguration() { + return EnableReactiveMongoRepositoriesConfiguration.class; + } + + @Override + protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() { + return new ReactiveMongoRepositoryConfigurationExtension(); + } + + @EnableReactiveMongoRepositories + private static class EnableReactiveMongoRepositoriesConfiguration { + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor.java new file mode 100644 index 00000000000..318e194a0c5 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.mongo; + +import com.mongodb.reactivestreams.client.MongoClient; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.data.mongodb.core.ReactiveMongoClientFactoryBean; + +/** + * {@link BeanFactoryPostProcessor} to automatically set up the recommended + * {@link BeanDefinition#setDependsOn(String[]) dependsOn} configuration for Mongo clients + * when used embedded Mongo. + * + * @author Mark Paluch + * @since 2.0.0 + */ +@Order(Ordered.LOWEST_PRECEDENCE) +public class ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor extends + AbstractDependsOnBeanFactoryPostProcessor { + + public ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor( + String... dependsOn) { + super(MongoClient.class, ReactiveMongoClientFactoryBean.class, dependsOn); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java index 83fb675354a..ec9d17e731d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -38,6 +38,7 @@ import org.springframework.core.env.Environment; * @author Dave Syer * @author Oliver Gierke * @author Phillip Webb + * @author Mark Paluch */ @Configuration @ConditionalOnClass(MongoClient.class) @@ -51,6 +52,8 @@ public class MongoAutoConfiguration { private final Environment environment; + private final MongoClientFactory factory; + private MongoClient mongo; public MongoAutoConfiguration(MongoProperties properties, @@ -58,6 +61,7 @@ public class MongoAutoConfiguration { this.properties = properties; this.options = options.getIfAvailable(); this.environment = environment; + this.factory = new MongoClientFactory(properties); } @PreDestroy @@ -70,7 +74,7 @@ public class MongoAutoConfiguration { @Bean @ConditionalOnMissingBean public MongoClient mongo() throws UnknownHostException { - this.mongo = this.properties.createMongoClient(this.options, this.environment); + this.mongo = this.factory.createMongoClient(this.options, this.environment); return this.mongo; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactory.java new file mode 100644 index 00000000000..941d2832ffe --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactory.java @@ -0,0 +1,121 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.mongo; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; +import com.mongodb.MongoClientOptions.Builder; +import com.mongodb.MongoClientURI; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; + +import org.springframework.core.env.Environment; + +/** + * A factory for a blocking {@link MongoClient} that applies {@link MongoProperties}. + * + * @author Mark Paluch + * @since 2.0.0 + */ +public class MongoClientFactory { + + private final MongoProperties properties; + + public MongoClientFactory(MongoProperties properties) { + this.properties = properties; + } + + /** + * Creates a {@link MongoClient} using the given {@code options} and + * {@code environment}. If the configured port is zero, the value of the + * {@code local.mongo.port} property retrieved from the {@code environment} is used to + * configure the client. + * @param options the options + * @param environment the environment + * @return the Mongo client + * @throws UnknownHostException if the configured host is unknown + */ + public MongoClient createMongoClient(MongoClientOptions options, + Environment environment) throws UnknownHostException { + if (hasCustomAddress() || hasCustomCredentials()) { + if (this.properties.getUri() != null) { + throw new IllegalStateException("Invalid mongo configuration, " + + "either uri or host/port/credentials must be specified"); + } + if (options == null) { + options = MongoClientOptions.builder().build(); + } + List credentials = new ArrayList(); + if (hasCustomCredentials()) { + String database = this.properties.getAuthenticationDatabase() == null ? this.properties + .getMongoClientDatabase() : this.properties + .getAuthenticationDatabase(); + credentials.add(MongoCredential.createCredential( + this.properties.getUsername(), database, + this.properties.getPassword())); + } + String host = this.properties.getHost() == null ? "localhost" + : this.properties.getHost(); + int port = determinePort(environment); + return new MongoClient( + Collections.singletonList(new ServerAddress(host, port)), + credentials, options); + } + // The options and credentials are in the URI + return new MongoClient(new MongoClientURI(this.properties.determineUri(), + builder(options))); + } + + private boolean hasCustomAddress() { + return this.properties.getHost() != null || this.properties.getPort() != null; + } + + private boolean hasCustomCredentials() { + return this.properties.getUsername() != null + && this.properties.getPassword() != null; + } + + private int determinePort(Environment environment) { + if (this.properties.getPort() == null) { + return MongoProperties.DEFAULT_PORT; + } + if (this.properties.getPort() == 0) { + if (environment != null) { + String localPort = environment.getProperty("local.mongo.port"); + if (localPort != null) { + return Integer.valueOf(localPort); + } + } + throw new IllegalStateException( + "spring.data.mongodb.port=0 and no local mongo port configuration " + + "is available"); + } + return this.properties.getPort(); + } + + private Builder builder(MongoClientOptions options) { + if (options != null) { + return MongoClientOptions.builder(options); + } + return MongoClientOptions.builder(); + } +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java index fa69691ae6d..4e33decd050 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,9 @@ package org.springframework.boot.autoconfigure.mongo; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import com.mongodb.MongoClient; -import com.mongodb.MongoClientOptions; -import com.mongodb.MongoClientOptions.Builder; import com.mongodb.MongoClientURI; -import com.mongodb.MongoCredential; -import com.mongodb.ServerAddress; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.env.Environment; /** * Configuration properties for Mongo. @@ -41,6 +30,7 @@ import org.springframework.core.env.Environment; * @author Eddú Meléndez * @author Stephane Nicoll * @author Nasko Vasilev + * @author Mark Paluch */ @ConfigurationProperties(prefix = "spring.data.mongodb") public class MongoProperties { @@ -189,79 +179,4 @@ public class MongoProperties { return new MongoClientURI(determineUri()).getDatabase(); } - /** - * Creates a {@link MongoClient} using the given {@code options} and - * {@code environment}. If the configured port is zero, the value of the - * {@code local.mongo.port} property retrieved from the {@code environment} is used to - * configure the client. - * @param options the options - * @param environment the environment - * @return the Mongo client - * @throws UnknownHostException if the configured host is unknown - */ - public MongoClient createMongoClient(MongoClientOptions options, - Environment environment) throws UnknownHostException { - try { - if (hasCustomAddress() || hasCustomCredentials()) { - if (this.uri != null) { - throw new IllegalStateException("Invalid mongo configuration, " - + "either uri or host/port/credentials must be specified"); - } - if (options == null) { - options = MongoClientOptions.builder().build(); - } - List credentials = new ArrayList(); - if (hasCustomCredentials()) { - String database = this.authenticationDatabase == null - ? getMongoClientDatabase() : this.authenticationDatabase; - credentials.add(MongoCredential.createCredential(this.username, - database, this.password)); - } - String host = this.host == null ? "localhost" : this.host; - int port = determinePort(environment); - return new MongoClient( - Collections.singletonList(new ServerAddress(host, port)), - credentials, options); - } - // The options and credentials are in the URI - return new MongoClient(new MongoClientURI(determineUri(), builder(options))); - } - finally { - clearPassword(); - } - } - - private boolean hasCustomAddress() { - return this.host != null || this.port != null; - } - - private boolean hasCustomCredentials() { - return this.username != null && this.password != null; - } - - private int determinePort(Environment environment) { - if (this.port == null) { - return DEFAULT_PORT; - } - if (this.port == 0) { - if (environment != null) { - String localPort = environment.getProperty("local.mongo.port"); - if (localPort != null) { - return Integer.valueOf(localPort); - } - } - throw new IllegalStateException( - "spring.data.mongodb.port=0 and no local mongo port configuration " - + "is available"); - } - return this.port; - } - - private Builder builder(MongoClientOptions options) { - if (options != null) { - return MongoClientOptions.builder(options); - } - return MongoClientOptions.builder(); - } - } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfiguration.java new file mode 100644 index 00000000000..318facb8083 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfiguration.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.mongo; + +import javax.annotation.PreDestroy; + +import com.mongodb.async.client.MongoClientSettings; +import com.mongodb.reactivestreams.client.MongoClient; + +import org.springframework.beans.factory.ObjectProvider; +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.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Reactive Mongo. + * + * @author Mark Paluch + * @since 2.0.0 + */ +@Configuration +@ConditionalOnClass(MongoClient.class) +@EnableConfigurationProperties(MongoProperties.class) +public class ReactiveMongoAutoConfiguration { + + private final MongoProperties properties; + + private final MongoClientSettings settings; + + private final Environment environment; + + private final ReactiveMongoClientFactory factory; + + private MongoClient mongo; + + public ReactiveMongoAutoConfiguration(MongoProperties properties, + ObjectProvider settings, Environment environment) { + this.properties = properties; + this.settings = settings.getIfAvailable(); + this.environment = environment; + this.factory = new ReactiveMongoClientFactory(properties); + } + + @PreDestroy + public void close() { + if (this.mongo != null) { + this.mongo.close(); + } + } + + @Bean + @ConditionalOnMissingBean + public MongoClient reactiveStreamsMongoClient() { + this.mongo = this.factory.createMongoClient(this.settings, this.environment); + return this.mongo; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactory.java new file mode 100644 index 00000000000..e32d6da4df3 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactory.java @@ -0,0 +1,163 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.mongo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.async.client.MongoClientSettings; +import com.mongodb.async.client.MongoClientSettings.Builder; +import com.mongodb.connection.ClusterSettings; +import com.mongodb.connection.ConnectionPoolSettings; +import com.mongodb.connection.ServerSettings; +import com.mongodb.connection.SocketSettings; +import com.mongodb.connection.SslSettings; +import com.mongodb.reactivestreams.client.MongoClient; +import com.mongodb.reactivestreams.client.MongoClients; + +import org.springframework.core.env.Environment; + +/** + * A factory for a reactive {@link MongoClient} that applies {@link MongoProperties}. + * + * @author Mark Paluch + * @since 2.0.0 + */ +public class ReactiveMongoClientFactory { + + private final MongoProperties properties; + + public ReactiveMongoClientFactory(MongoProperties properties) { + this.properties = properties; + } + + /** + * Creates a {@link MongoClient} using the given {@code options} and + * {@code environment}. If the configured port is zero, the value of the + * {@code local.mongo.port} property retrieved from the {@code environment} is used to + * configure the client. + * @param settings the settings + * @param environment the environment + * @return the Mongo client + */ + public MongoClient createMongoClient(MongoClientSettings settings, + Environment environment) { + if (hasCustomAddress() || hasCustomCredentials()) { + if (this.properties.getUri() != null) { + throw new IllegalStateException("Invalid mongo configuration, " + + "either uri or host/port/credentials must be specified"); + } + + Builder builder = builder(settings); + if (hasCustomCredentials()) { + List credentials = new ArrayList(); + String database = this.properties.getAuthenticationDatabase() == null ? this.properties + .getMongoClientDatabase() : this.properties + .getAuthenticationDatabase(); + credentials.add(MongoCredential.createCredential( + this.properties.getUsername(), database, + this.properties.getPassword())); + builder.credentialList(credentials); + } + String host = this.properties.getHost() == null ? "localhost" + : this.properties.getHost(); + int port = determinePort(environment); + ClusterSettings clusterSettings = ClusterSettings.builder() + .hosts(Collections.singletonList(new ServerAddress(host, port))) + .build(); + builder.clusterSettings(clusterSettings); + return MongoClients.create(builder.build()); + } + ConnectionString connectionString = new ConnectionString( + this.properties.determineUri()); + return MongoClients.create(createBuilder(settings, connectionString).build()); + } + + private Builder createBuilder(MongoClientSettings settings, + ConnectionString connectionString) { + Builder builder = builder(settings) + .clusterSettings( + ClusterSettings.builder().applyConnectionString(connectionString) + .build()) + .connectionPoolSettings( + ConnectionPoolSettings.builder() + .applyConnectionString(connectionString).build()) + .serverSettings( + ServerSettings.builder().applyConnectionString(connectionString) + .build()) + .credentialList(connectionString.getCredentialList()) + .sslSettings( + SslSettings.builder().applyConnectionString(connectionString) + .build()) + .socketSettings( + SocketSettings.builder().applyConnectionString(connectionString) + .build()); + if (connectionString.getReadPreference() != null) { + builder.readPreference(connectionString.getReadPreference()); + } + if (connectionString.getReadConcern() != null) { + builder.readConcern(connectionString.getReadConcern()); + } + if (connectionString.getWriteConcern() != null) { + builder.writeConcern(connectionString.getWriteConcern()); + } + if (connectionString.getApplicationName() != null) { + builder.applicationName(connectionString.getApplicationName()); + } + return builder; + } + + private boolean hasCustomAddress() { + return this.properties.getHost() != null || this.properties.getPort() != null; + } + + private boolean hasCustomCredentials() { + return this.properties.getUsername() != null + && this.properties.getPassword() != null; + } + + private int determinePort(Environment environment) { + if (this.properties.getPort() == null) { + return MongoProperties.DEFAULT_PORT; + } + if (this.properties.getPort() == 0) { + if (environment != null) { + String localPort = environment.getProperty("local.mongo.port"); + if (localPort != null) { + return Integer.valueOf(localPort); + } + } + throw new IllegalStateException( + "spring.data.mongodb.port=0 and no local mongo port configuration " + + "is available"); + } + return this.properties.getPort(); + } + + private Builder builder(MongoClientSettings settings) { + if (settings == null) { + return MongoClientSettings.builder(); + } + + return MongoClientSettings.builder(settings); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java index d63391cdded..07166c006c9 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java @@ -53,6 +53,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.data.mongo.MongoClientDependsOnBeanFactoryPostProcessor; +import org.springframework.boot.autoconfigure.data.mongo.ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -64,6 +65,7 @@ import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.data.mongodb.core.MongoClientFactoryBean; +import org.springframework.data.mongodb.core.ReactiveMongoClientFactoryBean; import org.springframework.util.Assert; /** @@ -72,6 +74,7 @@ import org.springframework.util.Assert; * @author Henryk Konsek * @author Andy Wilkinson * @author Yogesh Lonkar + * @author Mark Paluch * @since 1.3.0 */ @Configuration @@ -226,6 +229,21 @@ public class EmbeddedMongoAutoConfiguration { } + /** + * Additional configuration to ensure that {@link MongoClient} beans depend on the + * {@code embeddedMongoServer} bean. + */ + @Configuration + @ConditionalOnClass({ com.mongodb.reactivestreams.client.MongoClient.class, ReactiveMongoClientFactoryBean.class }) + protected static class EmbeddedReactiveMongoDependencyConfiguration extends + ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor { + + public EmbeddedReactiveMongoDependencyConfiguration() { + super("embeddedMongoServer"); + } + + } + /** * A workaround for the lack of a {@code toString} implementation on * {@code GenericFeatureAwareVersion}. diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/mongo/ReactiveCityMongoDbRepository.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/mongo/ReactiveCityMongoDbRepository.java new file mode 100644 index 00000000000..39ffa8b40da --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/mongo/ReactiveCityMongoDbRepository.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.alt.mongo; + +import org.springframework.boot.autoconfigure.data.mongo.city.City; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; + +public interface ReactiveCityMongoDbRepository extends ReactiveCrudRepository { + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveAndBlockingMongoRepositoriesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveAndBlockingMongoRepositoriesAutoConfigurationTests.java new file mode 100644 index 00000000000..a32235908ba --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveAndBlockingMongoRepositoriesAutoConfigurationTests.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.mongo; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; +import org.springframework.boot.autoconfigure.data.mongo.city.CityRepository; +import org.springframework.boot.autoconfigure.data.mongo.city.ReactiveCityRepository; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfigurationTests; +import org.springframework.boot.autoconfigure.mongo.ReactiveMongoAutoConfiguration; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; +import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MongoRepositoriesAutoConfiguration} and + * {@link ReactiveMongoRepositoriesAutoConfiguration}. + * + * @author Mark Paluch + */ +public class ReactiveAndBlockingMongoRepositoriesAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + this.context.close(); + } + + @Test + public void shouldCreateInstancesForReactiveAndBlockingRepositories() + throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:false"); + this.context.register(BlockingAndReactiveConfiguration.class, + BaseConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + assertThat(this.context.getBean(ReactiveCityRepository.class)).isNotNull(); + } + + @Configuration + @TestAutoConfigurationPackage(MongoAutoConfigurationTests.class) + @EnableMongoRepositories(basePackageClasses = ReactiveCityRepository.class) + @EnableReactiveMongoRepositories(basePackageClasses = ReactiveCityRepository.class) + protected static class BlockingAndReactiveConfiguration { + + } + + @Configuration + @Import(Registrar.class) + protected static class BaseConfiguration { + + } + + protected static class Registrar implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + List names = new ArrayList(); + for (Class type : new Class[] { MongoAutoConfiguration.class, + ReactiveMongoAutoConfiguration.class, + MongoDataAutoConfiguration.class, + MongoRepositoriesAutoConfiguration.class, + ReactiveMongoDataAutoConfiguration.class, + ReactiveMongoRepositoriesAutoConfiguration.class }) { + names.add(type.getName()); + } + return names.toArray(new String[0]); + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfigurationTests.java new file mode 100644 index 00000000000..cd20c86f6eb --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfigurationTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.mongo; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.ReactiveMongoAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ReactiveMongoDataAutoConfiguration}. + * + * @author Mark Paluch + */ +public class ReactiveMongoDataAutoConfigurationTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void templateExists() { + this.context = new AnnotationConfigApplicationContext( + PropertyPlaceholderAutoConfiguration.class, MongoAutoConfiguration.class, + MongoDataAutoConfiguration.class, ReactiveMongoAutoConfiguration.class, + ReactiveMongoDataAutoConfiguration.class); + assertThat(this.context.getBeanNamesForType(ReactiveMongoTemplate.class).length) + .isEqualTo(1); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigurationTests.java new file mode 100644 index 00000000000..0633ad1769d --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigurationTests.java @@ -0,0 +1,134 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.mongo; + +import java.util.Set; + +import com.mongodb.reactivestreams.client.MongoClient; +import org.junit.After; +import org.junit.Test; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.data.alt.mongo.CityMongoDbRepository; +import org.springframework.boot.autoconfigure.data.alt.mongo.ReactiveCityMongoDbRepository; +import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage; +import org.springframework.boot.autoconfigure.data.mongo.city.City; +import org.springframework.boot.autoconfigure.data.mongo.city.ReactiveCityRepository; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.ReactiveMongoAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; +import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ReactiveMongoRepositoriesAutoConfiguration}. + * + * @author Mark Paluch + */ +public class ReactiveMongoRepositoriesAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + this.context.close(); + } + + @Test + public void testDefaultRepositoryConfiguration() throws Exception { + prepareApplicationContext(TestConfiguration.class); + + assertThat(this.context.getBean(ReactiveCityRepository.class)).isNotNull(); + MongoClient client = this.context.getBean(MongoClient.class); + assertThat(client).isInstanceOf(MongoClient.class); + MongoMappingContext mappingContext = this.context + .getBean(MongoMappingContext.class); + @SuppressWarnings("unchecked") + Set> entities = (Set>) ReflectionTestUtils + .getField(mappingContext, "initialEntitySet"); + assertThat(entities).hasSize(1); + } + + @Test + public void testNoRepositoryConfiguration() throws Exception { + prepareApplicationContext(EmptyConfiguration.class); + + MongoClient client = this.context.getBean(MongoClient.class); + assertThat(client).isInstanceOf(MongoClient.class); + } + + @Test + public void doesNotTriggerDefaultRepositoryDetectionIfCustomized() { + prepareApplicationContext(CustomizedConfiguration.class); + + assertThat(this.context.getBeansOfType(ReactiveCityMongoDbRepository.class)).isEmpty(); + } + + @Test(expected = NoSuchBeanDefinitionException.class) + public void autoConfigurationShouldNotKickInEvenIfManualConfigDidNotCreateAnyRepositories() { + prepareApplicationContext(SortOfInvalidCustomConfiguration.class); + + this.context.getBean(ReactiveCityRepository.class); + } + + private void prepareApplicationContext(Class... configurationClasses) { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(configurationClasses); + this.context.register(MongoAutoConfiguration.class, + MongoDataAutoConfiguration.class, + ReactiveMongoAutoConfiguration.class, + ReactiveMongoDataAutoConfiguration.class, + ReactiveMongoRepositoriesAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + } + + @Configuration + @TestAutoConfigurationPackage(City.class) + protected static class TestConfiguration { + + } + + @Configuration + @TestAutoConfigurationPackage(EmptyDataPackage.class) + protected static class EmptyConfiguration { + + } + + @Configuration + @TestAutoConfigurationPackage(ReactiveMongoRepositoriesAutoConfigurationTests.class) + @EnableMongoRepositories(basePackageClasses = CityMongoDbRepository.class) + protected static class CustomizedConfiguration { + + } + + @Configuration + // To not find any repositories + @EnableReactiveMongoRepositories("foo.bar") + @TestAutoConfigurationPackage(ReactiveMongoRepositoriesAutoConfigurationTests.class) + protected static class SortOfInvalidCustomConfiguration { + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/city/ReactiveCityRepository.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/city/ReactiveCityRepository.java new file mode 100644 index 00000000000..4c4e3f0cc22 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/city/ReactiveCityRepository.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.mongo.city; + +import reactor.core.publisher.Flux; + +import org.springframework.data.repository.Repository; + +public interface ReactiveCityRepository extends Repository { + + Flux findAll(); + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactoryTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactoryTests.java new file mode 100644 index 00000000000..ce455d05c7e --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactoryTests.java @@ -0,0 +1,168 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.mongo; + +import java.net.UnknownHostException; +import java.util.List; + +import com.mongodb.MongoClient; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.connection.Cluster; +import com.mongodb.connection.ClusterSettings; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MongoClientFactory} via {@link MongoProperties}. + * + * @author Phillip Webb + * @author Andy Wilkinson + * @author Stephane Nicoll + * @author Mark Paluch + */ +public class MongoClientFactoryTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void portCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setPort(12345); + MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + List allAddresses = extractServerAddresses(client); + assertThat(allAddresses).hasSize(1); + assertServerAddress(allAddresses.get(0), "localhost", 12345); + } + + @Test + public void hostCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setHost("mongo.example.com"); + MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + List allAddresses = extractServerAddresses(client); + assertThat(allAddresses).hasSize(1); + assertServerAddress(allAddresses.get(0), "mongo.example.com", 27017); + } + + @Test + public void credentialsCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setUsername("user"); + properties.setPassword("secret".toCharArray()); + MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", + "test"); + } + + @Test + public void databaseCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setDatabase("foo"); + properties.setUsername("user"); + properties.setPassword("secret".toCharArray()); + MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", + "foo"); + } + + @Test + public void authenticationDatabaseCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setAuthenticationDatabase("foo"); + properties.setUsername("user"); + properties.setPassword("secret".toCharArray()); + MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", + "foo"); + } + + @Test + public void uriCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setUri("mongodb://user:secret@mongo1.example.com:12345," + + "mongo2.example.com:23456/test"); + MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + List allAddresses = extractServerAddresses(client); + assertThat(allAddresses).hasSize(2); + assertServerAddress(allAddresses.get(0), "mongo1.example.com", 12345); + assertServerAddress(allAddresses.get(1), "mongo2.example.com", 23456); + List credentialsList = client.getCredentialsList(); + assertThat(credentialsList).hasSize(1); + assertMongoCredential(credentialsList.get(0), "user", "secret", "test"); + } + + @Test + public void uriCannotBeSetWithCredentials() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setUri("mongodb://127.0.0.1:1234/mydb"); + properties.setUsername("user"); + properties.setPassword("secret".toCharArray()); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Invalid mongo configuration, " + + "either uri or host/port/credentials must be specified"); + new MongoClientFactory(properties).createMongoClient(null, null); + } + + @Test + public void uriCannotBeSetWithHostPort() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setUri("mongodb://127.0.0.1:1234/mydb"); + properties.setHost("localhost"); + properties.setPort(4567); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Invalid mongo configuration, " + + "either uri or host/port/credentials must be specified"); + new MongoClientFactory(properties).createMongoClient(null, null); + } + + private List extractServerAddresses(MongoClient client) { + Cluster cluster = (Cluster) ReflectionTestUtils.getField(client, "cluster"); + ClusterSettings clusterSettings = (ClusterSettings) ReflectionTestUtils + .getField(cluster, "settings"); + List allAddresses = clusterSettings.getHosts(); + return allAddresses; + } + + private void assertServerAddress(ServerAddress serverAddress, String expectedHost, + int expectedPort) { + assertThat(serverAddress.getHost()).isEqualTo(expectedHost); + assertThat(serverAddress.getPort()).isEqualTo(expectedPort); + } + + private void assertMongoCredential(MongoCredential credentials, + String expectedUsername, String expectedPassword, String expectedSource) { + assertThat(credentials.getUserName()).isEqualTo(expectedUsername); + assertThat(credentials.getPassword()).isEqualTo(expectedPassword.toCharArray()); + assertThat(credentials.getSource()).isEqualTo(expectedSource); + } + + @Configuration + @EnableConfigurationProperties(MongoProperties.class) + static class Config { + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java index cd79c5e3b0f..b479c42014d 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -17,14 +17,9 @@ package org.springframework.boot.autoconfigure.mongo; import java.net.UnknownHostException; -import java.util.List; import com.mongodb.MongoClient; import com.mongodb.MongoClientOptions; -import com.mongodb.MongoCredential; -import com.mongodb.ServerAddress; -import com.mongodb.connection.Cluster; -import com.mongodb.connection.ClusterSettings; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -33,7 +28,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; -import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -43,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll + * @author Mark Paluch */ public class MongoPropertiesTests { @@ -60,97 +55,6 @@ public class MongoPropertiesTests { assertThat(properties.getPassword()).isEqualTo("word".toCharArray()); } - @Test - public void portCanBeCustomized() throws UnknownHostException { - MongoProperties properties = new MongoProperties(); - properties.setPort(12345); - MongoClient client = properties.createMongoClient(null, null); - List allAddresses = extractServerAddresses(client); - assertThat(allAddresses).hasSize(1); - assertServerAddress(allAddresses.get(0), "localhost", 12345); - } - - @Test - public void hostCanBeCustomized() throws UnknownHostException { - MongoProperties properties = new MongoProperties(); - properties.setHost("mongo.example.com"); - MongoClient client = properties.createMongoClient(null, null); - List allAddresses = extractServerAddresses(client); - assertThat(allAddresses).hasSize(1); - assertServerAddress(allAddresses.get(0), "mongo.example.com", 27017); - } - - @Test - public void credentialsCanBeCustomized() throws UnknownHostException { - MongoProperties properties = new MongoProperties(); - properties.setUsername("user"); - properties.setPassword("secret".toCharArray()); - MongoClient client = properties.createMongoClient(null, null); - assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", - "test"); - } - - @Test - public void databaseCanBeCustomized() throws UnknownHostException { - MongoProperties properties = new MongoProperties(); - properties.setDatabase("foo"); - properties.setUsername("user"); - properties.setPassword("secret".toCharArray()); - MongoClient client = properties.createMongoClient(null, null); - assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", - "foo"); - } - - @Test - public void authenticationDatabaseCanBeCustomized() throws UnknownHostException { - MongoProperties properties = new MongoProperties(); - properties.setAuthenticationDatabase("foo"); - properties.setUsername("user"); - properties.setPassword("secret".toCharArray()); - MongoClient client = properties.createMongoClient(null, null); - assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", - "foo"); - } - - @Test - public void uriCanBeCustomized() throws UnknownHostException { - MongoProperties properties = new MongoProperties(); - properties.setUri("mongodb://user:secret@mongo1.example.com:12345," - + "mongo2.example.com:23456/test"); - MongoClient client = properties.createMongoClient(null, null); - List allAddresses = extractServerAddresses(client); - assertThat(allAddresses).hasSize(2); - assertServerAddress(allAddresses.get(0), "mongo1.example.com", 12345); - assertServerAddress(allAddresses.get(1), "mongo2.example.com", 23456); - List credentialsList = client.getCredentialsList(); - assertThat(credentialsList).hasSize(1); - assertMongoCredential(credentialsList.get(0), "user", "secret", "test"); - } - - @Test - public void uriCannotBeSetWithCredentials() throws UnknownHostException { - MongoProperties properties = new MongoProperties(); - properties.setUri("mongodb://127.0.0.1:1234/mydb"); - properties.setUsername("user"); - properties.setPassword("secret".toCharArray()); - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage("Invalid mongo configuration, " - + "either uri or host/port/credentials must be specified"); - properties.createMongoClient(null, null); - } - - @Test - public void uriCannotBeSetWithHostPort() throws UnknownHostException { - MongoProperties properties = new MongoProperties(); - properties.setUri("mongodb://127.0.0.1:1234/mydb"); - properties.setHost("localhost"); - properties.setPort(4567); - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage("Invalid mongo configuration, " - + "either uri or host/port/credentials must be specified"); - properties.createMongoClient(null, null); - } - @Test public void allMongoClientOptionsCanBeSet() throws UnknownHostException { MongoClientOptions.Builder builder = MongoClientOptions.builder(); @@ -174,7 +78,7 @@ public class MongoPropertiesTests { builder.requiredReplicaSetName("testReplicaSetName"); MongoClientOptions options = builder.build(); MongoProperties properties = new MongoProperties(); - MongoClient client = properties.createMongoClient(options, null); + MongoClient client = new MongoClientFactory(properties).createMongoClient(options, null); MongoClientOptions wrapped = client.getMongoClientOptions(); assertThat(wrapped.isAlwaysUseMBeans()).isEqualTo(options.isAlwaysUseMBeans()); assertThat(wrapped.getConnectionsPerHost()) @@ -207,27 +111,6 @@ public class MongoPropertiesTests { .isEqualTo(options.getRequiredReplicaSetName()); } - private List extractServerAddresses(MongoClient client) { - Cluster cluster = (Cluster) ReflectionTestUtils.getField(client, "cluster"); - ClusterSettings clusterSettings = (ClusterSettings) ReflectionTestUtils - .getField(cluster, "settings"); - List allAddresses = clusterSettings.getHosts(); - return allAddresses; - } - - private void assertServerAddress(ServerAddress serverAddress, String expectedHost, - int expectedPort) { - assertThat(serverAddress.getHost()).isEqualTo(expectedHost); - assertThat(serverAddress.getPort()).isEqualTo(expectedPort); - } - - private void assertMongoCredential(MongoCredential credentials, - String expectedUsername, String expectedPassword, String expectedSource) { - assertThat(credentials.getUserName()).isEqualTo(expectedUsername); - assertThat(credentials.getPassword()).isEqualTo(expectedPassword.toCharArray()); - assertThat(credentials.getSource()).isEqualTo(expectedSource); - } - @Configuration @EnableConfigurationProperties(MongoProperties.class) static class Config { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfigurationTests.java new file mode 100644 index 00000000000..36bb7e96a16 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfigurationTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.mongo; + +import java.util.concurrent.TimeUnit; + +import com.mongodb.ReadPreference; +import com.mongodb.async.client.MongoClientSettings; +import com.mongodb.connection.SocketSettings; +import com.mongodb.connection.StreamFactory; +import com.mongodb.connection.StreamFactoryFactory; +import com.mongodb.reactivestreams.client.MongoClient; +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +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.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ReactiveMongoAutoConfiguration}. + * + * @author Mark Paluch + */ +public class ReactiveMongoAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void clientExists() { + this.context = new AnnotationConfigApplicationContext( + PropertyPlaceholderAutoConfiguration.class, ReactiveMongoAutoConfiguration.class); + assertThat(this.context.getBeanNamesForType(MongoClient.class).length).isEqualTo(1); + } + + @Test + public void optionsAdded() { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.data.mongodb.host:localhost"); + this.context.register(OptionsConfig.class, + PropertyPlaceholderAutoConfiguration.class, ReactiveMongoAutoConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBean(MongoClient.class).getSettings().getSocketSettings() + .getReadTimeout(TimeUnit.SECONDS)).isEqualTo(300); + } + + @Test + public void optionsAddedButNoHost() { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.data.mongodb.uri:mongodb://localhost/test"); + this.context.register(OptionsConfig.class, + PropertyPlaceholderAutoConfiguration.class, ReactiveMongoAutoConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBean(MongoClient.class).getSettings().getReadPreference()) + .isEqualTo(ReadPreference.nearest()); + } + + @Test + public void optionsSslConfig() { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.data.mongodb.uri:mongodb://localhost/test"); + this.context.register(SslOptionsConfig.class, + PropertyPlaceholderAutoConfiguration.class, ReactiveMongoAutoConfiguration.class); + this.context.refresh(); + MongoClient mongo = this.context.getBean(MongoClient.class); + MongoClientSettings settings = mongo.getSettings(); + assertThat(settings.getApplicationName()).isEqualTo("test-config"); + assertThat(settings.getStreamFactoryFactory()) + .isSameAs(this.context.getBean("myStreamFactoryFactory")); + } + + @Configuration + static class OptionsConfig { + + @Bean + public MongoClientSettings mongoClientSettings() { + return MongoClientSettings + .builder() + .readPreference(ReadPreference.nearest()) + .socketSettings( + SocketSettings.builder().readTimeout(300, TimeUnit.SECONDS) + .build()).build(); + } + + } + + @Configuration + static class SslOptionsConfig { + + @Bean + public MongoClientSettings mongoClientSettings() { + return MongoClientSettings.builder().applicationName("test-config") + .streamFactoryFactory(myStreamFactoryFactory()).build(); + } + + @Bean + public StreamFactoryFactory myStreamFactoryFactory() { + StreamFactoryFactory streamFactoryFactory = mock(StreamFactoryFactory.class); + given(streamFactoryFactory.create(any(), any())).willReturn(mock(StreamFactory.class)); + return streamFactoryFactory; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactoryTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactoryTests.java new file mode 100644 index 00000000000..79d84bc9f91 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactoryTests.java @@ -0,0 +1,163 @@ +/* + * Copyright 2012-2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.mongo; + +import java.net.UnknownHostException; +import java.util.List; + +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.async.client.MongoClientSettings; +import com.mongodb.connection.ClusterSettings; +import com.mongodb.reactivestreams.client.MongoClient; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ReactiveMongoClientFactory} via {@link MongoProperties}. + * + * @author Mark Paluch + */ +public class ReactiveMongoClientFactoryTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void portCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setPort(12345); + MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, + null); + List allAddresses = extractServerAddresses(client); + assertThat(allAddresses).hasSize(1); + assertServerAddress(allAddresses.get(0), "localhost", 12345); + } + + @Test + public void hostCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setHost("mongo.example.com"); + MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, + null); + List allAddresses = extractServerAddresses(client); + assertThat(allAddresses).hasSize(1); + assertServerAddress(allAddresses.get(0), "mongo.example.com", 27017); + } + + @Test + public void credentialsCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setUsername("user"); + properties.setPassword("secret".toCharArray()); + MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, + null); + assertMongoCredential(extractMongoCredentials(client).get(0), "user", "secret", + "test"); + } + + @Test + public void databaseCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setDatabase("foo"); + properties.setUsername("user"); + properties.setPassword("secret".toCharArray()); + MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, + null); + assertMongoCredential(extractMongoCredentials(client).get(0), "user", "secret", "foo"); + } + + @Test + public void authenticationDatabaseCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setAuthenticationDatabase("foo"); + properties.setUsername("user"); + properties.setPassword("secret".toCharArray()); + MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, + null); + assertMongoCredential(extractMongoCredentials(client).get(0), "user", "secret", "foo"); + } + + @Test + public void uriCanBeCustomized() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setUri("mongodb://user:secret@mongo1.example.com:12345," + + "mongo2.example.com:23456/test"); + MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, + null); + List allAddresses = extractServerAddresses(client); + assertThat(allAddresses).hasSize(2); + assertServerAddress(allAddresses.get(0), "mongo1.example.com", 12345); + assertServerAddress(allAddresses.get(1), "mongo2.example.com", 23456); + List credentialsList = extractMongoCredentials(client); + assertThat(credentialsList).hasSize(1); + assertMongoCredential(credentialsList.get(0), "user", "secret", "test"); + } + + @Test + public void uriCannotBeSetWithCredentials() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setUri("mongodb://127.0.0.1:1234/mydb"); + properties.setUsername("user"); + properties.setPassword("secret".toCharArray()); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Invalid mongo configuration, " + + "either uri or host/port/credentials must be specified"); + new MongoClientFactory(properties).createMongoClient(null, null); + } + + @Test + public void uriCannotBeSetWithHostPort() throws UnknownHostException { + MongoProperties properties = new MongoProperties(); + properties.setUri("mongodb://127.0.0.1:1234/mydb"); + properties.setHost("localhost"); + properties.setPort(4567); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Invalid mongo configuration, " + + "either uri or host/port/credentials must be specified"); + new MongoClientFactory(properties).createMongoClient(null, null); + } + + private List extractServerAddresses(MongoClient client) { + MongoClientSettings settings = client.getSettings(); + ClusterSettings clusterSettings = settings.getClusterSettings(); + List allAddresses = clusterSettings.getHosts(); + return allAddresses; + } + + private List extractMongoCredentials(MongoClient client) { + MongoClientSettings settings = client.getSettings(); + return settings.getCredentialList(); + } + + private void assertServerAddress(ServerAddress serverAddress, String expectedHost, + int expectedPort) { + assertThat(serverAddress.getHost()).isEqualTo(expectedHost); + assertThat(serverAddress.getPort()).isEqualTo(expectedPort); + } + + private void assertMongoCredential(MongoCredential credentials, + String expectedUsername, String expectedPassword, String expectedSource) { + assertThat(credentials.getUserName()).isEqualTo(expectedUsername); + assertThat(credentials.getPassword()).isEqualTo(expectedPassword.toCharArray()); + assertThat(credentials.getSource()).isEqualTo(expectedSource); + } + +} diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index cb061fd1301..fba3daff269 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -376,6 +376,11 @@ spring-boot-starter-data-mongodb 2.0.0.BUILD-SNAPSHOT + + org.springframework.boot + spring-boot-starter-data-mongodb-reactive + 2.0.0.BUILD-SNAPSHOT + org.springframework.boot spring-boot-starter-data-redis diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index cac52775435..b49990d3b46 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -34,6 +34,7 @@ spring-boot-starter-data-jpa spring-boot-starter-data-ldap spring-boot-starter-data-mongodb + spring-boot-starter-data-mongodb-reactive spring-boot-starter-data-neo4j spring-boot-starter-data-redis spring-boot-starter-data-rest diff --git a/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/pom.xml b/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/pom.xml new file mode 100644 index 00000000000..d764fef5606 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starters + 2.0.0.BUILD-SNAPSHOT + + spring-boot-starter-data-mongodb-reactive + Spring Boot Data MongoDB Reactive Starter + Starter for using MongoDB document-oriented database and Spring Data + MongoDB Reactive + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter + + + org.mongodb + mongodb-driver + + + org.mongodb + mongodb-driver-async + + + org.mongodb + mongodb-driver-reactivestreams + + + io.projectreactor + reactor-core + + + org.springframework.data + spring-data-mongodb + + + org.mongodb + mongo-java-driver + + + + + diff --git a/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/src/main/resources/META-INF/spring.provides new file mode 100644 index 00000000000..b19ad47d075 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: spring-data-mongodb-reactive \ No newline at end of file From b30d4303d584c040e2b6120f546272fbcc83fa4b Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 9 Feb 2017 14:52:58 +0100 Subject: [PATCH 2/2] Polish contribution Closes gh-8230 --- spring-boot-autoconfigure/pom.xml | 20 +++++------ .../mongo/MongoAutoConfiguration.java | 11 ++---- .../mongo/MongoClientFactory.java | 31 +++++++++++------ .../autoconfigure/mongo/MongoProperties.java | 9 ----- .../mongo/ReactiveMongoAutoConfiguration.java | 11 ++---- .../mongo/ReactiveMongoClientFactory.java | 24 +++++++------ .../EmbeddedMongoAutoConfiguration.java | 3 +- ...itional-spring-configuration-metadata.json | 6 ++++ .../main/resources/META-INF/spring.factories | 3 ++ .../mongo/ReactiveCityMongoDbRepository.java | 3 +- ...activeMongoDataAutoConfigurationTests.java | 9 ++--- ...ngoRepositoriesAutoConfigurationTests.java | 4 ++- .../mongo/MongoClientFactoryTests.java | 23 ++++++++----- .../mongo/MongoPropertiesTests.java | 3 +- .../ReactiveMongoAutoConfigurationTests.java | 20 ++++++----- .../ReactiveMongoClientFactoryTests.java | 34 +++++++++---------- .../appendix-application-properties.adoc | 1 + .../main/asciidoc/spring-boot-features.adoc | 3 +- .../pom.xml | 20 +++++------ .../main/resources/META-INF/spring.provides | 2 +- .../main/resources/META-INF/spring.provides | 2 +- 21 files changed, 127 insertions(+), 115 deletions(-) diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index f618ce593f9..ff941faba63 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -100,16 +100,6 @@ de.flapdoodle.embed.mongo true - - org.mongodb - mongodb-driver-async - true - - - org.mongodb - mongodb-driver-reactivestreams - true - javax.cache cache-api @@ -309,6 +299,16 @@ jboss-transaction-spi true + + org.mongodb + mongodb-driver-async + true + + + org.mongodb + mongodb-driver-reactivestreams + true + org.springframework spring-jdbc diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java index ec9d17e731d..2774a3d3c5d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java @@ -39,6 +39,7 @@ import org.springframework.core.env.Environment; * @author Oliver Gierke * @author Phillip Webb * @author Mark Paluch + * @author Stephane Nicoll */ @Configuration @ConditionalOnClass(MongoClient.class) @@ -46,22 +47,16 @@ import org.springframework.core.env.Environment; @ConditionalOnMissingBean(type = "org.springframework.data.mongodb.MongoDbFactory") public class MongoAutoConfiguration { - private final MongoProperties properties; - private final MongoClientOptions options; - private final Environment environment; - private final MongoClientFactory factory; private MongoClient mongo; public MongoAutoConfiguration(MongoProperties properties, ObjectProvider options, Environment environment) { - this.properties = properties; this.options = options.getIfAvailable(); - this.environment = environment; - this.factory = new MongoClientFactory(properties); + this.factory = new MongoClientFactory(properties, environment); } @PreDestroy @@ -74,7 +69,7 @@ public class MongoAutoConfiguration { @Bean @ConditionalOnMissingBean public MongoClient mongo() throws UnknownHostException { - this.mongo = this.factory.createMongoClient(this.options, this.environment); + this.mongo = this.factory.createMongoClient(this.options); return this.mongo; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactory.java index 941d2832ffe..4acd20772b0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactory.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactory.java @@ -33,29 +33,37 @@ import org.springframework.core.env.Environment; /** * A factory for a blocking {@link MongoClient} that applies {@link MongoProperties}. * + * @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 * @since 2.0.0 */ public class MongoClientFactory { private final MongoProperties properties; + private final Environment environment; - public MongoClientFactory(MongoProperties properties) { + public MongoClientFactory(MongoProperties properties, Environment environment) { this.properties = properties; + this.environment = environment; } /** - * Creates a {@link MongoClient} using the given {@code options} and - * {@code environment}. If the configured port is zero, the value of the - * {@code local.mongo.port} property retrieved from the {@code environment} is used to + * Creates a {@link MongoClient} using the given {@code options}. If the configured + * port is zero, the value of the {@code local.mongo.port} property is used to * configure the client. * @param options the options - * @param environment the environment * @return the Mongo client * @throws UnknownHostException if the configured host is unknown */ - public MongoClient createMongoClient(MongoClientOptions options, - Environment environment) throws UnknownHostException { + public MongoClient createMongoClient(MongoClientOptions options) + throws UnknownHostException { + if (hasCustomAddress() || hasCustomCredentials()) { if (this.properties.getUri() != null) { throw new IllegalStateException("Invalid mongo configuration, " @@ -75,7 +83,7 @@ public class MongoClientFactory { } String host = this.properties.getHost() == null ? "localhost" : this.properties.getHost(); - int port = determinePort(environment); + int port = determinePort(); return new MongoClient( Collections.singletonList(new ServerAddress(host, port)), credentials, options); @@ -94,13 +102,13 @@ public class MongoClientFactory { && this.properties.getPassword() != null; } - private int determinePort(Environment environment) { + private int determinePort() { if (this.properties.getPort() == null) { return MongoProperties.DEFAULT_PORT; } if (this.properties.getPort() == 0) { - if (environment != null) { - String localPort = environment.getProperty("local.mongo.port"); + if (this.environment != null) { + String localPort = this.environment.getProperty("local.mongo.port"); if (localPort != null) { return Integer.valueOf(localPort); } @@ -118,4 +126,5 @@ public class MongoClientFactory { } return MongoClientOptions.builder(); } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java index 4e33decd050..b47cabe29c4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java @@ -135,15 +135,6 @@ public class MongoProperties { this.fieldNamingStrategy = fieldNamingStrategy; } - public void clearPassword() { - if (this.password == null) { - return; - } - for (int i = 0; i < this.password.length; i++) { - this.password[i] = 0; - } - } - public String getUri() { return this.uri; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfiguration.java index 318facb8083..cfcd64a82a1 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.core.env.Environment; * {@link EnableAutoConfiguration Auto-configuration} for Reactive Mongo. * * @author Mark Paluch + * @author Stephane Nicoll * @since 2.0.0 */ @Configuration @@ -41,22 +42,16 @@ import org.springframework.core.env.Environment; @EnableConfigurationProperties(MongoProperties.class) public class ReactiveMongoAutoConfiguration { - private final MongoProperties properties; - private final MongoClientSettings settings; - private final Environment environment; - private final ReactiveMongoClientFactory factory; private MongoClient mongo; public ReactiveMongoAutoConfiguration(MongoProperties properties, ObjectProvider settings, Environment environment) { - this.properties = properties; this.settings = settings.getIfAvailable(); - this.environment = environment; - this.factory = new ReactiveMongoClientFactory(properties); + this.factory = new ReactiveMongoClientFactory(properties, environment); } @PreDestroy @@ -69,7 +64,7 @@ public class ReactiveMongoAutoConfiguration { @Bean @ConditionalOnMissingBean public MongoClient reactiveStreamsMongoClient() { - this.mongo = this.factory.createMongoClient(this.settings, this.environment); + this.mongo = this.factory.createMongoClient(this.settings); return this.mongo; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactory.java index e32d6da4df3..1a38ce47e40 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactory.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactory.java @@ -39,27 +39,29 @@ import org.springframework.core.env.Environment; * A factory for a reactive {@link MongoClient} that applies {@link MongoProperties}. * * @author Mark Paluch + * @author Stephane Nicoll * @since 2.0.0 */ public class ReactiveMongoClientFactory { private final MongoProperties properties; - public ReactiveMongoClientFactory(MongoProperties properties) { + private final Environment environment; + + public ReactiveMongoClientFactory(MongoProperties properties, + Environment environment) { this.properties = properties; + this.environment = environment; } /** - * Creates a {@link MongoClient} using the given {@code options} and - * {@code environment}. If the configured port is zero, the value of the - * {@code local.mongo.port} property retrieved from the {@code environment} is used to + * Creates a {@link MongoClient} using the given {@code options}. If the configured + * port is zero, the value of the {@code local.mongo.port} property is used to * configure the client. * @param settings the settings - * @param environment the environment * @return the Mongo client */ - public MongoClient createMongoClient(MongoClientSettings settings, - Environment environment) { + public MongoClient createMongoClient(MongoClientSettings settings) { if (hasCustomAddress() || hasCustomCredentials()) { if (this.properties.getUri() != null) { throw new IllegalStateException("Invalid mongo configuration, " @@ -79,7 +81,7 @@ public class ReactiveMongoClientFactory { } String host = this.properties.getHost() == null ? "localhost" : this.properties.getHost(); - int port = determinePort(environment); + int port = determinePort(); ClusterSettings clusterSettings = ClusterSettings.builder() .hosts(Collections.singletonList(new ServerAddress(host, port))) .build(); @@ -134,13 +136,13 @@ public class ReactiveMongoClientFactory { && this.properties.getPassword() != null; } - private int determinePort(Environment environment) { + private int determinePort() { if (this.properties.getPort() == null) { return MongoProperties.DEFAULT_PORT; } if (this.properties.getPort() == 0) { - if (environment != null) { - String localPort = environment.getProperty("local.mongo.port"); + if (this.environment != null) { + String localPort = this.environment.getProperty("local.mongo.port"); if (localPort != null) { return Integer.valueOf(localPort); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java index 07166c006c9..d576c1d93d4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java @@ -234,7 +234,8 @@ public class EmbeddedMongoAutoConfiguration { * {@code embeddedMongoServer} bean. */ @Configuration - @ConditionalOnClass({ com.mongodb.reactivestreams.client.MongoClient.class, ReactiveMongoClientFactoryBean.class }) + @ConditionalOnClass({ com.mongodb.reactivestreams.client.MongoClient.class, + ReactiveMongoClientFactoryBean.class }) protected static class EmbeddedReactiveMongoDependencyConfiguration extends ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor { diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index e097896216d..4d7ab3e8942 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -112,6 +112,12 @@ "description": "Enable LDAP repositories.", "defaultValue": true }, + { + "name": "spring.data.mongodb.reactive-repositories.enabled", + "type": "java.lang.Boolean", + "description": "Enable Mongo reactive repositories.", + "defaultValue": true + }, { "name": "spring.data.mongodb.repositories.enabled", "type": "java.lang.Boolean", diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index e4c64257fb6..415bc5c874c 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -41,6 +41,8 @@ org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ +org.springframework.boot.autoconfigure.data.mongo.ReactiveMongoDataAutoConfiguration,\ +org.springframework.boot.autoconfigure.data.mongo.ReactiveMongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ @@ -83,6 +85,7 @@ org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoCo org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ +org.springframework.boot.autoconfigure.mongo.ReactiveMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/mongo/ReactiveCityMongoDbRepository.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/mongo/ReactiveCityMongoDbRepository.java index 39ffa8b40da..5ac5d2ec3b4 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/mongo/ReactiveCityMongoDbRepository.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/mongo/ReactiveCityMongoDbRepository.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.data.alt.mongo; import org.springframework.boot.autoconfigure.data.mongo.city.City; import org.springframework.data.repository.reactive.ReactiveCrudRepository; -public interface ReactiveCityMongoDbRepository extends ReactiveCrudRepository { +public interface ReactiveCityMongoDbRepository + extends ReactiveCrudRepository { } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfigurationTests.java index cd20c86f6eb..b9efde7d62d 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoDataAutoConfigurationTests.java @@ -17,9 +17,7 @@ package org.springframework.boot.autoconfigure.data.mongo; import org.junit.After; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; @@ -36,9 +34,6 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class ReactiveMongoDataAutoConfigurationTests { - @Rule - public final ExpectedException thrown = ExpectedException.none(); - private AnnotationConfigApplicationContext context; @After @@ -54,8 +49,8 @@ public class ReactiveMongoDataAutoConfigurationTests { PropertyPlaceholderAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, ReactiveMongoAutoConfiguration.class, ReactiveMongoDataAutoConfiguration.class); - assertThat(this.context.getBeanNamesForType(ReactiveMongoTemplate.class).length) - .isEqualTo(1); + assertThat(this.context.getBeanNamesForType(ReactiveMongoTemplate.class)) + .hasSize(1); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigurationTests.java index 0633ad1769d..ea24dad6499 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/ReactiveMongoRepositoriesAutoConfigurationTests.java @@ -52,7 +52,9 @@ public class ReactiveMongoRepositoriesAutoConfigurationTests { @After public void close() { - this.context.close(); + if (this.context != null) { + this.context.close(); + } } @Test diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactoryTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactoryTests.java index ce455d05c7e..6126d684749 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactoryTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoClientFactoryTests.java @@ -35,7 +35,7 @@ import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link MongoClientFactory} via {@link MongoProperties}. + * Tests for {@link MongoClientFactory}. * * @author Phillip Webb * @author Andy Wilkinson @@ -51,7 +51,7 @@ public class MongoClientFactoryTests { public void portCanBeCustomized() throws UnknownHostException { MongoProperties properties = new MongoProperties(); properties.setPort(12345); - MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + MongoClient client = createMongoClient(properties); List allAddresses = extractServerAddresses(client); assertThat(allAddresses).hasSize(1); assertServerAddress(allAddresses.get(0), "localhost", 12345); @@ -61,7 +61,7 @@ public class MongoClientFactoryTests { public void hostCanBeCustomized() throws UnknownHostException { MongoProperties properties = new MongoProperties(); properties.setHost("mongo.example.com"); - MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + MongoClient client = createMongoClient(properties); List allAddresses = extractServerAddresses(client); assertThat(allAddresses).hasSize(1); assertServerAddress(allAddresses.get(0), "mongo.example.com", 27017); @@ -72,7 +72,7 @@ public class MongoClientFactoryTests { MongoProperties properties = new MongoProperties(); properties.setUsername("user"); properties.setPassword("secret".toCharArray()); - MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + MongoClient client = createMongoClient(properties); assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", "test"); } @@ -83,7 +83,7 @@ public class MongoClientFactoryTests { properties.setDatabase("foo"); properties.setUsername("user"); properties.setPassword("secret".toCharArray()); - MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + MongoClient client = createMongoClient(properties); assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", "foo"); } @@ -94,7 +94,7 @@ public class MongoClientFactoryTests { properties.setAuthenticationDatabase("foo"); properties.setUsername("user"); properties.setPassword("secret".toCharArray()); - MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + MongoClient client = createMongoClient(properties); assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", "foo"); } @@ -104,7 +104,7 @@ public class MongoClientFactoryTests { MongoProperties properties = new MongoProperties(); properties.setUri("mongodb://user:secret@mongo1.example.com:12345," + "mongo2.example.com:23456/test"); - MongoClient client = new MongoClientFactory(properties).createMongoClient(null, null); + MongoClient client = createMongoClient(properties); List allAddresses = extractServerAddresses(client); assertThat(allAddresses).hasSize(2); assertServerAddress(allAddresses.get(0), "mongo1.example.com", 12345); @@ -123,7 +123,7 @@ public class MongoClientFactoryTests { this.thrown.expect(IllegalStateException.class); this.thrown.expectMessage("Invalid mongo configuration, " + "either uri or host/port/credentials must be specified"); - new MongoClientFactory(properties).createMongoClient(null, null); + createMongoClient(properties); } @Test @@ -135,7 +135,12 @@ public class MongoClientFactoryTests { this.thrown.expect(IllegalStateException.class); this.thrown.expectMessage("Invalid mongo configuration, " + "either uri or host/port/credentials must be specified"); - new MongoClientFactory(properties).createMongoClient(null, null); + createMongoClient(properties); + } + + private MongoClient createMongoClient(MongoProperties properties) + throws UnknownHostException { + return new MongoClientFactory(properties, null).createMongoClient(null); } private List extractServerAddresses(MongoClient client) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java index b479c42014d..504e807f8b0 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java @@ -78,7 +78,8 @@ public class MongoPropertiesTests { builder.requiredReplicaSetName("testReplicaSetName"); MongoClientOptions options = builder.build(); MongoProperties properties = new MongoProperties(); - MongoClient client = new MongoClientFactory(properties).createMongoClient(options, null); + MongoClient client = new MongoClientFactory(properties, null) + .createMongoClient(options); MongoClientOptions wrapped = client.getMongoClientOptions(); assertThat(wrapped.isAlwaysUseMBeans()).isEqualTo(options.isAlwaysUseMBeans()); assertThat(wrapped.getConnectionsPerHost()) diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfigurationTests.java index 36bb7e96a16..bbc8360012e 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoAutoConfigurationTests.java @@ -57,7 +57,8 @@ public class ReactiveMongoAutoConfigurationTests { @Test public void clientExists() { this.context = new AnnotationConfigApplicationContext( - PropertyPlaceholderAutoConfiguration.class, ReactiveMongoAutoConfiguration.class); + PropertyPlaceholderAutoConfiguration.class, + ReactiveMongoAutoConfiguration.class); assertThat(this.context.getBeanNamesForType(MongoClient.class).length).isEqualTo(1); } @@ -67,10 +68,11 @@ public class ReactiveMongoAutoConfigurationTests { EnvironmentTestUtils.addEnvironment(this.context, "spring.data.mongodb.host:localhost"); this.context.register(OptionsConfig.class, - PropertyPlaceholderAutoConfiguration.class, ReactiveMongoAutoConfiguration.class); + PropertyPlaceholderAutoConfiguration.class, + ReactiveMongoAutoConfiguration.class); this.context.refresh(); - assertThat(this.context.getBean(MongoClient.class).getSettings().getSocketSettings() - .getReadTimeout(TimeUnit.SECONDS)).isEqualTo(300); + assertThat(this.context.getBean(MongoClient.class).getSettings() + .getSocketSettings().getReadTimeout(TimeUnit.SECONDS)).isEqualTo(300); } @Test @@ -79,10 +81,11 @@ public class ReactiveMongoAutoConfigurationTests { EnvironmentTestUtils.addEnvironment(this.context, "spring.data.mongodb.uri:mongodb://localhost/test"); this.context.register(OptionsConfig.class, - PropertyPlaceholderAutoConfiguration.class, ReactiveMongoAutoConfiguration.class); + PropertyPlaceholderAutoConfiguration.class, + ReactiveMongoAutoConfiguration.class); this.context.refresh(); - assertThat(this.context.getBean(MongoClient.class).getSettings().getReadPreference()) - .isEqualTo(ReadPreference.nearest()); + assertThat(this.context.getBean(MongoClient.class).getSettings() + .getReadPreference()).isEqualTo(ReadPreference.nearest()); } @Test @@ -91,7 +94,8 @@ public class ReactiveMongoAutoConfigurationTests { EnvironmentTestUtils.addEnvironment(this.context, "spring.data.mongodb.uri:mongodb://localhost/test"); this.context.register(SslOptionsConfig.class, - PropertyPlaceholderAutoConfiguration.class, ReactiveMongoAutoConfiguration.class); + PropertyPlaceholderAutoConfiguration.class, + ReactiveMongoAutoConfiguration.class); this.context.refresh(); MongoClient mongo = this.context.getBean(MongoClient.class); MongoClientSettings settings = mongo.getSettings(); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactoryTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactoryTests.java index 79d84bc9f91..651d733ef25 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactoryTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/ReactiveMongoClientFactoryTests.java @@ -31,7 +31,7 @@ import org.junit.rules.ExpectedException; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link ReactiveMongoClientFactory} via {@link MongoProperties}. + * Tests for {@link ReactiveMongoClientFactory}. * * @author Mark Paluch */ @@ -44,8 +44,7 @@ public class ReactiveMongoClientFactoryTests { public void portCanBeCustomized() throws UnknownHostException { MongoProperties properties = new MongoProperties(); properties.setPort(12345); - MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, - null); + MongoClient client = createMongoClient(properties); List allAddresses = extractServerAddresses(client); assertThat(allAddresses).hasSize(1); assertServerAddress(allAddresses.get(0), "localhost", 12345); @@ -55,8 +54,7 @@ public class ReactiveMongoClientFactoryTests { public void hostCanBeCustomized() throws UnknownHostException { MongoProperties properties = new MongoProperties(); properties.setHost("mongo.example.com"); - MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, - null); + MongoClient client = createMongoClient(properties); List allAddresses = extractServerAddresses(client); assertThat(allAddresses).hasSize(1); assertServerAddress(allAddresses.get(0), "mongo.example.com", 27017); @@ -67,8 +65,7 @@ public class ReactiveMongoClientFactoryTests { MongoProperties properties = new MongoProperties(); properties.setUsername("user"); properties.setPassword("secret".toCharArray()); - MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, - null); + MongoClient client = createMongoClient(properties); assertMongoCredential(extractMongoCredentials(client).get(0), "user", "secret", "test"); } @@ -79,9 +76,9 @@ public class ReactiveMongoClientFactoryTests { properties.setDatabase("foo"); properties.setUsername("user"); properties.setPassword("secret".toCharArray()); - MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, - null); - assertMongoCredential(extractMongoCredentials(client).get(0), "user", "secret", "foo"); + MongoClient client = createMongoClient(properties); + assertMongoCredential(extractMongoCredentials(client).get(0), "user", "secret", + "foo"); } @Test @@ -90,9 +87,9 @@ public class ReactiveMongoClientFactoryTests { properties.setAuthenticationDatabase("foo"); properties.setUsername("user"); properties.setPassword("secret".toCharArray()); - MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, - null); - assertMongoCredential(extractMongoCredentials(client).get(0), "user", "secret", "foo"); + MongoClient client = createMongoClient(properties); + assertMongoCredential(extractMongoCredentials(client).get(0), "user", "secret", + "foo"); } @Test @@ -100,8 +97,7 @@ public class ReactiveMongoClientFactoryTests { MongoProperties properties = new MongoProperties(); properties.setUri("mongodb://user:secret@mongo1.example.com:12345," + "mongo2.example.com:23456/test"); - MongoClient client = new ReactiveMongoClientFactory(properties).createMongoClient(null, - null); + MongoClient client = createMongoClient(properties); List allAddresses = extractServerAddresses(client); assertThat(allAddresses).hasSize(2); assertServerAddress(allAddresses.get(0), "mongo1.example.com", 12345); @@ -120,7 +116,7 @@ public class ReactiveMongoClientFactoryTests { this.thrown.expect(IllegalStateException.class); this.thrown.expectMessage("Invalid mongo configuration, " + "either uri or host/port/credentials must be specified"); - new MongoClientFactory(properties).createMongoClient(null, null); + createMongoClient(properties); } @Test @@ -132,7 +128,11 @@ public class ReactiveMongoClientFactoryTests { this.thrown.expect(IllegalStateException.class); this.thrown.expectMessage("Invalid mongo configuration, " + "either uri or host/port/credentials must be specified"); - new MongoClientFactory(properties).createMongoClient(null, null); + createMongoClient(properties); + } + + private MongoClient createMongoClient(MongoProperties properties) { + return new ReactiveMongoClientFactory(properties, null).createMongoClient(null); } private List extractServerAddresses(MongoClient client) { diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 842c9b33e0e..f2e5cf28be6 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -589,6 +589,7 @@ content into your application; rather pick only the properties that you need. spring.data.mongodb.host=localhost # Mongo server host. Cannot be set with uri. spring.data.mongodb.password= # Login password of the mongo server. Cannot be set with uri. spring.data.mongodb.port=27017 # Mongo server port. Cannot be set with uri. + spring.data.mongodb.reactive-repositories.enabled=true # Enable Mongo reactive repositories. spring.data.mongodb.repositories.enabled=true # Enable Mongo repositories. spring.data.mongodb.uri=mongodb://localhost/test # Mongo database URI. Cannot be set with host, port and credentials. spring.data.mongodb.username= # Login user of the mongo server. Cannot be set with uri. diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index c76b67afb61..33b22b2cb18 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3299,7 +3299,8 @@ pooled connection factory by default. http://www.mongodb.com/[MongoDB] is an open-source NoSQL document database that uses a JSON-like schema instead of traditional table-based relational data. Spring Boot offers several conveniences for working with MongoDB, including the -`spring-boot-starter-data-mongodb` '`Starter`'. +`spring-boot-starter-data-mongodb` and `spring-boot-starter-data-mongodb-reactive` +'`Starters`'. diff --git a/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/pom.xml b/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/pom.xml index d764fef5606..258f03e19bb 100644 --- a/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/pom.xml +++ b/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/pom.xml @@ -23,6 +23,16 @@ org.springframework.boot spring-boot-starter + + org.springframework.data + spring-data-mongodb + + + org.mongodb + mongo-java-driver + + + org.mongodb mongodb-driver @@ -39,15 +49,5 @@ io.projectreactor reactor-core - - org.springframework.data - spring-data-mongodb - - - org.mongodb - mongo-java-driver - - - diff --git a/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/src/main/resources/META-INF/spring.provides index b19ad47d075..60fce3513b1 100644 --- a/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/src/main/resources/META-INF/spring.provides +++ b/spring-boot-starters/spring-boot-starter-data-mongodb-reactive/src/main/resources/META-INF/spring.provides @@ -1 +1 @@ -provides: spring-data-mongodb-reactive \ No newline at end of file +provides: spring-data-mongodb,mongodb-driver-async,mongodb-driver-reactivestreams \ No newline at end of file diff --git a/spring-boot-starters/spring-boot-starter-data-mongodb/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-data-mongodb/src/main/resources/META-INF/spring.provides index c7406f4ec61..14fde4c65bf 100644 --- a/spring-boot-starters/spring-boot-starter-data-mongodb/src/main/resources/META-INF/spring.provides +++ b/spring-boot-starters/spring-boot-starter-data-mongodb/src/main/resources/META-INF/spring.provides @@ -1 +1 @@ -provides: spring-data-mongodb \ No newline at end of file +provides: spring-data-mongodb,mongodb-driver \ No newline at end of file