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.data.mongo;
018
019import com.mongodb.ClientSessionOptions;
020import com.mongodb.DB;
021import com.mongodb.MongoClient;
022import com.mongodb.client.ClientSession;
023import com.mongodb.client.MongoDatabase;
024
025import org.springframework.beans.factory.ObjectProvider;
026import org.springframework.boot.autoconfigure.AutoConfigureAfter;
027import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
028import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
029import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
030import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
031import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
032import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration.AnyMongoClientAvailable;
033import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
034import org.springframework.boot.autoconfigure.mongo.MongoProperties;
035import org.springframework.boot.context.properties.EnableConfigurationProperties;
036import org.springframework.context.annotation.Bean;
037import org.springframework.context.annotation.Conditional;
038import org.springframework.context.annotation.Configuration;
039import org.springframework.context.annotation.Import;
040import org.springframework.dao.DataAccessException;
041import org.springframework.dao.support.PersistenceExceptionTranslator;
042import org.springframework.data.mongodb.MongoDbFactory;
043import org.springframework.data.mongodb.core.MongoDbFactorySupport;
044import org.springframework.data.mongodb.core.MongoTemplate;
045import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
046import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
047import org.springframework.data.mongodb.core.convert.DbRefResolver;
048import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
049import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
050import org.springframework.data.mongodb.core.convert.MongoConverter;
051import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
052import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
053import org.springframework.data.mongodb.gridfs.GridFsTemplate;
054import org.springframework.util.Assert;
055import org.springframework.util.StringUtils;
056
057/**
058 * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's mongo support.
059 * <p>
060 * Registers a {@link MongoTemplate} and {@link GridFsTemplate} beans if no other beans of
061 * the same type are configured.
062 * <P>
063 * Honors the {@literal spring.data.mongodb.database} property if set, otherwise connects
064 * to the {@literal test} database.
065 *
066 * @author Dave Syer
067 * @author Oliver Gierke
068 * @author Josh Long
069 * @author Phillip Webb
070 * @author EddĂș MelĂ©ndez
071 * @author Stephane Nicoll
072 * @author Christoph Strobl
073 * @since 1.1.0
074 */
075@Configuration
076@ConditionalOnClass({ MongoClient.class, com.mongodb.client.MongoClient.class,
077                MongoTemplate.class })
078@Conditional(AnyMongoClientAvailable.class)
079@EnableConfigurationProperties(MongoProperties.class)
080@Import(MongoDataConfiguration.class)
081@AutoConfigureAfter(MongoAutoConfiguration.class)
082public class MongoDataAutoConfiguration {
083
084        private final MongoProperties properties;
085
086        public MongoDataAutoConfiguration(MongoProperties properties) {
087                this.properties = properties;
088        }
089
090        @Bean
091        @ConditionalOnMissingBean(MongoDbFactory.class)
092        public MongoDbFactorySupport<?> mongoDbFactory(ObjectProvider<MongoClient> mongo,
093                        ObjectProvider<com.mongodb.client.MongoClient> mongoClient) {
094                MongoClient preferredClient = mongo.getIfAvailable();
095                if (preferredClient != null) {
096                        return new SimpleMongoDbFactory(preferredClient,
097                                        this.properties.getMongoClientDatabase());
098                }
099                com.mongodb.client.MongoClient fallbackClient = mongoClient.getIfAvailable();
100                if (fallbackClient != null) {
101                        return new SimpleMongoClientDbFactory(fallbackClient,
102                                        this.properties.getMongoClientDatabase());
103                }
104                throw new IllegalStateException("Expected to find at least one MongoDB client.");
105        }
106
107        @Bean
108        @ConditionalOnMissingBean
109        public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory,
110                        MongoConverter converter) {
111                return new MongoTemplate(mongoDbFactory, converter);
112        }
113
114        @Bean
115        @ConditionalOnMissingBean(MongoConverter.class)
116        public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory,
117                        MongoMappingContext context, MongoCustomConversions conversions) {
118                DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
119                MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver,
120                                context);
121                mappingConverter.setCustomConversions(conversions);
122                return mappingConverter;
123        }
124
125        @Bean
126        @ConditionalOnMissingBean
127        public GridFsTemplate gridFsTemplate(MongoDbFactory mongoDbFactory,
128                        MongoTemplate mongoTemplate) {
129                return new GridFsTemplate(
130                                new GridFsMongoDbFactory(mongoDbFactory, this.properties),
131                                mongoTemplate.getConverter());
132        }
133
134        /**
135         * {@link MongoDbFactory} decorator to respect
136         * {@link MongoProperties#getGridFsDatabase()} if set.
137         */
138        private static class GridFsMongoDbFactory implements MongoDbFactory {
139
140                private final MongoDbFactory mongoDbFactory;
141
142                private final MongoProperties properties;
143
144                GridFsMongoDbFactory(MongoDbFactory mongoDbFactory, MongoProperties properties) {
145                        Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null");
146                        Assert.notNull(properties, "Properties must not be null");
147                        this.mongoDbFactory = mongoDbFactory;
148                        this.properties = properties;
149                }
150
151                @Override
152                public MongoDatabase getDb() throws DataAccessException {
153                        String gridFsDatabase = this.properties.getGridFsDatabase();
154                        if (StringUtils.hasText(gridFsDatabase)) {
155                                return this.mongoDbFactory.getDb(gridFsDatabase);
156                        }
157                        return this.mongoDbFactory.getDb();
158                }
159
160                @Override
161                public MongoDatabase getDb(String dbName) throws DataAccessException {
162                        return this.mongoDbFactory.getDb(dbName);
163                }
164
165                @Override
166                public PersistenceExceptionTranslator getExceptionTranslator() {
167                        return this.mongoDbFactory.getExceptionTranslator();
168                }
169
170                @Override
171                @Deprecated
172                public DB getLegacyDb() {
173                        return this.mongoDbFactory.getLegacyDb();
174                }
175
176                @Override
177                public ClientSession getSession(ClientSessionOptions options) {
178                        return this.mongoDbFactory.getSession(options);
179                }
180
181                @Override
182                public MongoDbFactory withSession(ClientSession session) {
183                        return this.mongoDbFactory.withSession(session);
184                }
185
186        }
187
188        /**
189         * Check if either a {@link MongoClient com.mongodb.MongoClient} or
190         * {@link com.mongodb.client.MongoClient com.mongodb.client.MongoClient} bean is
191         * available.
192         */
193        static class AnyMongoClientAvailable extends AnyNestedCondition {
194
195                AnyMongoClientAvailable() {
196                        super(ConfigurationPhase.REGISTER_BEAN);
197                }
198
199                @ConditionalOnBean(MongoClient.class)
200                static class PreferredClientAvailable {
201
202                }
203
204                @ConditionalOnBean(com.mongodb.client.MongoClient.class)
205                static class FallbackClientAvailable {
206
207                }
208
209        }
210
211}