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;
018
019import java.util.Collections;
020import java.util.List;
021
022import com.mongodb.ConnectionString;
023import com.mongodb.MongoClientSettings;
024import com.mongodb.MongoClientSettings.Builder;
025import com.mongodb.MongoCredential;
026import com.mongodb.ServerAddress;
027import com.mongodb.reactivestreams.client.MongoClient;
028import com.mongodb.reactivestreams.client.MongoClients;
029
030import org.springframework.core.env.Environment;
031import org.springframework.util.Assert;
032
033/**
034 * A factory for a reactive {@link MongoClient} that applies {@link MongoProperties}.
035 *
036 * @author Mark Paluch
037 * @author Stephane Nicoll
038 * @since 2.0.0
039 */
040public class ReactiveMongoClientFactory {
041
042        private final MongoProperties properties;
043
044        private final Environment environment;
045
046        private final List<MongoClientSettingsBuilderCustomizer> builderCustomizers;
047
048        public ReactiveMongoClientFactory(MongoProperties properties, Environment environment,
049                        List<MongoClientSettingsBuilderCustomizer> builderCustomizers) {
050                this.properties = properties;
051                this.environment = environment;
052                this.builderCustomizers = (builderCustomizers != null) ? builderCustomizers
053                                : Collections.emptyList();
054        }
055
056        /**
057         * Creates a {@link MongoClient} using the given {@code settings}. If the environment
058         * contains a {@code local.mongo.port} property, it is used to configure a client to
059         * an embedded MongoDB instance.
060         * @param settings the settings
061         * @return the Mongo client
062         */
063        public MongoClient createMongoClient(MongoClientSettings settings) {
064                Integer embeddedPort = getEmbeddedPort();
065                if (embeddedPort != null) {
066                        return createEmbeddedMongoClient(settings, embeddedPort);
067                }
068                return createNetworkMongoClient(settings);
069        }
070
071        private Integer getEmbeddedPort() {
072                if (this.environment != null) {
073                        String localPort = this.environment.getProperty("local.mongo.port");
074                        if (localPort != null) {
075                                return Integer.valueOf(localPort);
076                        }
077                }
078                return null;
079        }
080
081        private MongoClient createEmbeddedMongoClient(MongoClientSettings settings,
082                        int port) {
083                Builder builder = builder(settings);
084                String host = (this.properties.getHost() != null) ? this.properties.getHost()
085                                : "localhost";
086                builder.applyToClusterSettings((cluster) -> cluster
087                                .hosts(Collections.singletonList(new ServerAddress(host, port))));
088                return createMongoClient(builder);
089        }
090
091        private MongoClient createNetworkMongoClient(MongoClientSettings settings) {
092                if (hasCustomAddress() || hasCustomCredentials()) {
093                        return createCredentialNetworkMongoClient(settings);
094                }
095                ConnectionString connectionString = new ConnectionString(
096                                this.properties.determineUri());
097                return createMongoClient(createBuilder(settings, connectionString));
098        }
099
100        private MongoClient createCredentialNetworkMongoClient(MongoClientSettings settings) {
101                Assert.state(this.properties.getUri() == null, "Invalid mongo configuration, "
102                                + "either uri or host/port/credentials must be specified");
103                Builder builder = builder(settings);
104                if (hasCustomCredentials()) {
105                        applyCredentials(builder);
106                }
107                String host = getOrDefault(this.properties.getHost(), "localhost");
108                int port = getOrDefault(this.properties.getPort(), MongoProperties.DEFAULT_PORT);
109                ServerAddress serverAddress = new ServerAddress(host, port);
110                builder.applyToClusterSettings(
111                                (cluster) -> cluster.hosts(Collections.singletonList(serverAddress)));
112                return createMongoClient(builder);
113        }
114
115        private void applyCredentials(Builder builder) {
116                String database = (this.properties.getAuthenticationDatabase() != null)
117                                ? this.properties.getAuthenticationDatabase()
118                                : this.properties.getMongoClientDatabase();
119                builder.credential((MongoCredential.createCredential(
120                                this.properties.getUsername(), database, this.properties.getPassword())));
121        }
122
123        private <T> T getOrDefault(T value, T defaultValue) {
124                return (value != null) ? value : defaultValue;
125        }
126
127        private MongoClient createMongoClient(Builder builder) {
128                customize(builder);
129                return MongoClients.create(builder.build());
130        }
131
132        private Builder createBuilder(MongoClientSettings settings,
133                        ConnectionString connection) {
134                return builder(settings).applyConnectionString(connection);
135        }
136
137        private void customize(MongoClientSettings.Builder builder) {
138                for (MongoClientSettingsBuilderCustomizer customizer : this.builderCustomizers) {
139                        customizer.customize(builder);
140                }
141        }
142
143        private boolean hasCustomAddress() {
144                return this.properties.getHost() != null || this.properties.getPort() != null;
145        }
146
147        private boolean hasCustomCredentials() {
148                return this.properties.getUsername() != null
149                                && this.properties.getPassword() != null;
150        }
151
152        private Builder builder(MongoClientSettings settings) {
153                if (settings == null) {
154                        return MongoClientSettings.builder();
155                }
156                return MongoClientSettings.builder(settings);
157        }
158
159}