1 /* 2 * #%L 3 * MongoDbUri.java - mongodb-async-driver - Allanbank Consulting, Inc. 4 * %% 5 * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc. 6 * %% 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * #L% 19 */ 20 package com.allanbank.mongodb; 21 22 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.LinkedHashMap; 25 import java.util.List; 26 import java.util.Locale; 27 import java.util.Map; 28 import java.util.StringTokenizer; 29 30 import com.allanbank.mongodb.bson.element.UuidElement; 31 32 /** 33 * MongoDbUri provides the ability to parse a MongoDB URI into fields. 34 * <p> 35 * The set of possible options is a combination of the <a 36 * href="http://docs.mongodb.org/manual/reference/connection-string/">standard 37 * MongoDB URI options</a> and the complete set of fields supported by the 38 * {@link MongoClientConfiguration} class. The driver uses the Java beans 39 * standard to define the label for each field. 40 * </p> 41 * See the <a 42 * href="http://docs.mongodb.org/manual/reference/connection-string/">Connection 43 * String</a> documentation for information on the standard options. The 44 * following standard options are not supported by the driver: 45 * <dl> 46 * <dt>replicaSet</dt> 47 * <dd>The driver does automatic cluster type discovery so this options is not 48 * needed or used.</dd> 49 * <dt>maxIdleTimeMS</dt> 50 * <dd>The driver does not use a specific wait time. Instead the connection is 51 * considered idle when it experiences a defined number of idle ticks. A tick is 52 * defined by the {@link MongoClientConfiguration#setReadTimeout(int) 53 * readTimeout} and the number of ticks is defined by 54 * {@link MongoClientConfiguration#setMaxIdleTickCount(int) maxIdleTickCount}.</dd> 55 * <dt>waitQueueMultiple</dt> 56 * <dd>The driver does not queue requests waiting for a socket since it is 57 * asynchronous. The closest option would be the 58 * {@link MongoClientConfiguration#setMaxPendingOperationsPerConnection(int) 59 * maxPendingOperationsPerConnection} which can be used to control how 60 * aggressively the driver will apply back pressure.</dd> 61 * <dt>waitQueueTimeoutMS</dt> 62 * <dd>Similar to {@code waitQueueMultiple} this option does not make sense for 63 * an asynchronous driver.</dd> 64 * <dt>uuidRepresentation</dt> 65 * <dd>The UUID representation can only be controlled via construction. See the 66 * {@link UuidElement#UuidElement(String, byte, java.util.UUID)} and 67 * {@link UuidElement#LEGACY_UUID_SUBTTYPE}.</dd> 68 * </dl> 69 * <p> 70 * </p> 71 * <p> 72 * The current set of auto-mapped fields is listed below. See the linked 73 * documentation for details on the fields values. 74 * <ul> 75 * <li>{@link MongoClientConfiguration#setAutoDiscoverServers(boolean) 76 * autoDiscoverServers}</li> 77 * <li>{@link MongoClientConfiguration#setConnectionModel(ConnectionModel) 78 * connectionModel}</li> 79 * <li>{@link MongoClientConfiguration#setConnectTimeout(int) connectTimeout}</li> 80 * <li>{@link MongoClientConfiguration#setLockType(LockType) lockType}</li> 81 * <li>{@link MongoClientConfiguration#setMaxCachedStringEntries(int) 82 * maxCachedStringEntries}</li> 83 * <li>{@link MongoClientConfiguration#setMaxCachedStringLength(int) 84 * maxCachedStringLength}</li> 85 * <li>{@link MongoClientConfiguration#setMaxConnectionCount(int) 86 * maxConnectionCount}</li> 87 * <li>{@link MongoClientConfiguration#setMaxIdleTickCount(int) 88 * maxIdleTickCount}</li> 89 * <li> 90 * {@link MongoClientConfiguration#setMaxPendingOperationsPerConnection(int) 91 * maxPendingOperationsPerConnection}</li> 92 * <li> 93 * {@link MongoClientConfiguration#setMaxSecondaryLag(long) maxSecondaryLag}</li> 94 * <li> 95 * {@link MongoClientConfiguration#setMinConnectionCount(int) 96 * minConnectionCount}</li> 97 * <li> 98 * {@link MongoClientConfiguration#setReadTimeout(int) readTimeout}</li> 99 * <li> 100 * {@link MongoClientConfiguration#setReconnectTimeout(int) reconnectTimeout}</li> 101 * <li> 102 * {@link MongoClientConfiguration#setUsingSoKeepalive(boolean) 103 * usingSoKeepalive}</li> 104 * </ul> 105 * </p> 106 * <p> 107 * Any credentials, default read preference, and default durability will also be 108 * determined via the URI. See the {@link CredentialEditor}, 109 * {@link ReadPreferenceEditor}, and {@link DurabilityEditor} for details 110 * </p> 111 * 112 * @see <a href="http://www.mongodb.org/display/DOCS/Connections"> MongoDB 113 * Connections</a> 114 * @api.yes This class is part of the driver's API. Public and protected members 115 * will be deprecated for at least 1 non-bugfix release (version 116 * numbers are <major>.<minor>.<bugfix>) before being 117 * removed or modified. 118 * @copyright 2012-2014, Allanbank Consulting, Inc., All Rights Reserved 119 */ 120 public class MongoDbUri { 121 /** The prefix for a MongoDB URI. */ 122 public static final String MONGODB_URI_PREFIX = "mongodb://"; 123 124 /** The prefix for a MongoDB URI that uses SSL. */ 125 public static final String MONGODBS_URI_PREFIX = "mongodbs://"; 126 127 /** 128 * Tests if the {@code mongoDbUri} starts with the correct prefix to be a 129 * MongoDB URI. 130 * 131 * @param mongoDbUri 132 * The presumed MongoDB URI. 133 * @return True if the {@code mongoDbUri} starts with 134 * {@value #MONGODB_URI_PREFIX} of {@value #MONGODBS_URI_PREFIX}. 135 */ 136 public static boolean isUri(final String mongoDbUri) { 137 final String lower = mongoDbUri.toLowerCase(Locale.US); 138 139 return lower.startsWith(MONGODB_URI_PREFIX) 140 || lower.startsWith(MONGODBS_URI_PREFIX); 141 } 142 143 /** The database contained in the URI. */ 144 private final String myDatabase; 145 146 /** The hosts contained in the URI. */ 147 private final List<String> myHosts; 148 149 /** The options contained in the URI. */ 150 private final String myOptions; 151 152 /** The database contained in the URI. */ 153 private final String myOriginalText; 154 155 /** The password contained in the URI. */ 156 private final String myPassword; 157 158 /** The user name contained in the URI. */ 159 private final String myUserName; 160 161 /** 162 * Set to true if the URL uses the {@value #MONGODBS_URI_PREFIX} prefix. 163 */ 164 private final boolean myUseSsl; 165 166 /** 167 * Creates a new MongoDbUri. 168 * 169 * @param mongoDbUri 170 * The configuration for the connection to MongoDB expressed as a 171 * MongoDB URL. 172 * @throws IllegalArgumentException 173 * If the <tt>mongoDbUri</tt> is not a properly formated MongoDB 174 * style URL. 175 * 176 * @see <a href="http://www.mongodb.org/display/DOCS/Connections"> MongoDB 177 * Connections</a> 178 */ 179 public MongoDbUri(final String mongoDbUri) throws IllegalArgumentException { 180 myOriginalText = mongoDbUri; 181 182 String remaining; 183 boolean useSsl = false; 184 if (mongoDbUri == null) { 185 throw new IllegalArgumentException( 186 "The MongoDB URI cannot be null."); 187 } 188 else if (mongoDbUri.substring(0, MONGODB_URI_PREFIX.length()) 189 .equalsIgnoreCase(MONGODB_URI_PREFIX)) { 190 remaining = mongoDbUri.substring(MONGODB_URI_PREFIX.length()); 191 } 192 else if (mongoDbUri.substring(0, MONGODBS_URI_PREFIX.length()) 193 .equalsIgnoreCase(MONGODBS_URI_PREFIX)) { 194 remaining = mongoDbUri.substring(MONGODBS_URI_PREFIX.length()); 195 useSsl = true; 196 } 197 else { 198 throw new IllegalArgumentException( 199 "The MongoDB URI must start with '" + MONGODB_URI_PREFIX 200 + "'."); 201 } 202 203 String userNamePassword; 204 String hosts; 205 206 int position = remaining.indexOf('@'); 207 if (position >= 0) { 208 userNamePassword = remaining.substring(0, position); 209 remaining = remaining.substring(position + 1); 210 } 211 else { 212 userNamePassword = ""; 213 } 214 215 // Figure out the DB - if any. 216 position = remaining.indexOf('/'); 217 if (position >= 0) { 218 hosts = remaining.substring(0, position); 219 remaining = remaining.substring(position + 1); 220 221 position = remaining.indexOf('?'); 222 if (position >= 0) { 223 myDatabase = remaining.substring(0, position); 224 myOptions = remaining.substring(position + 1); 225 } 226 else { 227 myDatabase = remaining; 228 myOptions = ""; 229 } 230 } 231 else { 232 myDatabase = ""; 233 position = remaining.indexOf('?'); 234 if (position >= 0) { 235 hosts = remaining.substring(0, position); 236 myOptions = remaining.substring(position + 1); 237 } 238 else { 239 hosts = remaining; 240 myOptions = ""; 241 } 242 } 243 244 // Now the hosts. 245 final StringTokenizer tokenizer = new StringTokenizer(hosts, ","); 246 myHosts = new ArrayList<String>(tokenizer.countTokens()); 247 while (tokenizer.hasMoreTokens()) { 248 myHosts.add(tokenizer.nextToken()); 249 } 250 if (myHosts.isEmpty()) { 251 throw new IllegalArgumentException( 252 "Must provide at least 1 host to connect to."); 253 } 254 255 // User name and password? 256 if (!userNamePassword.isEmpty()) { 257 position = userNamePassword.indexOf(':'); 258 if (position >= 0) { 259 myUserName = userNamePassword.substring(0, position); 260 myPassword = userNamePassword.substring(position + 1); 261 } 262 else { 263 throw new IllegalArgumentException( 264 "The password for the user '" + userNamePassword 265 + "' must be provided."); 266 } 267 } 268 else { 269 myUserName = null; 270 myPassword = null; 271 } 272 273 myUseSsl = useSsl; 274 } 275 276 /** 277 * Returns the database contained in the URI. 278 * 279 * @return The database contained in the URI. 280 */ 281 public String getDatabase() { 282 return myDatabase; 283 } 284 285 /** 286 * Returns the hosts contained in the URI. 287 * 288 * @return The hosts contained in the URI. 289 */ 290 public List<String> getHosts() { 291 return Collections.unmodifiableList(myHosts); 292 } 293 294 /** 295 * Returns the options contained in the URI. Will never be null bu may be an 296 * empty string. 297 * 298 * @return The options contained in the URI. 299 */ 300 public String getOptions() { 301 return myOptions; 302 } 303 304 /** 305 * Returns the options contained in the URI parsed into a map of token 306 * values. 307 * 308 * @return The options contained in the URI. 309 */ 310 public Map<String, String> getParsedOptions() { 311 final Map<String, String> parsedOptions = new LinkedHashMap<String, String>(); 312 final StringTokenizer tokenizer = new StringTokenizer(getOptions(), 313 "?;&"); 314 while (tokenizer.hasMoreTokens()) { 315 String property; 316 String value; 317 318 final String propertyAndValue = tokenizer.nextToken(); 319 final int position = propertyAndValue.indexOf('='); 320 if (position >= 0) { 321 property = propertyAndValue.substring(0, position); 322 value = propertyAndValue.substring(position + 1); 323 } 324 else { 325 property = propertyAndValue; 326 value = Boolean.TRUE.toString(); 327 } 328 329 // Normalize the names.. 330 property = property.toLowerCase(Locale.US); 331 332 // Put the option at the end. 333 parsedOptions.remove(property); 334 parsedOptions.put(property, value); 335 } 336 337 return parsedOptions; 338 } 339 340 /** 341 * Returns the password contained in the URI. May be <code>null</code>. 342 * 343 * @return The password contained in the URI. 344 */ 345 public String getPassword() { 346 return myPassword; 347 } 348 349 /** 350 * Returns the user name contained in the URI. May be <code>null</code>. 351 * 352 * @return The user name contained in the URI. 353 */ 354 public String getUserName() { 355 return myUserName; 356 } 357 358 /** 359 * Returns the values for a single property. This allows for duplicate 360 * values. 361 * 362 * @param field 363 * The field to return all values for. 364 * @return The options contained in the URI. 365 */ 366 public List<String> getValuesFor(final String field) { 367 final List<String> values = new ArrayList<String>(); 368 final StringTokenizer tokenizer = new StringTokenizer(getOptions(), 369 "?;&"); 370 while (tokenizer.hasMoreTokens()) { 371 String property; 372 String value; 373 374 final String propertyAndValue = tokenizer.nextToken(); 375 final int position = propertyAndValue.indexOf('='); 376 if (position >= 0) { 377 property = propertyAndValue.substring(0, position); 378 value = propertyAndValue.substring(position + 1); 379 } 380 else { 381 property = propertyAndValue; 382 value = Boolean.TRUE.toString(); 383 } 384 385 if (field.equalsIgnoreCase(property)) { 386 values.add(value); 387 } 388 } 389 390 return values; 391 } 392 393 /** 394 * Returns true if the URL uses the {@value #MONGODBS_URI_PREFIX} prefix. 395 * 396 * @return True if the URL uses the {@value #MONGODBS_URI_PREFIX} prefix. 397 */ 398 public boolean isUseSsl() { 399 return myUseSsl; 400 } 401 402 /** 403 * {@inheritDoc} 404 * <p> 405 * Overridden to return the string used to construct the URI. 406 * </p> 407 */ 408 @Override 409 public String toString() { 410 return myOriginalText; 411 } 412 413 }