com.mongodb.MongoClientURI Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mongo-java-driver Show documentation
Show all versions of mongo-java-driver Show documentation
The MongoDB Java Driver uber-artifact, containing mongodb-driver, mongodb-driver-core, and bson
/*
* Copyright (c) 2008-2014 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb;
import javax.net.ssl.SSLSocketFactory;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
/**
* Represents a URI
* which can be used to create a MongoClient instance. The URI describes the hosts to
* be used and options.
* The format of the URI is:
*
* mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database[.collection]][?options]]
*
*
* - {@code mongodb://} is a required prefix to identify that this is a string in the standard connection format.
* - {@code username:password@} are optional. If given, the driver will attempt to login to a database after
* connecting to a database server. For some authentication mechanisms, only the username is specified and the password is not,
* in which case the ":" after the username is left off as well.
* - {@code host1} is the only required part of the URI. It identifies a server address to connect to.
* - {@code :portX} is optional and defaults to :27017 if not provided.
* - {@code /database} is the name of the database to login to and thus is only relevant if the
* {@code username:password@} syntax is used. If not specified the "admin" database will be used by default.
* - {@code ?options} are connection options. Note that if {@code database} is absent there is still a {@code /}
* required between the last host and the {@code ?} introducing the options. Options are name=value pairs and the pairs
* are separated by "&". For backwards compatibility, ";" is accepted as a separator in addition to "&",
* but should be considered as deprecated.
*
* The following options are supported (case insensitive):
*
* Replica set configuration:
*
* - {@code replicaSet=name}: Implies that the hosts given are a seed list, and the driver will attempt to find
* all members of the set.
*
*
* Connection Configuration:
*
* - {@code ssl=true|false}: Whether to connect using SSL.
* - {@code connectTimeoutMS=ms}: How long a connection can take to be opened before timing out.
* - {@code socketTimeoutMS=ms}: How long a send or receive on a socket can take before timing out.
* - {@code maxIdleTimeMS=ms}: Maximum idle time of a pooled connection. A connection that exceeds this limit will be closed
* - {@code maxLifeTimeMS=ms}: Maximum life time of a pooled connection. A connection that exceeds this limit will be closed
*
*
* Connection pool configuration:
*
* - {@code maxPoolSize=n}: The maximum number of connections in the connection pool.
* - {@code minPoolSize=n}: The minimum number of connections in the connection pool.
* - {@code waitQueueMultiple=n} : this multiplier, multiplied with the maxPoolSize setting, gives the maximum number of
* threads that may be waiting for a connection to become available from the pool. All further threads will get an
* exception right away.
* - {@code waitQueueTimeoutMS=ms}: The maximum wait time in milliseconds that a thread may wait for a connection to
* become available.
*
* Write concern configuration:
*
* - {@code safe=true|false}
*
* - {@code true}: the driver sends a getLastError command after every update to ensure that the update succeeded
* (see also {@code w} and {@code wtimeoutMS}).
* - {@code false}: the driver does not send a getLastError command after every update.
*
*
* - {@code w=wValue}
*
* - The driver adds { w : wValue } to the getLastError command. Implies {@code safe=true}.
* - wValue is typically a number, but can be any string in order to allow for specifications like
* {@code "majority"}
*
*
* - {@code wtimeoutMS=ms}
*
* - The driver adds { wtimeout : ms } to the getlasterror command. Implies {@code safe=true}.
* - Used in combination with {@code w}
*
*
*
*
* Read preference configuration:
*
* - {@code slaveOk=true|false}: Whether a driver connected to a replica set will send reads to slaves/secondaries.
* - {@code readPreference=enum}: The read preference for this connection. If set, it overrides any slaveOk value.
*
* - Enumerated values:
*
* - {@code primary}
* - {@code primaryPreferred}
* - {@code secondary}
* - {@code secondaryPreferred}
* - {@code nearest}
*
*
*
*
* - {@code readPreferenceTags=string}. A representation of a tag set as a comma-separated list of colon-separated
* key-value pairs, e.g. {@code "dc:ny,rack:1}". Spaces are stripped from beginning and end of all keys and values.
* To specify a list of tag sets, using multiple readPreferenceTags,
* e.g. {@code readPreferenceTags=dc:ny,rack:1;readPreferenceTags=dc:ny;readPreferenceTags=}
*
* - Note the empty value for the last one, which means match any secondary as a last resort.
* - Order matters when using multiple readPreferenceTags.
*
*
*
* Authentication configuration:
*
* - {@code authMechanism=MONGO-CR|GSSAPI|PLAIN|MONGODB-X509}: The authentication mechanism to use if a credential was supplied.
* The default is unspecified, in which case the client will pick the most secure mechanism available based on the sever version. For the
* GSSAPI and MONGODB-X509 mechanisms, no password is accepted, only the username.
*
* - {@code authSource=string}: The source of the authentication credentials. This is typically the database that
* the credentials have been created. The value defaults to the database specified in the path portion of the URI.
* If the database is specified in neither place, the default value is "admin". For GSSAPI, it's not necessary to specify
* a source.
*
* - {@code gssapiServiceName=string}: This option only applies to the GSSAPI mechanism and is used to alter the service name..
*
*
*
* Note: This class is a replacement for {@code MongoURI}, to be used with {@code MongoClient}. The main difference in
* behavior is that the default write concern is {@code WriteConcern.ACKNOWLEDGED}.
*
* @mongodb.driver.manual reference/connection-string Connection String URI Format
* @see MongoClientOptions for the default values for all options
* @since 2.10.0
*/
public class MongoClientURI {
private static final String PREFIX = "mongodb://";
private static final String UTF_8 = "UTF-8";
/**
* Creates a MongoURI from the given string.
*
* @param uri the URI
* @dochub connections
*/
public MongoClientURI(final String uri) {
this(uri, new MongoClientOptions.Builder());
}
/**
* Creates a MongoURI from the given URI string, and MongoClientOptions.Builder. The builder can be configured with default options,
* which may be overridden by options specified in the URI string.
*
* @param uri the URI
* @param builder a Builder
* @see com.mongodb.MongoClientURI#getOptions()
* @since 2.11.0
*/
public MongoClientURI(String uri, MongoClientOptions.Builder builder) {
try {
this.uri = uri;
if (!uri.startsWith(PREFIX))
throw new IllegalArgumentException("uri needs to start with " + PREFIX);
uri = uri.substring(PREFIX.length());
String serverPart;
String nsPart;
String optionsPart;
String userName = null;
char[] password = null;
{
int idx = uri.lastIndexOf("/");
if (idx < 0) {
if (uri.contains("?")) {
throw new IllegalArgumentException("URI contains options without trailing slash");
}
serverPart = uri;
nsPart = null;
optionsPart = "";
} else {
serverPart = uri.substring(0, idx);
nsPart = uri.substring(idx + 1);
idx = nsPart.indexOf("?");
if (idx >= 0) {
optionsPart = nsPart.substring(idx + 1);
nsPart = nsPart.substring(0, idx);
} else {
optionsPart = "";
}
}
}
{ // userName,password,hosts
List all = new LinkedList();
int idx = serverPart.indexOf("@");
if (idx > 0) {
String authPart = serverPart.substring(0, idx);
serverPart = serverPart.substring(idx + 1);
idx = authPart.indexOf(":");
if (idx == -1) {
userName = URLDecoder.decode(authPart, UTF_8);
} else {
userName = URLDecoder.decode(authPart.substring(0, idx), UTF_8);
password = URLDecoder.decode(authPart.substring(idx + 1), UTF_8).toCharArray();
}
}
Collections.addAll(all, serverPart.split(","));
Collections.sort(all);
hosts = Collections.unmodifiableList(all);
}
if (nsPart != null && nsPart.length() != 0) { // database,_collection
int idx = nsPart.indexOf(".");
if (idx < 0) {
database = nsPart;
collection = null;
} else {
database = nsPart.substring(0, idx);
collection = nsPart.substring(idx + 1);
}
} else {
database = null;
collection = null;
}
Map> optionsMap = parseOptions(optionsPart);
options = createOptions(optionsMap, builder);
credentials = createCredentials(optionsMap, userName, password, database);
warnOnUnsupportedOptions(optionsMap);
} catch (UnsupportedEncodingException e) {
throw new MongoInternalException("This should not happen", e);
}
}
static Set generalOptionsKeys = new HashSet();
static Set authKeys = new HashSet();
static Set readPreferenceKeys = new HashSet();
static Set writeConcernKeys = new HashSet();
static Set allKeys = new HashSet();
static {
generalOptionsKeys.add("minpoolsize");
generalOptionsKeys.add("maxpoolsize");
generalOptionsKeys.add("waitqueuemultiple");
generalOptionsKeys.add("waitqueuetimeoutms");
generalOptionsKeys.add("connecttimeoutms");
generalOptionsKeys.add("maxidletimems");
generalOptionsKeys.add("maxlifetimems");
generalOptionsKeys.add("sockettimeoutms");
generalOptionsKeys.add("sockettimeoutms");
generalOptionsKeys.add("autoconnectretry");
generalOptionsKeys.add("ssl");
generalOptionsKeys.add("replicaset");
readPreferenceKeys.add("slaveok");
readPreferenceKeys.add("readpreference");
readPreferenceKeys.add("readpreferencetags");
writeConcernKeys.add("safe");
writeConcernKeys.add("w");
writeConcernKeys.add("wtimeoutms");
writeConcernKeys.add("fsync");
writeConcernKeys.add("j");
authKeys.add("authmechanism");
authKeys.add("authsource");
authKeys.add("gssapiservicename");
allKeys.addAll(generalOptionsKeys);
allKeys.addAll(authKeys);
allKeys.addAll(readPreferenceKeys);
allKeys.addAll(writeConcernKeys);
}
private void warnOnUnsupportedOptions(Map> optionsMap) {
for (String key : optionsMap.keySet()) {
if (!allKeys.contains(key)) {
LOGGER.warning("Unknown or Unsupported Option '" + key + "'");
}
}
}
private MongoClientOptions createOptions(Map> optionsMap, MongoClientOptions.Builder builder) {
for (String key : generalOptionsKeys) {
String value = getLastValue(optionsMap, key);
if (value == null) {
continue;
}
if (key.equals("maxpoolsize")) {
builder.connectionsPerHost(Integer.parseInt(value));
} else if (key.equals("minpoolsize")) {
builder.minConnectionsPerHost(Integer.parseInt(value));
} else if (key.equals("maxidletimems")) {
builder.maxConnectionIdleTime(Integer.parseInt(value));
} else if (key.equals("maxlifetimems")) {
builder.maxConnectionLifeTime(Integer.parseInt(value));
} else if (key.equals("waitqueuemultiple")) {
builder.threadsAllowedToBlockForConnectionMultiplier(Integer.parseInt(value));
} else if (key.equals("waitqueuetimeoutms")) {
builder.maxWaitTime(Integer.parseInt(value));
} else if (key.equals("connecttimeoutms")) {
builder.connectTimeout(Integer.parseInt(value));
} else if (key.equals("sockettimeoutms")) {
builder.socketTimeout(Integer.parseInt(value));
} else if (key.equals("autoconnectretry")) {
builder.autoConnectRetry(_parseBoolean(value));
} else if (key.equals("replicaset")) {
builder.requiredReplicaSetName(value);
} else if (key.equals("ssl")) {
if (_parseBoolean(value)) {
builder.socketFactory(SSLSocketFactory.getDefault());
}
}
}
WriteConcern writeConcern = createWriteConcern(optionsMap);
ReadPreference readPreference = createReadPreference(optionsMap);
if (writeConcern != null) {
builder.writeConcern(writeConcern);
}
if (readPreference != null) {
builder.readPreference(readPreference);
}
return builder.build();
}
private WriteConcern createWriteConcern(final Map> optionsMap) {
Boolean safe = null;
String w = null;
int wTimeout = 0;
boolean fsync = false;
boolean journal = false;
for (String key : writeConcernKeys) {
String value = getLastValue(optionsMap, key);
if (value == null) {
continue;
}
if (key.equals("safe")) {
safe = _parseBoolean(value);
} else if (key.equals("w")) {
w = value;
} else if (key.equals("wtimeoutms")) {
wTimeout = Integer.parseInt(value);
} else if (key.equals("fsync")) {
fsync = _parseBoolean(value);
} else if (key.equals("j")) {
journal = _parseBoolean(value);
}
}
return buildWriteConcern(safe, w, wTimeout, fsync, journal);
}
private ReadPreference createReadPreference(final Map> optionsMap) {
Boolean slaveOk = null;
String readPreferenceType = null;
DBObject firstTagSet = null;
List remainingTagSets = new ArrayList();
for (String key : readPreferenceKeys) {
String value = getLastValue(optionsMap, key);
if (value == null) {
continue;
}
if (key.equals("slaveok")) {
slaveOk = _parseBoolean(value);
} else if (key.equals("readpreference")) {
readPreferenceType = value;
} else if (key.equals("readpreferencetags")) {
for (String cur : optionsMap.get(key)) {
DBObject tagSet = getTagSet(cur.trim());
if (firstTagSet == null) {
firstTagSet = tagSet;
} else {
remainingTagSets.add(tagSet);
}
}
}
}
return buildReadPreference(readPreferenceType, firstTagSet, remainingTagSets, slaveOk);
}
private MongoCredential createCredentials(Map> optionsMap, final String userName,
final char[] password, String database) {
if (userName == null) {
return null;
}
if (database == null) {
database = "admin";
}
String mechanism = null;
String authSource = database;
String gssapiServiceName = null;
for (String key : authKeys) {
String value = getLastValue(optionsMap, key);
if (value == null) {
continue;
}
if (key.equals("authmechanism")) {
mechanism = value;
} else if (key.equals("authsource")) {
authSource = value;
} else if (key.equals("gssapiservicename")) {
gssapiServiceName = value;
}
}
if (MongoCredential.GSSAPI_MECHANISM.equals(mechanism)) {
MongoCredential gssapiCredential = MongoCredential.createGSSAPICredential(userName);
if (gssapiServiceName != null) {
gssapiCredential = gssapiCredential.withMechanismProperty("SERVICE_NAME", gssapiServiceName);
}
return gssapiCredential;
}
else if (MongoCredential.PLAIN_MECHANISM.equals(mechanism)) {
return MongoCredential.createPlainCredential(userName, authSource, password);
}
else if (MongoCredential.MONGODB_CR_MECHANISM.equals(mechanism)) {
return MongoCredential.createMongoCRCredential(userName, authSource, password);
}
else if (MongoCredential.MONGODB_X509_MECHANISM.equals(mechanism)) {
return MongoCredential.createMongoX509Credential(userName);
}
else if (MongoCredential.SCRAM_SHA_1_MECHANISM.equals(mechanism)) {
return MongoCredential.createScramSha1Credential(userName, authSource, password);
}
else if (mechanism == null) {
return MongoCredential.createCredential(userName, authSource, password);
}
else {
throw new IllegalArgumentException("Unsupported authMechanism: " + mechanism);
}
}
private String getLastValue(final Map> optionsMap, final String key) {
List valueList = optionsMap.get(key);
if (valueList == null) {
return null;
}
return valueList.get(valueList.size() - 1);
}
private Map> parseOptions(String optionsPart) {
Map> optionsMap = new HashMap>();
for (String _part : optionsPart.split("&|;")) {
int idx = _part.indexOf("=");
if (idx >= 0) {
String key = _part.substring(0, idx).toLowerCase();
String value = _part.substring(idx + 1);
List valueList = optionsMap.get(key);
if (valueList == null) {
valueList = new ArrayList(1);
}
valueList.add(value);
optionsMap.put(key, valueList);
}
}
// JAVA-943 handle legacy wtimeout settings
if (optionsMap.containsKey("wtimeout") && !optionsMap.containsKey("wtimeoutms")) {
optionsMap.put("wtimeoutms", optionsMap.remove("wtimeout"));
}
return optionsMap;
}
private ReadPreference buildReadPreference(final String readPreferenceType, final DBObject firstTagSet,
final List remainingTagSets, final Boolean slaveOk) {
if (readPreferenceType != null) {
if (firstTagSet == null) {
return ReadPreference.valueOf(readPreferenceType);
} else {
return ReadPreference.valueOf(readPreferenceType, firstTagSet,
remainingTagSets.toArray(new DBObject[remainingTagSets.size()]));
}
} else if (slaveOk != null) {
if (slaveOk.equals(Boolean.TRUE)) {
return ReadPreference.secondaryPreferred();
}
}
return null;
}
private WriteConcern buildWriteConcern(final Boolean safe, final String w,
final int wTimeout, final boolean fsync, final boolean journal) {
if (w != null || wTimeout != 0 || fsync || journal) {
if (w == null) {
return new WriteConcern(1, wTimeout, fsync, journal);
} else {
try {
return new WriteConcern(Integer.parseInt(w), wTimeout, fsync, journal);
} catch (NumberFormatException e) {
return new WriteConcern(w, wTimeout, fsync, journal);
}
}
} else if (safe != null) {
if (safe) {
return WriteConcern.ACKNOWLEDGED;
} else {
return WriteConcern.UNACKNOWLEDGED;
}
}
return null;
}
private DBObject getTagSet(String tagSetString) {
DBObject tagSet = new BasicDBObject();
if (tagSetString.length() > 0) {
for (String tag : tagSetString.split(",")) {
String[] tagKeyValuePair = tag.split(":");
if (tagKeyValuePair.length != 2) {
throw new IllegalArgumentException("Bad read preference tags: " + tagSetString);
}
tagSet.put(tagKeyValuePair[0].trim(), tagKeyValuePair[1].trim());
}
}
return tagSet;
}
boolean _parseBoolean(String _in) {
String in = _in.trim();
return in != null && in.length() > 0 && (in.equals("1") || in.toLowerCase().equals("true") || in.toLowerCase()
.equals("yes"));
}
// ---------------------------------
/**
* Gets the username
*
* @return the username
*/
public String getUsername() {
return credentials != null ? credentials.getUserName() : null;
}
/**
* Gets the password
*
* @return the password
*/
public char[] getPassword() {
return credentials != null ? credentials.getPassword() : null;
}
/**
* Gets the list of hosts
*
* @return the host list
*/
public List getHosts() {
return hosts;
}
/**
* Gets the database name
*
* @return the database name
*/
public String getDatabase() {
return database;
}
/**
* Gets the collection name
*
* @return the collection name
*/
public String getCollection() {
return collection;
}
/**
* Get the unparsed URI.
*
* @return the URI
*/
public String getURI() {
return uri;
}
/**
* Gets the credentials.
*
* @return the credentials
*/
public MongoCredential getCredentials() {
return credentials;
}
/**
* Gets the options
*
* @return the MongoClientOptions based on this URI.
*/
public MongoClientOptions getOptions() {
return options;
}
// ---------------------------------
private final MongoClientOptions options;
private final MongoCredential credentials;
private final List hosts;
private final String database;
private final String collection;
private final String uri;
static final Logger LOGGER = Logger.getLogger("com.mongodb.MongoURI");
@Override
public String toString() {
return uri;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MongoClientURI that = (MongoClientURI) o;
if (!hosts.equals(that.hosts)) {
return false;
}
if (database != null ? !database.equals(that.database) : that.database != null) {
return false;
}
if (collection != null ? !collection.equals(that.collection) : that.collection != null) {
return false;
}
if (credentials != null ? !credentials.equals(that.credentials) : that.credentials != null) {
return false;
}
if (!options.equals(that.options)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = options.hashCode();
result = 31 * result + (credentials != null ? credentials.hashCode() : 0);
result = 31 * result + hosts.hashCode();
result = 31 * result + (database != null ? database.hashCode() : 0);
result = 31 * result + (collection != null ? collection.hashCode() : 0);
return result;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy