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}