From 48b0f1577bdbcd68285ea052c759492137330c8c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 8 Feb 2017 09:42:41 +0100 Subject: [PATCH] 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