001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.autoconfigure.mongo.embedded; 018 019import java.io.IOException; 020import java.net.InetAddress; 021import java.net.UnknownHostException; 022import java.util.HashMap; 023import java.util.Map; 024 025import com.mongodb.MongoClient; 026import de.flapdoodle.embed.mongo.Command; 027import de.flapdoodle.embed.mongo.MongodExecutable; 028import de.flapdoodle.embed.mongo.MongodStarter; 029import de.flapdoodle.embed.mongo.config.DownloadConfigBuilder; 030import de.flapdoodle.embed.mongo.config.ExtractedArtifactStoreBuilder; 031import de.flapdoodle.embed.mongo.config.IMongodConfig; 032import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; 033import de.flapdoodle.embed.mongo.config.Net; 034import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder; 035import de.flapdoodle.embed.mongo.config.Storage; 036import de.flapdoodle.embed.mongo.distribution.Feature; 037import de.flapdoodle.embed.mongo.distribution.IFeatureAwareVersion; 038import de.flapdoodle.embed.mongo.distribution.Version; 039import de.flapdoodle.embed.mongo.distribution.Versions; 040import de.flapdoodle.embed.process.config.IRuntimeConfig; 041import de.flapdoodle.embed.process.config.io.ProcessOutput; 042import de.flapdoodle.embed.process.distribution.GenericVersion; 043import de.flapdoodle.embed.process.io.Processors; 044import de.flapdoodle.embed.process.io.Slf4jLevel; 045import de.flapdoodle.embed.process.io.progress.Slf4jProgressListener; 046import de.flapdoodle.embed.process.runtime.Network; 047import de.flapdoodle.embed.process.store.ArtifactStoreBuilder; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051import org.springframework.boot.autoconfigure.AutoConfigureBefore; 052import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 053import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 054import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 055import org.springframework.boot.autoconfigure.data.mongo.MongoClientDependsOnBeanFactoryPostProcessor; 056import org.springframework.boot.autoconfigure.data.mongo.ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor; 057import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; 058import org.springframework.boot.autoconfigure.mongo.MongoProperties; 059import org.springframework.boot.context.properties.EnableConfigurationProperties; 060import org.springframework.context.ApplicationContext; 061import org.springframework.context.ConfigurableApplicationContext; 062import org.springframework.context.annotation.Bean; 063import org.springframework.context.annotation.Configuration; 064import org.springframework.core.env.MapPropertySource; 065import org.springframework.core.env.MutablePropertySources; 066import org.springframework.core.env.PropertySource; 067import org.springframework.data.mongodb.core.MongoClientFactoryBean; 068import org.springframework.data.mongodb.core.ReactiveMongoClientFactoryBean; 069 070/** 071 * {@link EnableAutoConfiguration Auto-configuration} for Embedded Mongo. 072 * 073 * @author Henryk Konsek 074 * @author Andy Wilkinson 075 * @author Yogesh Lonkar 076 * @author Mark Paluch 077 * @since 1.3.0 078 */ 079@Configuration 080@EnableConfigurationProperties({ MongoProperties.class, EmbeddedMongoProperties.class }) 081@AutoConfigureBefore(MongoAutoConfiguration.class) 082@ConditionalOnClass({ MongoClient.class, MongodStarter.class }) 083public class EmbeddedMongoAutoConfiguration { 084 085 private static final byte[] IP4_LOOPBACK_ADDRESS = { 127, 0, 0, 1 }; 086 087 private static final byte[] IP6_LOOPBACK_ADDRESS = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 088 0, 0, 0, 0, 1 }; 089 090 private final MongoProperties properties; 091 092 private final EmbeddedMongoProperties embeddedProperties; 093 094 private final ApplicationContext context; 095 096 private final IRuntimeConfig runtimeConfig; 097 098 public EmbeddedMongoAutoConfiguration(MongoProperties properties, 099 EmbeddedMongoProperties embeddedProperties, ApplicationContext context, 100 IRuntimeConfig runtimeConfig) { 101 this.properties = properties; 102 this.embeddedProperties = embeddedProperties; 103 this.context = context; 104 this.runtimeConfig = runtimeConfig; 105 } 106 107 @Bean(initMethod = "start", destroyMethod = "stop") 108 @ConditionalOnMissingBean 109 public MongodExecutable embeddedMongoServer(IMongodConfig mongodConfig) 110 throws IOException { 111 Integer configuredPort = this.properties.getPort(); 112 if (configuredPort == null || configuredPort == 0) { 113 setEmbeddedPort(mongodConfig.net().getPort()); 114 } 115 MongodStarter mongodStarter = getMongodStarter(this.runtimeConfig); 116 return mongodStarter.prepare(mongodConfig); 117 } 118 119 private MongodStarter getMongodStarter(IRuntimeConfig runtimeConfig) { 120 if (runtimeConfig == null) { 121 return MongodStarter.getDefaultInstance(); 122 } 123 return MongodStarter.getInstance(runtimeConfig); 124 } 125 126 @Bean 127 @ConditionalOnMissingBean 128 public IMongodConfig embeddedMongoConfiguration() throws IOException { 129 MongodConfigBuilder builder = new MongodConfigBuilder() 130 .version(determineVersion()); 131 EmbeddedMongoProperties.Storage storage = this.embeddedProperties.getStorage(); 132 if (storage != null) { 133 String databaseDir = storage.getDatabaseDir(); 134 String replSetName = storage.getReplSetName(); 135 int oplogSize = (storage.getOplogSize() != null) 136 ? (int) storage.getOplogSize().toMegabytes() : 0; 137 builder.replication(new Storage(databaseDir, replSetName, oplogSize)); 138 } 139 Integer configuredPort = this.properties.getPort(); 140 if (configuredPort != null && configuredPort > 0) { 141 builder.net(new Net(getHost().getHostAddress(), configuredPort, 142 Network.localhostIsIPv6())); 143 } 144 else { 145 builder.net(new Net(getHost().getHostAddress(), 146 Network.getFreeServerPort(getHost()), Network.localhostIsIPv6())); 147 } 148 return builder.build(); 149 } 150 151 private IFeatureAwareVersion determineVersion() { 152 if (this.embeddedProperties.getFeatures() == null) { 153 for (Version version : Version.values()) { 154 if (version.asInDownloadPath() 155 .equals(this.embeddedProperties.getVersion())) { 156 return version; 157 } 158 } 159 return Versions.withFeatures( 160 new GenericVersion(this.embeddedProperties.getVersion())); 161 } 162 return Versions.withFeatures( 163 new GenericVersion(this.embeddedProperties.getVersion()), 164 this.embeddedProperties.getFeatures().toArray(new Feature[0])); 165 } 166 167 private InetAddress getHost() throws UnknownHostException { 168 if (this.properties.getHost() == null) { 169 return InetAddress.getByAddress(Network.localhostIsIPv6() 170 ? IP6_LOOPBACK_ADDRESS : IP4_LOOPBACK_ADDRESS); 171 } 172 return InetAddress.getByName(this.properties.getHost()); 173 } 174 175 private void setEmbeddedPort(int port) { 176 setPortProperty(this.context, port); 177 } 178 179 private void setPortProperty(ApplicationContext currentContext, int port) { 180 if (currentContext instanceof ConfigurableApplicationContext) { 181 MutablePropertySources sources = ((ConfigurableApplicationContext) currentContext) 182 .getEnvironment().getPropertySources(); 183 getMongoPorts(sources).put("local.mongo.port", port); 184 } 185 if (currentContext.getParent() != null) { 186 setPortProperty(currentContext.getParent(), port); 187 } 188 } 189 190 @SuppressWarnings("unchecked") 191 private Map<String, Object> getMongoPorts(MutablePropertySources sources) { 192 PropertySource<?> propertySource = sources.get("mongo.ports"); 193 if (propertySource == null) { 194 propertySource = new MapPropertySource("mongo.ports", new HashMap<>()); 195 sources.addFirst(propertySource); 196 } 197 return (Map<String, Object>) propertySource.getSource(); 198 } 199 200 @Configuration 201 @ConditionalOnClass(Logger.class) 202 @ConditionalOnMissingBean(IRuntimeConfig.class) 203 static class RuntimeConfigConfiguration { 204 205 @Bean 206 public IRuntimeConfig embeddedMongoRuntimeConfig() { 207 Logger logger = LoggerFactory 208 .getLogger(getClass().getPackage().getName() + ".EmbeddedMongo"); 209 ProcessOutput processOutput = new ProcessOutput( 210 Processors.logTo(logger, Slf4jLevel.INFO), 211 Processors.logTo(logger, Slf4jLevel.ERROR), Processors.named( 212 "[console>]", Processors.logTo(logger, Slf4jLevel.DEBUG))); 213 return new RuntimeConfigBuilder().defaultsWithLogger(Command.MongoD, logger) 214 .processOutput(processOutput).artifactStore(getArtifactStore(logger)) 215 .build(); 216 } 217 218 private ArtifactStoreBuilder getArtifactStore(Logger logger) { 219 return new ExtractedArtifactStoreBuilder().defaults(Command.MongoD) 220 .download(new DownloadConfigBuilder() 221 .defaultsForCommand(Command.MongoD) 222 .progressListener(new Slf4jProgressListener(logger)).build()); 223 } 224 225 } 226 227 /** 228 * Additional configuration to ensure that {@link MongoClient} beans depend on the 229 * {@code embeddedMongoServer} bean. 230 */ 231 @Configuration 232 @ConditionalOnClass({ MongoClient.class, MongoClientFactoryBean.class }) 233 protected static class EmbeddedMongoDependencyConfiguration 234 extends MongoClientDependsOnBeanFactoryPostProcessor { 235 236 public EmbeddedMongoDependencyConfiguration() { 237 super("embeddedMongoServer"); 238 } 239 240 } 241 242 /** 243 * Additional configuration to ensure that {@link MongoClient} beans depend on the 244 * {@code embeddedMongoServer} bean. 245 */ 246 @Configuration 247 @ConditionalOnClass({ com.mongodb.reactivestreams.client.MongoClient.class, 248 ReactiveMongoClientFactoryBean.class }) 249 protected static class EmbeddedReactiveMongoDependencyConfiguration 250 extends ReactiveStreamsMongoClientDependsOnBeanFactoryPostProcessor { 251 252 public EmbeddedReactiveMongoDependencyConfiguration() { 253 super("embeddedMongoServer"); 254 } 255 256 } 257 258}