001/* 002 * (C) Copyright 2014-2017 Nuxeo SA (http://nuxeo.com/) and others. 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 * Contributors: 017 * Florent Guillaume 018 * Kevin Leturc 019 * Funsho David 020 */ 021package org.nuxeo.runtime.mongodb; 022 023import java.io.IOException; 024import java.io.InputStream; 025import java.nio.file.Files; 026import java.nio.file.Paths; 027import java.security.GeneralSecurityException; 028import java.security.KeyStore; 029import java.util.stream.StreamSupport; 030 031import javax.net.ssl.SSLContext; 032 033import org.apache.commons.lang3.StringUtils; 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.apache.http.ssl.SSLContextBuilder; 037import org.apache.http.ssl.SSLContexts; 038 039import com.mongodb.MongoClient; 040import com.mongodb.MongoClientOptions; 041import com.mongodb.MongoClientURI; 042import com.mongodb.ServerAddress; 043import com.mongodb.client.MongoDatabase; 044import com.mongodb.client.MongoIterable; 045 046/** 047 * Helper for connection to the MongoDB server 048 * 049 * @since 9.1 050 */ 051public class MongoDBConnectionHelper { 052 053 private static final Log log = LogFactory.getLog(MongoDBConnectionHelper.class); 054 055 private static final String DB_DEFAULT = "nuxeo"; 056 057 private static final int MONGODB_OPTION_CONNECTION_TIMEOUT_MS = 30000; 058 059 private static final int MONGODB_OPTION_SOCKET_TIMEOUT_MS = 60000; 060 061 private MongoDBConnectionHelper() { 062 // Empty 063 } 064 065 /** 066 * Initialize a connection to the MongoDB server 067 * 068 * @param server the server url 069 * @return the MongoDB client 070 */ 071 public static MongoClient newMongoClient(String server) { 072 MongoDBConnectionConfig config = new MongoDBConnectionConfig(); 073 config.server = server; 074 return newMongoClient(config); 075 } 076 077 /** 078 * Initializes a connection to the MongoDB server. 079 * 080 * @param config the MongoDB connection config 081 * @return the MongoDB client 082 * @since 10.3 083 */ 084 public static MongoClient newMongoClient(MongoDBConnectionConfig config) { 085 String server = config.server; 086 if (StringUtils.isBlank(server)) { 087 throw new RuntimeException("Missing <server> in MongoDB descriptor"); 088 } 089 MongoClientOptions.Builder optionsBuilder = MongoClientOptions.builder() 090 // can help to prevent firewall disconnecting inactive connection, option not available from URI 091 .socketKeepAlive(true) 092 // don't wait forever by default, can be overridden using URI options 093 .connectTimeout(MONGODB_OPTION_CONNECTION_TIMEOUT_MS) 094 .socketTimeout(MONGODB_OPTION_SOCKET_TIMEOUT_MS) 095 .description("Nuxeo"); 096 SSLContext sslContext = getSSLContext(config); 097 if (sslContext == null) { 098 if (config.ssl != null) { 099 optionsBuilder.sslEnabled(config.ssl.booleanValue()); 100 } 101 } else { 102 optionsBuilder.sslEnabled(true); 103 optionsBuilder.sslContext(sslContext); 104 } 105 MongoClient client; 106 if (server.startsWith("mongodb://")) { 107 // allow mongodb:// URI syntax for the server, to pass everything in one string 108 client = new MongoClient(new MongoClientURI(server, optionsBuilder)); 109 } else { 110 client = new MongoClient(new ServerAddress(server), optionsBuilder.build()); 111 } 112 if (log.isDebugEnabled()) { 113 log.debug("MongoClient initialized with options: " + client.getMongoClientOptions().toString()); 114 } 115 return client; 116 } 117 118 protected static SSLContext getSSLContext(MongoDBConnectionConfig config) { 119 try { 120 KeyStore trustStore = loadKeyStore(config.trustStorePath, config.trustStorePassword, config.trustStoreType); 121 KeyStore keyStore = loadKeyStore(config.keyStorePath, config.keyStorePassword, config.keyStoreType); 122 if (trustStore == null && keyStore == null) { 123 return null; 124 } 125 SSLContextBuilder sslContextBuilder = SSLContexts.custom(); 126 if (trustStore != null) { 127 sslContextBuilder.loadTrustMaterial(trustStore, null); 128 } 129 if (keyStore != null) { 130 sslContextBuilder.loadKeyMaterial(keyStore, null); 131 } 132 return sslContextBuilder.build(); 133 } catch (GeneralSecurityException | IOException e) { 134 throw new RuntimeException("Cannot setup SSL context: " + config, e); 135 } 136 } 137 138 protected static KeyStore loadKeyStore(String path, String password, String type) 139 throws GeneralSecurityException, IOException { 140 if (StringUtils.isBlank(path)) { 141 return null; 142 } 143 String keyStoreType = StringUtils.defaultIfBlank(type, KeyStore.getDefaultType()); 144 KeyStore keyStore = KeyStore.getInstance(keyStoreType); 145 char[] passwordChars = StringUtils.isBlank(password) ? null : password.toCharArray(); 146 try (InputStream is = Files.newInputStream(Paths.get(path))) { 147 keyStore.load(is, passwordChars); 148 } 149 return keyStore; 150 } 151 152 /** 153 * @return a database representing the specified database 154 */ 155 public static MongoDatabase getDatabase(MongoClient mongoClient, String dbname) { 156 if (StringUtils.isBlank(dbname)) { 157 dbname = DB_DEFAULT; 158 } 159 return mongoClient.getDatabase(dbname); 160 } 161 162 /** 163 * Check if the collection exists and if it is not empty 164 * 165 * @param mongoDatabase the Mongo database 166 * @param collection the collection name 167 * @return true if the collection exists and not empty, false otherwise 168 */ 169 public static boolean hasCollection(MongoDatabase mongoDatabase, String collection) { 170 MongoIterable<String> collections = mongoDatabase.listCollectionNames(); 171 boolean found = StreamSupport.stream(collections.spliterator(), false).anyMatch(collection::equals); 172 return found && mongoDatabase.getCollection(collection).count() > 0; 173 } 174}