Coverage Report - com.allanbank.mongodb.MongoDbUri
 
Classes in this File Line Coverage Branch Coverage Complexity
MongoDbUri
96%
84/87
94%
34/36
2.909
 
 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 &lt;major&gt;.&lt;minor&gt;.&lt;bugfix&gt;) 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  100
         final String lower = mongoDbUri.toLowerCase(Locale.US);
 138  
 
 139  100
         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  142
     public MongoDbUri(final String mongoDbUri) throws IllegalArgumentException {
 180  142
         myOriginalText = mongoDbUri;
 181  
 
 182  
         String remaining;
 183  142
         boolean useSsl = false;
 184  142
         if (mongoDbUri == null) {
 185  2
             throw new IllegalArgumentException(
 186  
                     "The MongoDB URI cannot be null.");
 187  
         }
 188  140
         else if (mongoDbUri.substring(0, MONGODB_URI_PREFIX.length())
 189  
                 .equalsIgnoreCase(MONGODB_URI_PREFIX)) {
 190  125
             remaining = mongoDbUri.substring(MONGODB_URI_PREFIX.length());
 191  
         }
 192  15
         else if (mongoDbUri.substring(0, MONGODBS_URI_PREFIX.length())
 193  
                 .equalsIgnoreCase(MONGODBS_URI_PREFIX)) {
 194  9
             remaining = mongoDbUri.substring(MONGODBS_URI_PREFIX.length());
 195  9
             useSsl = true;
 196  
         }
 197  
         else {
 198  6
             throw new IllegalArgumentException(
 199  
                     "The MongoDB URI must start with '" + MONGODB_URI_PREFIX
 200  
                             + "'.");
 201  
         }
 202  
 
 203  
         String userNamePassword;
 204  
         String hosts;
 205  
 
 206  134
         int position = remaining.indexOf('@');
 207  134
         if (position >= 0) {
 208  21
             userNamePassword = remaining.substring(0, position);
 209  21
             remaining = remaining.substring(position + 1);
 210  
         }
 211  
         else {
 212  113
             userNamePassword = "";
 213  
         }
 214  
 
 215  
         // Figure out the DB - if any.
 216  134
         position = remaining.indexOf('/');
 217  134
         if (position >= 0) {
 218  104
             hosts = remaining.substring(0, position);
 219  104
             remaining = remaining.substring(position + 1);
 220  
 
 221  104
             position = remaining.indexOf('?');
 222  104
             if (position >= 0) {
 223  84
                 myDatabase = remaining.substring(0, position);
 224  84
                 myOptions = remaining.substring(position + 1);
 225  
             }
 226  
             else {
 227  20
                 myDatabase = remaining;
 228  20
                 myOptions = "";
 229  
             }
 230  
         }
 231  
         else {
 232  30
             myDatabase = "";
 233  30
             position = remaining.indexOf('?');
 234  30
             if (position >= 0) {
 235  1
                 hosts = remaining.substring(0, position);
 236  1
                 myOptions = remaining.substring(position + 1);
 237  
             }
 238  
             else {
 239  29
                 hosts = remaining;
 240  29
                 myOptions = "";
 241  
             }
 242  
         }
 243  
 
 244  
         // Now the hosts.
 245  134
         final StringTokenizer tokenizer = new StringTokenizer(hosts, ",");
 246  134
         myHosts = new ArrayList<String>(tokenizer.countTokens());
 247  269
         while (tokenizer.hasMoreTokens()) {
 248  135
             myHosts.add(tokenizer.nextToken());
 249  
         }
 250  134
         if (myHosts.isEmpty()) {
 251  3
             throw new IllegalArgumentException(
 252  
                     "Must provide at least 1 host to connect to.");
 253  
         }
 254  
 
 255  
         // User name and password?
 256  131
         if (!userNamePassword.isEmpty()) {
 257  21
             position = userNamePassword.indexOf(':');
 258  21
             if (position >= 0) {
 259  21
                 myUserName = userNamePassword.substring(0, position);
 260  21
                 myPassword = userNamePassword.substring(position + 1);
 261  
             }
 262  
             else {
 263  0
                 throw new IllegalArgumentException(
 264  
                         "The password for the user '" + userNamePassword
 265  
                                 + "' must be provided.");
 266  
             }
 267  
         }
 268  
         else {
 269  110
             myUserName = null;
 270  110
             myPassword = null;
 271  
         }
 272  
 
 273  131
         myUseSsl = useSsl;
 274  131
     }
 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  96
         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  47
         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  145
         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  131
         final Map<String, String> parsedOptions = new LinkedHashMap<String, String>();
 312  131
         final StringTokenizer tokenizer = new StringTokenizer(getOptions(),
 313  
                 "?;&");
 314  343
         while (tokenizer.hasMoreTokens()) {
 315  
             String property;
 316  
             String value;
 317  
 
 318  212
             final String propertyAndValue = tokenizer.nextToken();
 319  212
             final int position = propertyAndValue.indexOf('=');
 320  212
             if (position >= 0) {
 321  192
                 property = propertyAndValue.substring(0, position);
 322  192
                 value = propertyAndValue.substring(position + 1);
 323  
             }
 324  
             else {
 325  20
                 property = propertyAndValue;
 326  20
                 value = Boolean.TRUE.toString();
 327  
             }
 328  
 
 329  
             // Normalize the names..
 330  212
             property = property.toLowerCase(Locale.US);
 331  
 
 332  
             // Put the option at the end.
 333  212
             parsedOptions.remove(property);
 334  212
             parsedOptions.put(property, value);
 335  212
         }
 336  
 
 337  131
         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  15
         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  72
         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  14
         final List<String> values = new ArrayList<String>();
 368  14
         final StringTokenizer tokenizer = new StringTokenizer(getOptions(),
 369  
                 "?;&");
 370  33
         while (tokenizer.hasMoreTokens()) {
 371  
             String property;
 372  
             String value;
 373  
 
 374  19
             final String propertyAndValue = tokenizer.nextToken();
 375  19
             final int position = propertyAndValue.indexOf('=');
 376  19
             if (position >= 0) {
 377  19
                 property = propertyAndValue.substring(0, position);
 378  19
                 value = propertyAndValue.substring(position + 1);
 379  
             }
 380  
             else {
 381  0
                 property = propertyAndValue;
 382  0
                 value = Boolean.TRUE.toString();
 383  
             }
 384  
 
 385  19
             if (field.equalsIgnoreCase(property)) {
 386  3
                 values.add(value);
 387  
             }
 388  19
         }
 389  
 
 390  14
         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  50
         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  63
         return myOriginalText;
 411  
     }
 412  
 
 413  
 }