From f2b2c085e9f0253bb3d9721172463ac620f2d2c3 Mon Sep 17 00:00:00 2001 From: Henryk Konsek Date: Tue, 25 Nov 2014 22:28:50 +0100 Subject: [PATCH 1/2] Add auto-configuration support for Embedded MongoDB See gh-2002 --- spring-boot-autoconfigure/pom.xml | 5 ++ .../mongo/EmbedMongoAutoConfiguration.java | 35 +++++++++++++ .../main/resources/META-INF/spring.factories | 1 + .../EmbedMongoAutoConfigurationTests.java | 52 +++++++++++++++++++ spring-boot-dependencies/pom.xml | 6 +++ 5 files changed, 99 insertions(+) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfigurationTests.java diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index cd63dfc249..f5f3551371 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -25,6 +25,11 @@ spring-boot + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + true + com.atomikos transactions-jdbc diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfiguration.java new file mode 100644 index 0000000000..b9931a52cb --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfiguration.java @@ -0,0 +1,35 @@ +package org.springframework.boot.autoconfigure.mongo; + +import com.mongodb.Mongo; +import de.flapdoodle.embed.mongo.MongodExecutable; +import de.flapdoodle.embed.mongo.MongodStarter; +import de.flapdoodle.embed.mongo.config.IMongodConfig; +import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; +import de.flapdoodle.embed.mongo.config.Net; +import de.flapdoodle.embed.mongo.distribution.Version; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; + +import static de.flapdoodle.embed.process.runtime.Network.localhostIsIPv6; + +@Configuration +@ConditionalOnClass({ Mongo.class, MongodStarter.class}) +public class EmbedMongoAutoConfiguration { + + @Autowired + private MongoProperties properties; + + @Bean(initMethod = "start", destroyMethod = "stop") + public MongodExecutable embedMongoServer() throws IOException { + IMongodConfig mongodConfig = new MongodConfigBuilder() + .version(Version.Main.PRODUCTION) + .net(new Net(properties.getPort(), localhostIsIPv6())) + .build(); + return MongodStarter.getDefaultInstance().prepare(mongodConfig); + } + +} 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 7d9ef47e2a..a49ccc9fd2 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -46,6 +46,7 @@ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\ +org.springframework.boot.autoconfigure.mongo.EmbedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfigurationTests.java new file mode 100644 index 0000000000..d4440825a9 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfigurationTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2014 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 com.mongodb.CommandResult; +import org.junit.After; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.data.mongodb.core.MongoTemplate; + +import static org.junit.Assert.assertEquals; +import static org.springframework.boot.test.EnvironmentTestUtils.addEnvironment; +import static org.springframework.util.SocketUtils.findAvailableTcpPort; + +public class EmbedMongoAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void shouldReturnVersionOfEmbeddedMongoServer() { + this.context = new AnnotationConfigApplicationContext(); + int mongoPort = findAvailableTcpPort(); + addEnvironment(context, "spring.data.mongodb.host=localhost", "spring.data.mongodb.port=" + mongoPort); + context.register(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, EmbedMongoAutoConfiguration.class); + context.refresh(); + MongoTemplate mongo = context.getBean(MongoTemplate.class); + CommandResult buildInfo = mongo.executeCommand("{ buildInfo: 1 }"); + assertEquals("2.6.1", buildInfo.getString("version")); + } + +} diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index fe1fb983d0..83cfaff7a1 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -63,6 +63,7 @@ 10.11.1.1 3.1.2 2.10.0 + 1.46.4 3.2.1 2.3.22 1.5.2 @@ -473,6 +474,11 @@ logback-classic ${logback.version} + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + ${embedmongo.version} + com.atomikos transactions-jdbc From 2c81907d588690bb623745c95f577a0c60a156f5 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 26 Nov 2014 11:40:31 +0000 Subject: [PATCH 2/2] Flesh out and polish Embedded MongoDB auto-configuration contribution Embedded MongoDB is now auto-configured when it is on the classpath. The Mongo instance will listen on the port specified by the spring.data.mongodb.port property. If this property has a value of zero and randomly allocated port will be used. In such an event, the MongoClient created by MongoAutoConfiguration will be automatically configured to use the port that was allocated. By default, MongoDB 2.6.10 will be used. This can be configured using the spring.embedded-mongodb.version property. Mongo's sync delay feature is enabled by default. This can be configured using the spring.embedded-mongobd.features property. Closes gh-2002 --- spring-boot-autoconfigure/pom.xml | 10 +- ...ractDependsOnBeanFactoryPostProcessor.java | 99 +++++++ ...yManagerFactoryDependsOnPostProcessor.java | 63 +---- .../mongo/EmbedMongoAutoConfiguration.java | 35 --- .../mongo/MongoAutoConfiguration.java | 6 +- .../autoconfigure/mongo/MongoProperties.java | 52 +++- .../EmbeddedMongoAutoConfiguration.java | 259 ++++++++++++++++++ .../embedded/EmbeddedMongoProperties.java | 63 +++++ ...ientDependsOnBeanFactoryPostProcessor.java | 44 +++ .../main/resources/META-INF/spring.factories | 2 +- .../EmbedMongoAutoConfigurationTests.java | 52 ---- .../mongo/MongoPropertiesTests.java | 12 +- .../EmbeddedMongoAutoConfigurationTests.java | 124 +++++++++ spring-boot-dependencies/pom.xml | 12 +- .../appendix-application-properties.adoc | 4 + .../main/asciidoc/spring-boot-features.adoc | 20 ++ .../spring-boot-sample-data-mongodb/pom.xml | 4 + .../src/main/resources/application.properties | 1 + .../mongo/SampleMongoApplicationTests.java | 48 +--- 19 files changed, 710 insertions(+), 200 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AbstractDependsOnBeanFactoryPostProcessor.java delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoProperties.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/MongoClientDependsOnBeanFactoryPostProcessor.java delete mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfigurationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-data-mongodb/src/main/resources/application.properties diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index f5f3551371..bedbc8262d 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -25,11 +25,6 @@ spring-boot - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - true - com.atomikos transactions-jdbc @@ -85,6 +80,11 @@ javax.mail true + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + true + javax.cache cache-api diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AbstractDependsOnBeanFactoryPostProcessor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AbstractDependsOnBeanFactoryPostProcessor.java new file mode 100644 index 0000000000..dce1f4b90a --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AbstractDependsOnBeanFactoryPostProcessor.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-2015 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; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.util.StringUtils; + +/** + * Abstract base class for a {@link BeanFactoryPostProcessor} that can be used to + * dynamically declare that all beans of a specific type should depend on one or more + * specific beans + * + * @author Marcel Overdijk + * @author Dave Syer + * @author Phillip Webb + * @author Andy Wilkinson + * @since 1.3.0 + * @see BeanDefinition#setDependsOn(String[]) + */ +public abstract class AbstractDependsOnBeanFactoryPostProcessor implements + BeanFactoryPostProcessor { + + private final Class beanClass; + + private final Class> factoryBeanClass; + + private final String[] dependsOn; + + protected AbstractDependsOnBeanFactoryPostProcessor(Class beanClass, + Class> factoryBeanClass, String... dependsOn) { + this.beanClass = beanClass; + this.factoryBeanClass = factoryBeanClass; + this.dependsOn = dependsOn; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + for (String beanName : getBeanNames(beanFactory)) { + BeanDefinition definition = getBeanDefinition(beanName, beanFactory); + String[] dependencies = definition.getDependsOn(); + for (String bean : this.dependsOn) { + dependencies = StringUtils.addStringToArray(dependencies, bean); + } + definition.setDependsOn(dependencies); + } + } + + private Iterable getBeanNames(ListableBeanFactory beanFactory) { + Set names = new HashSet(); + names.addAll(Arrays.asList(BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + beanFactory, this.beanClass, true, false))); + for (String factoryBeanName : BeanFactoryUtils + .beanNamesForTypeIncludingAncestors(beanFactory, this.factoryBeanClass, + true, false)) { + names.add(BeanFactoryUtils.transformedBeanName(factoryBeanName)); + } + return names; + } + + private static BeanDefinition getBeanDefinition(String beanName, + ConfigurableListableBeanFactory beanFactory) { + try { + return beanFactory.getBeanDefinition(beanName); + } + catch (NoSuchBeanDefinitionException ex) { + BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); + if (parentBeanFactory instanceof ConfigurableListableBeanFactory) { + return getBeanDefinition(beanName, + (ConfigurableListableBeanFactory) parentBeanFactory); + } + throw ex; + } + } +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/EntityManagerFactoryDependsOnPostProcessor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/EntityManagerFactoryDependsOnPostProcessor.java index f0311aceda..8e67eaab18 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/EntityManagerFactoryDependsOnPostProcessor.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/EntityManagerFactoryDependsOnPostProcessor.java @@ -16,79 +16,30 @@ package org.springframework.boot.autoconfigure.data.jpa; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - import javax.persistence.EntityManagerFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor; import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; -import org.springframework.util.StringUtils; /** * {@link BeanFactoryPostProcessor} that can be used to dynamically declare that all - * {@link EntityManagerFactory} beans should "depend on" a specific bean. + * {@link EntityManagerFactory} beans should "depend on" one or more specific beans. * * @author Marcel Overdijk * @author Dave Syer * @author Phillip Webb + * @author Andy Wilkinson * @since 1.1.0 * @see BeanDefinition#setDependsOn(String[]) */ -public class EntityManagerFactoryDependsOnPostProcessor implements - BeanFactoryPostProcessor { - - private final String[] dependsOn; +public class EntityManagerFactoryDependsOnPostProcessor extends + AbstractDependsOnBeanFactoryPostProcessor { public EntityManagerFactoryDependsOnPostProcessor(String... dependsOn) { - this.dependsOn = dependsOn; - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - for (String beanName : getEntityManagerFactoryBeanNames(beanFactory)) { - BeanDefinition definition = getBeanDefinition(beanName, beanFactory); - String[] dependencies = definition.getDependsOn(); - for (String bean : this.dependsOn) { - dependencies = StringUtils.addStringToArray(dependencies, bean); - } - definition.setDependsOn(dependencies); - } - } - - private static BeanDefinition getBeanDefinition(String beanName, - ConfigurableListableBeanFactory beanFactory) { - try { - return beanFactory.getBeanDefinition(beanName); - } - catch (NoSuchBeanDefinitionException ex) { - BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); - if (parentBeanFactory instanceof ConfigurableListableBeanFactory) { - return getBeanDefinition(beanName, - (ConfigurableListableBeanFactory) parentBeanFactory); - } - throw ex; - } - } - - private Iterable getEntityManagerFactoryBeanNames( - ListableBeanFactory beanFactory) { - Set names = new HashSet(); - names.addAll(Arrays.asList(BeanFactoryUtils.beanNamesForTypeIncludingAncestors( - beanFactory, EntityManagerFactory.class, true, false))); - for (String factoryBeanName : BeanFactoryUtils - .beanNamesForTypeIncludingAncestors(beanFactory, - AbstractEntityManagerFactoryBean.class, true, false)) { - names.add(BeanFactoryUtils.transformedBeanName(factoryBeanName)); - } - return names; + super(EntityManagerFactory.class, AbstractEntityManagerFactoryBean.class, + dependsOn); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfiguration.java deleted file mode 100644 index b9931a52cb..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfiguration.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.springframework.boot.autoconfigure.mongo; - -import com.mongodb.Mongo; -import de.flapdoodle.embed.mongo.MongodExecutable; -import de.flapdoodle.embed.mongo.MongodStarter; -import de.flapdoodle.embed.mongo.config.IMongodConfig; -import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; -import de.flapdoodle.embed.mongo.config.Net; -import de.flapdoodle.embed.mongo.distribution.Version; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.io.IOException; - -import static de.flapdoodle.embed.process.runtime.Network.localhostIsIPv6; - -@Configuration -@ConditionalOnClass({ Mongo.class, MongodStarter.class}) -public class EmbedMongoAutoConfiguration { - - @Autowired - private MongoProperties properties; - - @Bean(initMethod = "start", destroyMethod = "stop") - public MongodExecutable embedMongoServer() throws IOException { - IMongodConfig mongodConfig = new MongodConfigBuilder() - .version(Version.Main.PRODUCTION) - .net(new Net(properties.getPort(), localhostIsIPv6())) - .build(); - return MongodStarter.getDefaultInstance().prepare(mongodConfig); - } - -} 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 18de9523a5..c405d65ca6 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 @@ -27,6 +27,7 @@ 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; import com.mongodb.MongoClient; import com.mongodb.MongoClientOptions; @@ -50,6 +51,9 @@ public class MongoAutoConfiguration { @Autowired(required = false) private MongoClientOptions options; + @Autowired + private Environment environment; + private MongoClient mongo; @PreDestroy @@ -62,7 +66,7 @@ public class MongoAutoConfiguration { @Bean @ConditionalOnMissingBean public MongoClient mongo() throws UnknownHostException { - this.mongo = this.properties.createMongoClient(this.options); + this.mongo = this.properties.createMongoClient(this.options, this.environment); return this.mongo; } 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 d8011886a9..5e20a304b1 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 @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.env.Environment; import org.springframework.data.mapping.model.FieldNamingStrategy; import com.mongodb.MongoClient; @@ -42,7 +43,10 @@ import com.mongodb.ServerAddress; @ConfigurationProperties(prefix = "spring.data.mongodb") public class MongoProperties { - private static final int DEFAULT_PORT = 27017; + /** + * Default port used when the configured port is {@code null}. + */ + public static final int DEFAULT_PORT = 27017; /** * Mongo server host. @@ -178,8 +182,34 @@ public class MongoProperties { return new MongoClientURI(this.uri).getDatabase(); } + /** + * Creates a {@link MongoClient} using the given {@code options} + * + * @param options the options + * @return the Mongo client + * @throws UnknownHostException if the configured host is unknown + * @deprecated Since 1.3.0 in favour of + * {@link #createMongoClient(MongoClientOptions, Environment)} + */ + @Deprecated public MongoClient createMongoClient(MongoClientOptions options) throws UnknownHostException { + return this.createMongoClient(options, null); + } + + /** + * Creates a {@link MongoClient} using the given {@code options} and + * {@code environment}. If the configured port is zero, the value of the + * {@code local.server.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 (options == null) { @@ -193,7 +223,7 @@ public class MongoProperties { this.username, database, this.password)); } String host = this.host == null ? "localhost" : this.host; - int port = this.port == null ? DEFAULT_PORT : this.port; + int port = determinePort(environment); return new MongoClient(Arrays.asList(new ServerAddress(host, port)), credentials, options); } @@ -213,6 +243,24 @@ public class MongoProperties { 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) { Builder builder = MongoClientOptions.builder(); if (options != null) { 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 new file mode 100644 index 0000000000..418e0fce08 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java @@ -0,0 +1,259 @@ +/* + * Copyright 2012-2015 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.embedded; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +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.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.util.Assert; + +import com.mongodb.Mongo; +import com.mongodb.MongoClient; + +import de.flapdoodle.embed.mongo.Command; +import de.flapdoodle.embed.mongo.MongodExecutable; +import de.flapdoodle.embed.mongo.MongodStarter; +import de.flapdoodle.embed.mongo.config.ArtifactStoreBuilder; +import de.flapdoodle.embed.mongo.config.DownloadConfigBuilder; +import de.flapdoodle.embed.mongo.config.IMongodConfig; +import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; +import de.flapdoodle.embed.mongo.config.Net; +import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder; +import de.flapdoodle.embed.mongo.distribution.Feature; +import de.flapdoodle.embed.mongo.distribution.IFeatureAwareVersion; +import de.flapdoodle.embed.process.config.IRuntimeConfig; +import de.flapdoodle.embed.process.config.io.ProcessOutput; +import de.flapdoodle.embed.process.io.Processors; +import de.flapdoodle.embed.process.io.Slf4jLevel; +import de.flapdoodle.embed.process.io.progress.Slf4jProgressListener; + +import static de.flapdoodle.embed.process.runtime.Network.localhostIsIPv6; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Embedded Mongo. + * + * @author Henryk Konsek + * @author Andy Wilkinson + */ +@Configuration +@EnableConfigurationProperties({ MongoProperties.class, EmbeddedMongoProperties.class }) +@AutoConfigureBefore(MongoAutoConfiguration.class) +@ConditionalOnClass({ Mongo.class, MongodStarter.class }) +public class EmbeddedMongoAutoConfiguration { + + @Autowired + private MongoProperties properties; + + @Autowired + private EmbeddedMongoProperties embeddedProperties; + + @Autowired + private ApplicationContext context; + + @Bean(initMethod = "start", destroyMethod = "stop") + @ConditionalOnMissingBean + public MongodExecutable embeddedMongoServer(IMongodConfig mongodConfig, + IRuntimeConfig runtimeConfig) throws IOException { + return createEmbeddedMongoServer(mongodConfig, runtimeConfig); + } + + @Bean(initMethod = "start", destroyMethod = "stop") + @ConditionalOnMissingBean + public MongodExecutable embeddedMongoServer(IMongodConfig mongodConfig) + throws IOException { + return createEmbeddedMongoServer(mongodConfig, null); + } + + private MongodExecutable createEmbeddedMongoServer(IMongodConfig mongodConfig, + IRuntimeConfig runtimeConfig) { + if (getPort() == 0) { + publishPortInfo(mongodConfig.net().getPort()); + } + MongodStarter mongodStarter = runtimeConfig == null ? MongodStarter + .getDefaultInstance() : MongodStarter.getInstance(runtimeConfig); + return mongodStarter.prepare(mongodConfig); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(Logger.class) + public IRuntimeConfig embeddedMongoRuntimeConfig() { + Logger logger = LoggerFactory.getLogger(getClass().getPackage().getName() + + ".EmbeddedMongo"); + + ProcessOutput processOutput = new ProcessOutput( + Processors.logTo(logger, Slf4jLevel.INFO), + Processors.logTo(logger, Slf4jLevel.ERROR), + Processors.named("[console>]", Processors.logTo(logger, Slf4jLevel.DEBUG))); + + return new RuntimeConfigBuilder() + .defaultsWithLogger(Command.MongoD, logger) + .processOutput(processOutput) + .artifactStore( + new ArtifactStoreBuilder().defaults(Command.MongoD).download( + new DownloadConfigBuilder().defaultsForCommand( + Command.MongoD).progressListener( + new Slf4jProgressListener(logger)))).build(); + } + + @Bean + @ConditionalOnMissingBean + public IMongodConfig embeddedMongoConfiguration() throws IOException { + IFeatureAwareVersion featureAwareVersion = new ToStringFriendlyFeatureAwareVersion( + this.embeddedProperties.getVersion(), + this.embeddedProperties.getFeatures()); + MongodConfigBuilder builder = new MongodConfigBuilder() + .version(featureAwareVersion); + if (getPort() > 0) { + builder.net(new Net(getPort(), localhostIsIPv6())); + } + return builder.build(); + } + + private int getPort() { + return this.properties.getPort() == null ? MongoProperties.DEFAULT_PORT + : this.properties.getPort(); + } + + private void publishPortInfo(int port) { + setPortProperty(this.context, port); + } + + private void setPortProperty(ApplicationContext context, int port) { + if (context instanceof ConfigurableApplicationContext) { + ConfigurableEnvironment environment = ((ConfigurableApplicationContext) context) + .getEnvironment(); + MutablePropertySources sources = environment.getPropertySources(); + Map map; + if (!sources.contains("mongo.ports")) { + map = new HashMap(); + MapPropertySource source = new MapPropertySource("mongo.ports", map); + sources.addFirst(source); + } + else { + @SuppressWarnings("unchecked") + Map value = (Map) sources.get( + "mongo.ports").getSource(); + map = value; + } + map.put("local.mongo.port", port); + } + if (this.context.getParent() != null) { + setPortProperty(this.context.getParent(), port); + } + } + + /** + * Additional configuration to ensure that {@link MongoClient} beans depend on the + * {@code embeddedMongoServer} bean. + */ + @Configuration + @ConditionalOnClass(MongoClient.class) + protected static class EmbeddedMongoDependencyConfiguration extends + MongoClientDependsOnBeanFactoryPostProcessor { + + public EmbeddedMongoDependencyConfiguration() { + super("embeddedMongoServer"); + } + + } + + /** + * A workaround for the lack of a {@code toString} implementation on + * {@code GenericFeatureAwareVersion}. + */ + private static class ToStringFriendlyFeatureAwareVersion implements + IFeatureAwareVersion { + + private final String version; + + private final Set features; + + private ToStringFriendlyFeatureAwareVersion(String version, Set features) { + Assert.notNull(version, "version must not be null"); + this.version = version; + this.features = features == null ? Collections. emptySet() + : features; + } + + @Override + public String asInDownloadPath() { + return this.version; + } + + @Override + public boolean enabled(Feature feature) { + return this.features.contains(feature); + } + + @Override + public String toString() { + return this.version; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + this.features.hashCode(); + result = prime * result + this.version.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ToStringFriendlyFeatureAwareVersion other = (ToStringFriendlyFeatureAwareVersion) obj; + if (!this.features.equals(other.features)) { + return false; + } + else if (!this.version.equals(other.version)) { + return false; + } + return true; + } + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoProperties.java new file mode 100644 index 0000000000..2271869452 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoProperties.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2015 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.embedded; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import de.flapdoodle.embed.mongo.distribution.Feature; + +/** + * Configuration properties for Embedded Mongo + * + * @author Andy Wilkinson + * @since 1.3.0 + */ +@ConfigurationProperties(prefix = "spring.embedded-mongodb") +public class EmbeddedMongoProperties { + + /** + * Version of Mongo to use + */ + private String version = "2.6.10"; + + /** + * Comma-separated list of features to enable + */ + private Set features = new HashSet( + Arrays.asList(Feature.SYNC_DELAY)); + + public String getVersion() { + return this.version; + } + + public void setVersion(String version) { + this.version = version; + } + + public Set getFeatures() { + return this.features; + } + + public void setFeatures(Set features) { + this.features = features; + } + +} \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/MongoClientDependsOnBeanFactoryPostProcessor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/MongoClientDependsOnBeanFactoryPostProcessor.java new file mode 100644 index 0000000000..86724c68c6 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/MongoClientDependsOnBeanFactoryPostProcessor.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2015 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.embedded; + +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.MongoClientFactoryBean; + +import com.mongodb.MongoClient; + +/** + * {@link BeanFactoryPostProcessor} to automatically set up the recommended + * {@link BeanDefinition#setDependsOn(String[]) dependsOn} configuration for Mongo clients + * when used embedded Mongo. + * + * @author Andy Wilkinson + * @since 1.3.0 + */ +@Order(Ordered.LOWEST_PRECEDENCE) +public class MongoClientDependsOnBeanFactoryPostProcessor extends + AbstractDependsOnBeanFactoryPostProcessor { + + public MongoClientDependsOnBeanFactoryPostProcessor(String... dependsOn) { + super(MongoClient.class, MongoClientFactoryBean.class, dependsOn); + } + +} \ No newline at end of file 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 a49ccc9fd2..3e10a6106b 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -46,7 +46,7 @@ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\ -org.springframework.boot.autoconfigure.mongo.EmbedMongoAutoConfiguration,\ +org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfigurationTests.java deleted file mode 100644 index d4440825a9..0000000000 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/EmbedMongoAutoConfigurationTests.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2012-2014 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 com.mongodb.CommandResult; -import org.junit.After; -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.data.mongodb.core.MongoTemplate; - -import static org.junit.Assert.assertEquals; -import static org.springframework.boot.test.EnvironmentTestUtils.addEnvironment; -import static org.springframework.util.SocketUtils.findAvailableTcpPort; - -public class EmbedMongoAutoConfigurationTests { - - private AnnotationConfigApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void shouldReturnVersionOfEmbeddedMongoServer() { - this.context = new AnnotationConfigApplicationContext(); - int mongoPort = findAvailableTcpPort(); - addEnvironment(context, "spring.data.mongodb.host=localhost", "spring.data.mongodb.port=" + mongoPort); - context.register(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, EmbedMongoAutoConfiguration.class); - context.refresh(); - MongoTemplate mongo = context.getBean(MongoTemplate.class); - CommandResult buildInfo = mongo.executeCommand("{ buildInfo: 1 }"); - assertEquals("2.6.1", buildInfo.getString("version")); - } - -} 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 8e0cd1f595..91cd8166a0 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 @@ -57,7 +57,7 @@ public class MongoPropertiesTests { public void portCanBeCustomized() throws UnknownHostException { MongoProperties properties = new MongoProperties(); properties.setPort(12345); - MongoClient client = properties.createMongoClient(null); + MongoClient client = properties.createMongoClient(null, null); List allAddresses = client.getAllAddress(); assertThat(allAddresses, hasSize(1)); assertServerAddress(allAddresses.get(0), "localhost", 12345); @@ -67,7 +67,7 @@ public class MongoPropertiesTests { public void hostCanBeCustomized() throws UnknownHostException { MongoProperties properties = new MongoProperties(); properties.setHost("mongo.example.com"); - MongoClient client = properties.createMongoClient(null); + MongoClient client = properties.createMongoClient(null, null); List allAddresses = client.getAllAddress(); assertThat(allAddresses, hasSize(1)); assertServerAddress(allAddresses.get(0), "mongo.example.com", 27017); @@ -78,7 +78,7 @@ public class MongoPropertiesTests { MongoProperties properties = new MongoProperties(); properties.setUsername("user"); properties.setPassword("secret".toCharArray()); - MongoClient client = properties.createMongoClient(null); + MongoClient client = properties.createMongoClient(null, null); assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", "test"); } @@ -89,7 +89,7 @@ public class MongoPropertiesTests { properties.setDatabase("foo"); properties.setUsername("user"); properties.setPassword("secret".toCharArray()); - MongoClient client = properties.createMongoClient(null); + MongoClient client = properties.createMongoClient(null, null); assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", "foo"); } @@ -99,7 +99,7 @@ public class MongoPropertiesTests { properties.setAuthenticationDatabase("foo"); properties.setUsername("user"); properties.setPassword("secret".toCharArray()); - MongoClient client = properties.createMongoClient(null); + MongoClient client = properties.createMongoClient(null, null); assertMongoCredential(client.getCredentialsList().get(0), "user", "secret", "foo"); } @@ -108,7 +108,7 @@ public class MongoPropertiesTests { MongoProperties properties = new MongoProperties(); properties.setUri("mongodb://user:secret@mongo1.example.com:12345," + "mongo2.example.com:23456/test"); - MongoClient client = properties.createMongoClient(null); + MongoClient client = properties.createMongoClient(null, null); List allAddresses = client.getAllAddress(); assertEquals(2, allAddresses.size()); assertServerAddress(allAddresses.get(0), "mongo1.example.com", 12345); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfigurationTests.java new file mode 100644 index 0000000000..eef395d155 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfigurationTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012-2015 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.embedded; + +import java.net.UnknownHostException; + +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoTemplate; + +import com.mongodb.CommandResult; +import com.mongodb.MongoClient; + +import de.flapdoodle.embed.mongo.distribution.Feature; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.springframework.boot.test.EnvironmentTestUtils.addEnvironment; +import static org.springframework.util.SocketUtils.findAvailableTcpPort; + +/** + * Tests for {@link EmbeddedMongoAutoConfiguration}. + * + * @author Henryk Konsek + * @author Andy Wilkinson + */ +public class EmbeddedMongoAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void defaultVersion() { + assertVersionConfiguration(null, "2.6.10"); + } + + @Test + public void customVersion() { + assertVersionConfiguration("2.7.1", "2.7.1"); + } + + @Test + public void customFeatures() { + this.context = new AnnotationConfigApplicationContext(); + int mongoPort = findAvailableTcpPort(); + addEnvironment(this.context, "spring.data.mongodb.port=" + mongoPort, + "spring.embedded-mongodb.features=TEXT_SEARCH, SYNC_DELAY"); + this.context.register(EmbeddedMongoAutoConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBean(EmbeddedMongoProperties.class).getFeatures(), + hasItems(Feature.TEXT_SEARCH, Feature.SYNC_DELAY)); + } + + @Test + public void randomlyAllocatedPortIsAvailableWhenCreatingMongoClient() { + this.context = new AnnotationConfigApplicationContext(); + addEnvironment(this.context, "spring.data.mongodb.port=0"); + this.context.register(EmbeddedMongoAutoConfiguration.class, + MongoClientConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertThat( + this.context.getBean(MongoClient.class).getAddress().getPort(), + is(equalTo(Integer.valueOf(this.context.getEnvironment().getProperty( + "local.mongo.port"))))); + } + + private void assertVersionConfiguration(String configuredVersion, + String expectedVersion) { + this.context = new AnnotationConfigApplicationContext(); + int mongoPort = findAvailableTcpPort(); + addEnvironment(this.context, "spring.data.mongodb.port=" + mongoPort); + if (configuredVersion != null) { + addEnvironment(this.context, "spring.embedded-mongodb.version=" + + configuredVersion); + } + this.context.register(MongoAutoConfiguration.class, + MongoDataAutoConfiguration.class, EmbeddedMongoAutoConfiguration.class); + this.context.refresh(); + MongoTemplate mongo = this.context.getBean(MongoTemplate.class); + CommandResult buildInfo = mongo.executeCommand("{ buildInfo: 1 }"); + + assertThat(buildInfo.getString("version"), equalTo(expectedVersion)); + } + + @Configuration + static class MongoClientConfiguration { + + @Bean + public MongoClient mongoClient(@Value("${local.mongo.port}") int port) + throws UnknownHostException { + return new MongoClient("localhost", port); + } + } +} diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 83cfaff7a1..c644618d82 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -63,7 +63,7 @@ 10.11.1.1 3.1.2 2.10.0 - 1.46.4 + 1.48.0 3.2.1 2.3.22 1.5.2 @@ -474,11 +474,6 @@ logback-classic ${logback.version} - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - ${embedmongo.version} - com.atomikos transactions-jdbc @@ -664,6 +659,11 @@ commons-pool ${commons-pool.version} + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + ${embedded-mongo.version} + io.dropwizard.metrics metrics-core 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 65a6b3e4fc..d8d2f5c055 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -334,6 +334,10 @@ content into your application; rather pick only the properties that you need. spring.data.mongodb.repositories.enabled=true # if spring data repository support is enabled spring.data.mongodb.field-naming-strategy= # fully qualified name of the FieldNamingStrategy to use + # EMBEDDED MONGODB ({sc-spring-boot-autoconfigure}/mongo/embedded/EmbeddedMongoProerties.{sc-ext}[EmbeddedMongoProperties]) + spring.embedded-mongodb.version=2.6.10 # version of Mongo to use + spring.embedded-mongodb.features=SYNC_DELAY # comma-separated list of features to enable + # JPA ({sc-spring-boot-autoconfigure}/orm/jpa/JpaBaseConfiguration.{sc-ext}[JpaBaseConfiguration], {sc-spring-boot-autoconfigure}/orm/jpa/HibernateJpaAutoConfiguration.{sc-ext}[HibernateJpaAutoConfiguration]) spring.jpa.properties.*= # properties to set on the JPA connection spring.jpa.open-in-view=true 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 f9d016bde4..5841f89b2a 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -2478,6 +2478,26 @@ documentation]. +[[boot-features-mongo-embedded]] +==== Embedded Mongo +Spring Boot offers auto-configuration for +[https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo] Embedded Mongo. To use +it in your Spring Boot application add a dependency on +`de.flapdoodle.embed:de.flapdoodle.embed.mongo`. + +The port that Mongo will listen on can be configured using the `spring.data.mongodb.port` +property. To use a randomly allocated free port use a value of zero. The `MongoClient` +created by `MongoAutoConfiguration` will be automatically configured to use the randomly +allocated port. + +If you have SLF4J on the classpath, output produced by Mongo will be automatically routed +to a logger named `org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongo`. + +You can declare your own `IMongodConfig` and `IRuntimeConfig` beans to take control of the +Mongo instance's configuration and logging routing. + + + [[boot-features-gemfire]] === Gemfire https://github.com/spring-projects/spring-data-gemfire[Spring Data Gemfire] provides diff --git a/spring-boot-samples/spring-boot-sample-data-mongodb/pom.xml b/spring-boot-samples/spring-boot-sample-data-mongodb/pom.xml index ae7d4a1c77..5899b743a9 100644 --- a/spring-boot-samples/spring-boot-sample-data-mongodb/pom.xml +++ b/spring-boot-samples/spring-boot-sample-data-mongodb/pom.xml @@ -32,6 +32,10 @@ spring-boot-starter-test test + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + diff --git a/spring-boot-samples/spring-boot-sample-data-mongodb/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-data-mongodb/src/main/resources/application.properties new file mode 100644 index 0000000000..03ef29e288 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-mongodb/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.data.mongodb.port=0 \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-data-mongodb/src/test/java/sample/data/mongo/SampleMongoApplicationTests.java b/spring-boot-samples/spring-boot-sample-data-mongodb/src/test/java/sample/data/mongo/SampleMongoApplicationTests.java index 418826bb6b..85c9480ece 100644 --- a/spring-boot-samples/spring-boot-sample-data-mongodb/src/test/java/sample/data/mongo/SampleMongoApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-data-mongodb/src/test/java/sample/data/mongo/SampleMongoApplicationTests.java @@ -16,14 +16,13 @@ package sample.data.mongo; -import java.util.regex.Pattern; - -import org.junit.Rule; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.OutputCapture; -import org.springframework.core.NestedCheckedException; - -import com.mongodb.MongoTimeoutException; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.assertTrue; @@ -31,44 +30,21 @@ import static org.junit.Assert.assertTrue; * Tests for {@link SampleMongoApplication}. * * @author Dave Syer + * @author Andy Wilkinson */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SampleMongoApplication.class) +@IntegrationTest public class SampleMongoApplicationTests { - private static final Pattern TIMEOUT_MESSAGE_PATTERN = Pattern - .compile("Timed out after [0-9]+ ms while waiting for a server.*"); - - @Rule - public OutputCapture outputCapture = new OutputCapture(); + @ClassRule + public static OutputCapture outputCapture = new OutputCapture(); @Test public void testDefaultSettings() throws Exception { - try { - SampleMongoApplication.main(new String[0]); - } - catch (IllegalStateException ex) { - if (serverNotRunning(ex)) { - return; - } - } - String output = this.outputCapture.toString(); + String output = SampleMongoApplicationTests.outputCapture.toString(); assertTrue("Wrong output: " + output, output.contains("firstName='Alice', lastName='Smith'")); } - private boolean serverNotRunning(IllegalStateException ex) { - @SuppressWarnings("serial") - NestedCheckedException nested = new NestedCheckedException("failed", ex) { - }; - Throwable root = nested.getRootCause(); - if (root instanceof MongoTimeoutException) { - if (root.getMessage().contains("Unable to connect to any server")) { - return true; - } - if (TIMEOUT_MESSAGE_PATTERN.matcher(root.getMessage()).matches()) { - return true; - } - } - return false; - } - }