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
This commit is contained in:
Andy Wilkinson 2014-11-26 11:40:31 +00:00
parent f2b2c085e9
commit 2c81907d58
19 changed files with 710 additions and 200 deletions

View File

@ -25,11 +25,6 @@
<artifactId>spring-boot</artifactId>
</dependency>
<!-- Optional -->
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
@ -85,6 +80,11 @@
<artifactId>javax.mail</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>

View File

@ -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<? extends FactoryBean<?>> factoryBeanClass;
private final String[] dependsOn;
protected AbstractDependsOnBeanFactoryPostProcessor(Class<?> beanClass,
Class<? extends FactoryBean<?>> 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<String> getBeanNames(ListableBeanFactory beanFactory) {
Set<String> names = new HashSet<String>();
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;
}
}
}

View File

@ -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<String> getEntityManagerFactoryBeanNames(
ListableBeanFactory beanFactory) {
Set<String> names = new HashSet<String>();
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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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<String, Object> map;
if (!sources.contains("mongo.ports")) {
map = new HashMap<String, Object>();
MapPropertySource source = new MapPropertySource("mongo.ports", map);
sources.addFirst(source);
}
else {
@SuppressWarnings("unchecked")
Map<String, Object> value = (Map<String, Object>) 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<Feature> features;
private ToStringFriendlyFeatureAwareVersion(String version, Set<Feature> features) {
Assert.notNull(version, "version must not be null");
this.version = version;
this.features = features == null ? Collections.<Feature> 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;
}
}
}

View File

@ -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<Feature> features = new HashSet<Feature>(
Arrays.asList(Feature.SYNC_DELAY));
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
public Set<Feature> getFeatures() {
return this.features;
}
public void setFeatures(Set<Feature> features) {
this.features = features;
}
}

View File

@ -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);
}
}

View File

@ -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,\

View File

@ -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"));
}
}

View File

@ -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<ServerAddress> 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<ServerAddress> 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<ServerAddress> allAddresses = client.getAllAddress();
assertEquals(2, allAddresses.size());
assertServerAddress(allAddresses.get(0), "mongo1.example.com", 12345);

View File

@ -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);
}
}
}

View File

@ -63,7 +63,7 @@
<derby.version>10.11.1.1</derby.version>
<dropwizard-metrics.version>3.1.2</dropwizard-metrics.version>
<ehcache.version>2.10.0</ehcache.version>
<embedmongo.version>1.46.4</embedmongo.version>
<embedded-mongo.version>1.48.0</embedded-mongo.version>
<flyway.version>3.2.1</flyway.version>
<freemarker.version>2.3.22</freemarker.version>
<elasticsearch.version>1.5.2</elasticsearch.version>
@ -474,11 +474,6 @@
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>${embedmongo.version}</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
@ -664,6 +659,11 @@
<artifactId>commons-pool</artifactId>
<version>${commons-pool.version}</version>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>${embedded-mongo.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>

View File

@ -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

View File

@ -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

View File

@ -32,6 +32,10 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -0,0 +1 @@
spring.data.mongodb.port=0

View File

@ -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;
}
}