View Javadoc
1   /*
2    * #%L
3    * MongoClientConfiguration.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.beans.BeanInfo;
23  import java.beans.IntrospectionException;
24  import java.beans.Introspector;
25  import java.beans.PropertyChangeListener;
26  import java.beans.PropertyChangeSupport;
27  import java.beans.PropertyDescriptor;
28  import java.beans.PropertyEditor;
29  import java.beans.PropertyEditorManager;
30  import java.io.IOException;
31  import java.io.Serializable;
32  import java.lang.reflect.InvocationTargetException;
33  import java.lang.reflect.Method;
34  import java.net.InetSocketAddress;
35  import java.net.Socket;
36  import java.nio.charset.Charset;
37  import java.security.MessageDigest;
38  import java.security.NoSuchAlgorithmException;
39  import java.util.ArrayList;
40  import java.util.Collection;
41  import java.util.Collections;
42  import java.util.HashMap;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Locale;
46  import java.util.Map;
47  import java.util.concurrent.ConcurrentHashMap;
48  import java.util.concurrent.Executor;
49  import java.util.concurrent.Executors;
50  import java.util.concurrent.ThreadFactory;
51  import java.util.concurrent.TimeUnit;
52  
53  import javax.net.SocketFactory;
54  import javax.net.ssl.SSLSocketFactory;
55  
56  import com.allanbank.mongodb.bson.io.StringEncoderCache;
57  import com.allanbank.mongodb.error.MongoDbAuthenticationException;
58  import com.allanbank.mongodb.util.IOUtils;
59  import com.allanbank.mongodb.util.ServerNameUtils;
60  import com.allanbank.mongodb.util.log.Log;
61  import com.allanbank.mongodb.util.log.LogFactory;
62  
63  /**
64   * Contains the configuration for the connection(s) to the MongoDB servers.
65   * 
66   * @api.yes This class is part of the driver's API. Public and protected members
67   *          will be deprecated for at least 1 non-bugfix release (version
68   *          numbers are <major>.<minor>.<bugfix>) before being
69   *          removed or modified.
70   * @copyright 2011-2014, Allanbank Consulting, Inc., All Rights Reserved
71   */
72  public class MongoClientConfiguration implements Cloneable, Serializable {
73  
74      /** The name of the administration database. */
75      public static final String ADMIN_DB_NAME = "admin";
76  
77      /** The default database. */
78      public static final String DEFAULT_DB_NAME = "local";
79  
80      /** The ASCII character encoding. */
81      public static final Charset UTF8 = Charset.forName("UTF-8");
82  
83      /**
84       * The default maximum number of strings to keep in the string encoder and
85       * decoder cache.
86       */
87      protected static final int DEFAULT_MAX_STRING_CACHE_ENTRIES = 1024;
88  
89      /** The default maximum length byte array / string to cache. */
90      protected static final int DEFAULT_MAX_STRING_CACHE_LENGTH = StringEncoderCache.DEFAULT_MAX_CACHE_LENGTH;
91  
92      /** The logger for the {@link MongoClientConfiguration}. */
93      private static final Log LOG = LogFactory
94              .getLog(MongoClientConfiguration.class);
95  
96      /** The serialization version for the class. */
97      private static final long serialVersionUID = 2964127883934086500L;
98  
99      /**
100      * Determines if additional servers are auto discovered or if connections
101      * are limited to the ones manually configured.
102      * <p>
103      * Defaults to true, e.g., auto-discover.
104      * </p>
105      */
106     private boolean myAutoDiscoverServers = true;
107 
108     /**
109      * Determines the model the driver uses for managing connections.
110      * <p>
111      * Defaults to {@link ConnectionModel#RECEIVER_THREAD}.
112      * </p>
113      */
114     private ConnectionModel myConnectionModel = ConnectionModel.RECEIVER_THREAD;
115 
116     /**
117      * Determines how long to wait (in milliseconds) for a socket connection to
118      * complete.
119      * <p>
120      * Defaults to 0 or forever.
121      * </p>
122      */
123     private int myConnectTimeout = 0;
124 
125     /**
126      * The credentials for the user. This should be final but for support for
127      * the clone() method.
128      */
129     private ConcurrentHashMap<String, Credential> myCredentials;
130 
131     /**
132      * The default database for the connection. This is used as the database to
133      * authenticate against if the user is not an administrative user.
134      * <p>
135      * Defaults to {@value #DEFAULT_DB_NAME}.
136      * </p>
137      */
138     private String myDefaultDatabase = DEFAULT_DB_NAME;
139 
140     /**
141      * The default durability for write operations on the server.
142      * <p>
143      * Defaults to {@link Durability#ACK}.
144      * </p>
145      */
146     private Durability myDefaultDurability = Durability.ACK;
147 
148     /**
149      * The default read preference for a query.
150      * <p>
151      * Defaults to {@link ReadPreference#PRIMARY}.
152      * </p>
153      */
154     private ReadPreference myDefaultReadPreference = ReadPreference.PRIMARY;
155 
156     /** The executor for responses from the database. */
157     private transient Executor myExecutor = null;
158 
159     /**
160      * The legacy credentials created via {@link #authenticate(String, String)}
161      * and {@link #setDefaultDatabase(String)}.
162      */
163     private Credential myLegacyCredential;
164 
165     /**
166      * Determines the type of hand off lock to use between threads in the core
167      * of the driver.
168      * <p>
169      * Defaults to {@link LockType#MUTEX}.
170      * </p>
171      */
172     private LockType myLockType = LockType.MUTEX;
173 
174     /**
175      * The maximum number of strings that may have their encoded form cached.
176      * <p>
177      * Defaults to {@value #DEFAULT_MAX_STRING_CACHE_ENTRIES}.
178      * </p>
179      */
180     private int myMaxCachedStringEntries = DEFAULT_MAX_STRING_CACHE_ENTRIES;
181 
182     /**
183      * The maximum length for a string that the stream is allowed to cache.This
184      * can be used to stop a single long string from pushing useful values out
185      * of the cache. Setting this value to zero turns off the caching.
186      * <p>
187      * Defaults to {@value #DEFAULT_MAX_STRING_CACHE_LENGTH}.
188      * </p>
189      */
190     private int myMaxCachedStringLength = DEFAULT_MAX_STRING_CACHE_LENGTH;
191 
192     /**
193      * Determines the maximum number of connections to use.
194      * <p>
195      * Defaults to 3.
196      * </p>
197      * <p>
198      * <em>Note:</em> In the case of connecting to a replica set this setting
199      * limits the number of connections to the primary server. The driver will
200      * create single connections to the secondary servers if queries are issued
201      * with a {@link ReadPreference} other than {@link ReadPreference#PRIMARY}.
202      * </p>
203      */
204     private int myMaxConnectionCount = 3;
205 
206     /**
207      * Determines the number of read timeouts (a tick) before closing the
208      * connection.
209      * <p>
210      * Defaults to {@link Integer#MAX_VALUE}.
211      * </p>
212      */
213     private int myMaxIdleTickCount = Integer.MAX_VALUE;
214 
215     /**
216      * Determines the maximum number of pending operations to allow per
217      * connection. The higher the value the more "asynchronous" the driver can
218      * be but risks more operations being in an unknown state on a connection
219      * error. When the connection has this many pending requests additional
220      * requests will block.
221      * <p>
222      * Defaults to 1024.
223      * </p>
224      * <p>
225      * <em>Note:</em> In the case of an connection error it is impossible to
226      * determine which pending operations completed and which did not.
227      * </p>
228      */
229     private int myMaxPendingOperationsPerConnection = 1024;
230 
231     /**
232      * Determines the maximum number of milliseconds that a secondary can be
233      * behind the primary before they will be excluded from being used for
234      * queries on secondaries.
235      * <p>
236      * Defaults to 5 minutes (300,000).
237      * </p>
238      */
239     private long myMaxSecondaryLag = TimeUnit.MINUTES.toMillis(5);
240 
241     /**
242      * Determines the minimum number of connections to try and keep open.
243      * <p>
244      * Defaults to 0.
245      * </p>
246      */
247     private int myMinConnectionCount = 0;
248 
249     /**
250      * Support for emitting property change events to listeners. Not final for
251      * clone.
252      */
253     private PropertyChangeSupport myPropSupport;
254 
255     /**
256      * Determines how long to wait (in milliseconds) for a socket read to
257      * complete.
258      * <p>
259      * Defaults to 0 or never.
260      * </p>
261      */
262     private int myReadTimeout = 0;
263 
264     /**
265      * Determines how long to wait (in milliseconds) for a broken connection to
266      * reconnect.
267      * <p>
268      * Defaults to 0 or forever.
269      * </p>
270      */
271     private int myReconnectTimeout = 0;
272 
273     /**
274      * The list of servers to initially attempt to connect to. This should be
275      * final but for support for the clone() method.
276      */
277     private List<InetSocketAddress> myServers = new ArrayList<InetSocketAddress>();
278 
279     /** The socket factory for creating sockets. */
280     private transient SocketFactory mySocketFactory = null;
281 
282     /** The factory for creating threads to handle connections. */
283     private transient ThreadFactory myThreadFactory = null;
284 
285     /**
286      * Determines if the {@link java.net.Socket#setKeepAlive(boolean)
287      * SO_KEEPALIVE} socket option is set.
288      * <p>
289      * Defaults to true, e.g., use SO_KEEPALIVE.
290      * </p>
291      */
292     private boolean myUsingSoKeepalive = true;
293 
294     /**
295      * Creates a new MongoClientConfiguration.
296      */
297     public MongoClientConfiguration() {
298         super();
299 
300         myThreadFactory = Executors.defaultThreadFactory();
301         myCredentials = new ConcurrentHashMap<String, Credential>();
302         myPropSupport = new PropertyChangeSupport(this);
303     }
304 
305     /**
306      * Creates a new MongoClientConfiguration.
307      * 
308      * @param servers
309      *            The initial set of servers to connect to.
310      */
311     public MongoClientConfiguration(final InetSocketAddress... servers) {
312         this();
313 
314         for (final InetSocketAddress server : servers) {
315             addServer(server);
316         }
317     }
318 
319     /**
320      * Creates a new MongoClientConfiguration.
321      * 
322      * @param other
323      *            The configuration to copy.
324      */
325     public MongoClientConfiguration(final MongoClientConfiguration other) {
326         this();
327 
328         myAutoDiscoverServers = other.isAutoDiscoverServers();
329         myConnectionModel = other.getConnectionModel();
330         myConnectTimeout = other.getConnectTimeout();
331         myDefaultDatabase = other.getDefaultDatabase();
332         myDefaultDurability = other.getDefaultDurability();
333         myDefaultReadPreference = other.getDefaultReadPreference();
334         myExecutor = other.getExecutor();
335         myLockType = other.getLockType();
336         myMaxCachedStringEntries = other.getMaxCachedStringEntries();
337         myMaxCachedStringLength = other.getMaxCachedStringLength();
338         myMaxConnectionCount = other.getMaxConnectionCount();
339         myMaxIdleTickCount = other.getMaxIdleTickCount();
340         myMaxPendingOperationsPerConnection = other
341                 .getMaxPendingOperationsPerConnection();
342         myMaxSecondaryLag = other.getMaxSecondaryLag();
343         myMinConnectionCount = other.getMinConnectionCount();
344         myReadTimeout = other.getReadTimeout();
345         myReconnectTimeout = other.getReconnectTimeout();
346         mySocketFactory = other.getSocketFactory();
347         myThreadFactory = other.getThreadFactory();
348         myUsingSoKeepalive = other.isUsingSoKeepalive();
349 
350         for (final Credential credential : other.getCredentials()) {
351             addCredential(credential);
352         }
353         for (final InetSocketAddress addr : other.getServerAddresses()) {
354             addServer(addr);
355         }
356     }
357 
358     /**
359      * Creates a new {@link MongoClientConfiguration} instance using a MongoDB
360      * style URL to initialize its state. Further configuration is possible once
361      * the {@link MongoClientConfiguration} has been instantiated.
362      * 
363      * @param mongoDbUri
364      *            The configuration for the connection to MongoDB expressed as a
365      *            MongoDB URL.
366      * @throws IllegalArgumentException
367      *             If the <tt>mongoDbUri</tt> is not a properly formated MongoDB
368      *             style URL.
369      * 
370      * @see <a href="http://www.mongodb.org/display/DOCS/Connections"> MongoDB
371      *      Connections</a>
372      */
373     public MongoClientConfiguration(final MongoDbUri mongoDbUri)
374             throws IllegalArgumentException {
375         this(mongoDbUri, Durability.ACK);
376     }
377 
378     /**
379      * Creates a new {@link MongoClientConfiguration} instance using a MongoDB
380      * style URL to initialize its state. Further configuration is possible once
381      * the {@link MongoClientConfiguration} has been instantiated.
382      * 
383      * @param mongoDbUri
384      *            The configuration for the connection to MongoDB expressed as a
385      *            MongoDB URL.
386      * @throws IllegalArgumentException
387      *             If the <tt>mongoDbUri</tt> is not a properly formated MongoDB
388      *             style URL.
389      * 
390      * @see <a href="http://www.mongodb.org/display/DOCS/Connections"> MongoDB
391      *      Connections</a>
392      */
393     public MongoClientConfiguration(final String mongoDbUri)
394             throws IllegalArgumentException {
395         this(new MongoDbUri(mongoDbUri));
396     }
397 
398     /**
399      * Creates a new {@link MongoClientConfiguration} instance using a MongoDB
400      * style URL to initialize its state. Further configuration is possible once
401      * the {@link MongoClientConfiguration} has been instantiated.
402      * 
403      * @param mongoDbUri
404      *            The configuration for the connection to MongoDB expressed as a
405      *            MongoDB URL.
406      * @param defaultDurability
407      *            The default durability.
408      * @throws IllegalArgumentException
409      *             If the <tt>mongoDbUri</tt> is not a properly formated MongoDB
410      *             style URL.
411      * 
412      * @see <a href="http://www.mongodb.org/display/DOCS/Connections"> MongoDB
413      *      Connections</a>
414      */
415     protected MongoClientConfiguration(final MongoDbUri mongoDbUri,
416             final Durability defaultDurability) throws IllegalArgumentException {
417         this();
418 
419         myDefaultDurability = defaultDurability;
420         for (final String host : mongoDbUri.getHosts()) {
421             addServer(host);
422         }
423         if (myServers.isEmpty()) {
424             throw new IllegalArgumentException(
425                     "Must provide at least 1 host to connect to.");
426         }
427         if (!mongoDbUri.getDatabase().isEmpty()) {
428             setDefaultDatabase(mongoDbUri.getDatabase());
429         }
430 
431         final Map<String, String> parameters = mongoDbUri.getParsedOptions();
432         final Map<String, String> renames = new HashMap<String, String>();
433 
434         // Renames for the standard names.
435         final Iterator<Map.Entry<String, String>> iter = parameters.entrySet()
436                 .iterator();
437         while (iter.hasNext()) {
438             final Map.Entry<String, String> entry = iter.next();
439             final String name = entry.getKey();
440             if ("sockettimeoutms".equals(entry.getKey())) {
441                 renames.put("readtimeout", entry.getValue());
442                 iter.remove();
443             }
444             else if ("usesokeepalive".equals(name)) {
445                 renames.put("usingsokeepalive", entry.getValue());
446                 iter.remove();
447             }
448             else if ("minpoolsize".equals(name)) {
449                 renames.put("minconnectioncount", entry.getValue());
450                 iter.remove();
451             }
452             else if ("maxpoolsize".equals(name)) {
453                 renames.put("maxconnectioncount", entry.getValue());
454                 iter.remove();
455             }
456             else if (name.endsWith("ms")) {
457                 final String newName = name.substring(0,
458                         name.length() - "ms".length());
459                 renames.put(newName, entry.getValue());
460                 iter.remove();
461             }
462         }
463         parameters.putAll(renames);
464 
465         // Remove the parameters for the Credentials and durability and use the
466         // full URI for the text to feed to the property editor.
467         parameters.keySet().removeAll(CredentialEditor.MONGODB_URI_FIELDS);
468         parameters.put("credentials", mongoDbUri.toString());
469 
470         if (parameters.keySet().removeAll(DurabilityEditor.MONGODB_URI_FIELDS)) {
471             parameters.put("defaultdurability", mongoDbUri.toString());
472         }
473         if (parameters.keySet().removeAll(
474                 ReadPreferenceEditor.MONGODB_URI_FIELDS)) {
475             parameters.put("defaultreadpreference", mongoDbUri.toString());
476         }
477 
478         // Special handling for the SSL parameter.
479         final String sslValue = parameters.remove("ssl");
480         if (mongoDbUri.isUseSsl() || (sslValue != null)) {
481             if (mongoDbUri.isUseSsl() || Boolean.parseBoolean(sslValue)) {
482                 try {
483                     setSocketFactory((SocketFactory) Class
484                             .forName(
485                                     "com.allanbank.mongodb.extensions.tls.TlsSocketFactory")
486                             .newInstance());
487                 }
488                 catch (final Throwable error) {
489                     setSocketFactory(SSLSocketFactory.getDefault());
490                     LOG.warn("Using the JVM default SSL Socket Factory. "
491                             + "This may allow man-in-the-middle attacks. "
492                             + "See http://www.allanbank.com/mongodb-async-driver/userguide/tls.html");
493                 }
494             }
495             else {
496                 setSocketFactory(null);
497             }
498         }
499 
500         try {
501             final BeanInfo info = Introspector
502                     .getBeanInfo(MongoClientConfiguration.class);
503             final PropertyDescriptor[] descriptors = info
504                     .getPropertyDescriptors();
505             final Map<String, PropertyDescriptor> descriptorsByName = new HashMap<String, PropertyDescriptor>();
506             for (final PropertyDescriptor descriptor : descriptors) {
507                 descriptorsByName
508                         .put(descriptor.getName().toLowerCase(Locale.US),
509                                 descriptor);
510             }
511 
512             for (final Map.Entry<String, String> param : parameters.entrySet()) {
513                 final String propName = param.getKey();
514                 final String propValue = param.getValue();
515 
516                 final PropertyDescriptor descriptor = descriptorsByName
517                         .get(propName);
518                 if (descriptor != null) {
519                     try {
520                         updateFieldValue(descriptor, propName, propValue);
521                     }
522                     catch (final NumberFormatException nfe) {
523                         throw new IllegalArgumentException("The '" + propName
524                                 + "' parameter must have a numeric value not '"
525                                 + propValue + "'.", nfe);
526                     }
527                     catch (final Exception e) {
528                         throw new IllegalArgumentException("The '" + propName
529                                 + "' parameter's editor could not be set '"
530                                 + propValue + "'.", e);
531                     }
532                 }
533                 else if ("uuidrepresentation".equals(propName)) {
534                     LOG.info("Changing the UUID representation is not supported.");
535                 }
536                 else if ("replicaset".equals(propName)) {
537                     LOG.info("Not validating the replica set name is '{}'.",
538                             propValue);
539                 }
540                 else {
541                     LOG.info("Unknown property '{}' and value '{}'.", propName,
542                             propValue);
543                 }
544             }
545         }
546         catch (final IntrospectionException e) {
547             throw new IllegalArgumentException(
548                     "Could not introspect on the MongoClientConfiguration.");
549         }
550     }
551 
552     /**
553      * Adds the specified credentials to the configuration.
554      * 
555      * @param credentials
556      *            The credentials to use when accessing the MongoDB server.
557      * @throws IllegalArgumentException
558      *             If the credentials refer to an unknown authentication type or
559      *             the configuration already has a set of credentials for the
560      *             credentials specified database.
561      */
562     public void addCredential(final Credential credentials)
563             throws IllegalArgumentException {
564         final List<Credential> old = new ArrayList<Credential>(
565                 myCredentials.values());
566 
567         try {
568             credentials.loadAuthenticator();
569 
570             final Credential previous = myCredentials.putIfAbsent(
571                     credentials.getDatabase(), credentials);
572             if (previous != null) {
573                 throw new IllegalArgumentException(
574                         "There can only be one set of credentials for each database.");
575             }
576         }
577         catch (final ClassNotFoundException cnfe) {
578             throw new IllegalArgumentException(
579                     "Could not load the credentials authenticator.", cnfe);
580         }
581         catch (final InstantiationException ie) {
582             throw new IllegalArgumentException(
583                     "Could not load the credentials authenticator.", ie);
584         }
585         catch (final IllegalAccessException iae) {
586             throw new IllegalArgumentException(
587                     "Could not load the credentials authenticator.", iae);
588         }
589 
590         myPropSupport.firePropertyChange("credentials", old,
591                 new ArrayList<Credential>(myCredentials.values()));
592     }
593 
594     /**
595      * Adds the specified credentials to the configuration.
596      * 
597      * @param credentials
598      *            The credentials to use when accessing the MongoDB server.
599      * @throws IllegalArgumentException
600      *             If the credentials refer to an unknown authentication type or
601      *             the configuration already has a set of credentials for the
602      *             credentials specified database.
603      */
604     public void addCredential(final Credential.Builder credentials)
605             throws IllegalArgumentException {
606         addCredential(credentials.build());
607     }
608 
609     /**
610      * Add a {@link PropertyChangeListener} from the configuration. The listener
611      * will receive notification of all changes to the configuration.
612      * 
613      * @param listener
614      *            The {@link PropertyChangeListener} to be added
615      */
616     public synchronized void addPropertyChangeListener(
617             final PropertyChangeListener listener) {
618         myPropSupport.addPropertyChangeListener(listener);
619     }
620 
621     /**
622      * Add a {@link PropertyChangeListener} from the configuration. The listener
623      * will receive notification of all changes to the configuration's specified
624      * property.
625      * 
626      * @param propertyName
627      *            The name of the property to listen on.
628      * @param listener
629      *            The {@link PropertyChangeListener} to be added
630      */
631 
632     public synchronized void addPropertyChangeListener(
633             final String propertyName, final PropertyChangeListener listener) {
634         myPropSupport.addPropertyChangeListener(propertyName, listener);
635     }
636 
637     /**
638      * Adds a server to initially attempt to connect to.
639      * 
640      * @param server
641      *            The server to add.
642      */
643     public void addServer(final InetSocketAddress server) {
644         final List<InetSocketAddress> old = new ArrayList<InetSocketAddress>(
645                 myServers);
646 
647         myServers.add(server);
648 
649         myPropSupport.firePropertyChange("servers", old,
650                 Collections.unmodifiableList(myServers));
651     }
652 
653     /**
654      * Adds a server to initially attempt to connect to.
655      * 
656      * @param server
657      *            The server to add.
658      */
659     public void addServer(final String server) {
660         addServer(ServerNameUtils.parse(server));
661     }
662 
663     /**
664      * Sets up the instance to authenticate with the MongoDB servers. This
665      * should be done before using this configuration to instantiate a
666      * {@link Mongo} instance.
667      * 
668      * @param userName
669      *            The user name.
670      * @param password
671      *            the password.
672      * @throws MongoDbAuthenticationException
673      *             On a failure initializing the authentication information.
674      * @deprecated Replaced with the more general {@link Credential} capability.
675      *             Will be removed after the 1.3.0 release.
676      */
677     @Deprecated
678     public void authenticate(final String userName, final String password)
679             throws MongoDbAuthenticationException {
680         if (myLegacyCredential != null) {
681             myCredentials.remove(myLegacyCredential.getDatabase());
682         }
683         myLegacyCredential = Credential.builder().userName(userName)
684                 .password(password.toCharArray())
685                 .database(getDefaultDatabase()).mongodbCR().build();
686 
687         addCredential(myLegacyCredential);
688     }
689 
690     /**
691      * Sets up the instance to authenticate with the MongoDB servers. This
692      * should be done before using this configuration to instantiate a
693      * {@link Mongo} instance.
694      * 
695      * @param userName
696      *            The user name.
697      * @param password
698      *            the password.
699      * @throws MongoDbAuthenticationException
700      *             On a failure initializing the authentication information.
701      * @deprecated Replaced with the more general {@link Credential} capability.
702      *             Will be removed after the 1.3.0 release.
703      */
704     @Deprecated
705     public void authenticateAsAdmin(final String userName, final String password)
706             throws MongoDbAuthenticationException {
707 
708         addCredential(Credential.builder().userName(userName)
709                 .password(password.toCharArray()).database(ADMIN_DB_NAME)
710                 .mongodbCR().build());
711     }
712 
713     /**
714      * Creates a copy of this MongoClientConfiguration.
715      * <p>
716      * Note: This is not a traditional clone to ensure a deep copy of all
717      * information.
718      * </p>
719      */
720     @Override
721     public MongoClientConfiguration clone() {
722         MongoClientConfiguration clone = null;
723         try {
724             clone = (MongoClientConfiguration) super.clone();
725 
726             clone.myCredentials = new ConcurrentHashMap<String, Credential>();
727             for (final Credential credential : getCredentials()) {
728                 clone.addCredential(credential);
729             }
730 
731             clone.myServers = new ArrayList<InetSocketAddress>();
732             for (final InetSocketAddress addr : getServerAddresses()) {
733                 clone.addServer(addr);
734             }
735 
736             clone.myPropSupport = new PropertyChangeSupport(clone);
737         }
738         catch (final CloneNotSupportedException shouldNotHappen) {
739             clone = new MongoClientConfiguration(this);
740         }
741         return clone;
742     }
743 
744     /**
745      * Returns the model the driver uses for managing connections.
746      * <p>
747      * Defaults to {@link ConnectionModel#RECEIVER_THREAD}.
748      * </p>
749      * 
750      * @return The model used for managing connections.
751      */
752     public ConnectionModel getConnectionModel() {
753         return myConnectionModel;
754     }
755 
756     /**
757      * Returns how long to wait (in milliseconds) for a socket connection to
758      * complete.
759      * <p>
760      * Defaults to 0 or forever.
761      * </p>
762      * 
763      * @return The time to wait (in milliseconds) for a socket connection to
764      *         complete.
765      */
766     public int getConnectTimeout() {
767         return myConnectTimeout;
768     }
769 
770     /**
771      * Returns the map of database names to credentials to use to access that
772      * database on the server.
773      * 
774      * @return The map of database names to credentials to use to access that
775      *         database on the server.
776      */
777     public Collection<Credential> getCredentials() {
778         return Collections.unmodifiableCollection(myCredentials.values());
779     }
780 
781     /**
782      * Returns the default database for the connection.
783      * <p>
784      * This is used as the database to authenticate against if the user is not
785      * an administrative user.
786      * </p>
787      * <p>
788      * Defaults to {@value #DEFAULT_DB_NAME}.
789      * </p>
790      * 
791      * @return The default database value.
792      * @deprecated Replaced with the more general {@link Credential} capability.
793      *             Will be removed after the 1.3.0 release.
794      */
795     @Deprecated
796     public String getDefaultDatabase() {
797         return myDefaultDatabase;
798     }
799 
800     /**
801      * Returns the default durability for write operations on the server.
802      * <p>
803      * Defaults to {@link Durability#ACK}.
804      * </p>
805      * 
806      * @return The default durability for write operations on the server.
807      */
808     public Durability getDefaultDurability() {
809         return myDefaultDurability;
810     }
811 
812     /**
813      * Returns the default read preference for a query.
814      * <p>
815      * Defaults to {@link ReadPreference#PRIMARY}.
816      * </p>
817      * 
818      * @return The default read preference for a query.
819      */
820     public ReadPreference getDefaultReadPreference() {
821         return myDefaultReadPreference;
822     }
823 
824     /**
825      * Returns the executor to use when processing responses from the server.
826      * <p>
827      * By default the executor is <code>null</code> which will cause the reply
828      * handling to execute on the socket's receive thread.
829      * </p>
830      * <p>
831      * Care should be taken to ensure that the executor does not drop requests.
832      * This implies that the
833      * {@link java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy} or
834      * similar should be used as the
835      * {@link java.util.concurrent.RejectedExecutionHandler}.
836      * </p>
837      * 
838      * @return The executor value.
839      */
840     public Executor getExecutor() {
841         return myExecutor;
842     }
843 
844     /**
845      * Returns the type of hand off lock to use between threads in the core of
846      * the driver.
847      * <p>
848      * Defaults to {@link LockType#MUTEX}.
849      * </p>
850      * 
851      * @return The type of hand off lock used.
852      */
853     public LockType getLockType() {
854         return myLockType;
855     }
856 
857     /**
858      * Returns the maximum number of strings that may have their encoded form
859      * cached.
860      * <p>
861      * Defaults to {@value #DEFAULT_MAX_STRING_CACHE_ENTRIES}.
862      * </p>
863      * <p>
864      * Note: The caches are maintained per connection and there is a cache for
865      * the encoder and another for the decoder. The results is that caching 25
866      * string with 10 connections can result in 500 cache entries (2 * 25 * 10).
867      * </p>
868      * 
869      * @return The maximum number of strings that may have their encoded form
870      *         cached.
871      */
872     public int getMaxCachedStringEntries() {
873         return myMaxCachedStringEntries;
874     }
875 
876     /**
877      * Returns the maximum length for a string that the stream is allowed to
878      * cache.This can be used to stop a single long string from pushing useful
879      * values out of the cache. Setting this value to zero turns off the
880      * caching.
881      * <p>
882      * Defaults to {@value #DEFAULT_MAX_STRING_CACHE_LENGTH}.
883      * </p>
884      * <p>
885      * Note: The caches are maintained per connection and there is a cache for
886      * the encoder and another for the decoder. The results is that caching 25
887      * string with 10 connections can result in 500 cache entries (2 * 25 * 10).
888      * </p>
889      * 
890      * @return The maximum length for a string that the stream is allowed to
891      *         cache.
892      */
893     public int getMaxCachedStringLength() {
894         return myMaxCachedStringLength;
895     }
896 
897     /**
898      * Returns the maximum number of connections to use.
899      * <p>
900      * Defaults to 3.
901      * </p>
902      * <p>
903      * <em>Note:</em> In the case of connecting to a replica set this setting
904      * limits the number of connections to the primary server. The driver will
905      * create single connections to the secondary servers if queries are issued
906      * with a {@link ReadPreference} other than {@link ReadPreference#PRIMARY}.
907      * </p>
908      * 
909      * @return The maximum connections to use.
910      */
911     public int getMaxConnectionCount() {
912         return myMaxConnectionCount;
913     }
914 
915     /**
916      * Returns the number of read timeouts (a tick) before closing the
917      * connection.
918      * <p>
919      * Defaults to {@link Integer#MAX_VALUE}.
920      * </p>
921      * 
922      * @return The number of read timeouts (a tick) before closing the
923      *         connection.
924      */
925     public int getMaxIdleTickCount() {
926         return myMaxIdleTickCount;
927     }
928 
929     /**
930      * Returns the maximum number of pending operations to allow per connection.
931      * The higher the value the more "asynchronous" the driver can be but risks
932      * more operations being in an unknown state on a connection error. When the
933      * connection has this many pending connections additional requests will
934      * block.
935      * <p>
936      * Defaults to 1024.
937      * </p>
938      * <p>
939      * <em>Note:</em> In the case of an connection error it is impossible to
940      * determine which pending operations completed and which did not. Setting
941      * this value to 1 results in synchronous operations that wait for
942      * responses.
943      * </p>
944      * 
945      * @return The maximum number of pending operations to allow per connection.
946      */
947     public int getMaxPendingOperationsPerConnection() {
948         return myMaxPendingOperationsPerConnection;
949     }
950 
951     /**
952      * Returns the maximum number of milliseconds that a secondary can be behind
953      * the primary before they will be excluded from being used for queries on
954      * secondaries.
955      * <p>
956      * Defaults to 5 minutes (300,000).
957      * </p>
958      * 
959      * @return The maximum number of milliseconds that a secondary can be behind
960      *         the primary before they will be excluded from being used for
961      *         queries on secondaries.
962      */
963     public long getMaxSecondaryLag() {
964         return myMaxSecondaryLag;
965     }
966 
967     /**
968      * Returns the minimum number of connections to try and keep open.
969      * <p>
970      * Defaults to 0.
971      * </p>
972      * 
973      * @return The minimum number of connections to try and keep open.
974      */
975     public int getMinConnectionCount() {
976         return myMinConnectionCount;
977     }
978 
979     /**
980      * Gets the password hash for authentication with the database.
981      * 
982      * @return The password hash for authentication with the database.
983      * @deprecated Replaced with the more general {@link Credential} capability.
984      *             Will be removed after the 1.3.0 release.
985      */
986     @Deprecated
987     public String getPasswordHash() {
988         if (!myCredentials.isEmpty()) {
989             final Credential credentials = myCredentials.entrySet().iterator()
990                     .next().getValue();
991             try {
992                 final MessageDigest md5 = MessageDigest.getInstance("MD5");
993                 final byte[] digest = md5.digest((credentials.getUserName()
994                         + ":mongo:" + new String(credentials.getPassword()))
995                         .getBytes(UTF8));
996 
997                 return IOUtils.toHex(digest);
998             }
999             catch (final NoSuchAlgorithmException e) {
1000                 throw new MongoDbAuthenticationException(e);
1001             }
1002 
1003         }
1004         return null;
1005     }
1006 
1007     /**
1008      * Returns how long to wait (in milliseconds) for a socket read to complete.
1009      * <p>
1010      * Defaults to 0 or never.
1011      * </p>
1012      * 
1013      * @return The time to wait (in milliseconds) for a socket read to complete.
1014      */
1015     public int getReadTimeout() {
1016         return myReadTimeout;
1017     }
1018 
1019     /**
1020      * Returns how long to wait (in milliseconds) for a broken connection to be
1021      * reconnected.
1022      * <p>
1023      * Defaults to 0 or forever.
1024      * </p>
1025      * 
1026      * @return The time to wait (in milliseconds) for a broken connection to be
1027      *         reconnected.
1028      */
1029     public int getReconnectTimeout() {
1030         return myReconnectTimeout;
1031     }
1032 
1033     /**
1034      * Returns the list of servers to initially attempt to connect to.
1035      * 
1036      * @return The list of servers to initially attempt to connect to.
1037      */
1038     public List<InetSocketAddress> getServerAddresses() {
1039         return Collections.unmodifiableList(myServers);
1040     }
1041 
1042     /**
1043      * Returns the list of servers to initially attempt to connect to.
1044      * 
1045      * @return The list of servers to initially attempt to connect to.
1046      */
1047     public List<String> getServers() {
1048         final List<String> servers = new ArrayList<String>(myServers.size());
1049         for (final InetSocketAddress addr : myServers) {
1050             servers.add(ServerNameUtils.normalize(addr));
1051         }
1052         return servers;
1053     }
1054 
1055     /**
1056      * Returns the socket factory to use in making connections to the MongoDB
1057      * server.
1058      * <p>
1059      * Defaults to {@link SocketFactory#getDefault() SocketFactory.getDefault()}
1060      * .
1061      * </p>
1062      * 
1063      * @return The socketFactory value.
1064      * @see #setSocketFactory(SocketFactory) setSocketFactory(...) or usage
1065      *      examples and suggestions.
1066      */
1067     public SocketFactory getSocketFactory() {
1068         if (mySocketFactory == null) {
1069             mySocketFactory = SocketFactory.getDefault();
1070         }
1071         return mySocketFactory;
1072     }
1073 
1074     /**
1075      * Returns the thread factory for managing connections.
1076      * 
1077      * @return The thread factory for managing connections.
1078      */
1079     public ThreadFactory getThreadFactory() {
1080         return myThreadFactory;
1081     }
1082 
1083     /**
1084      * Gets the user name for authenticating with the database.
1085      * 
1086      * @return The user name for authenticating with the database.
1087      * @deprecated Replaced with the more general {@link Credential} capability.
1088      *             Will be removed after the 1.3.0 release.
1089      */
1090     @Deprecated
1091     public String getUserName() {
1092         if (!myCredentials.isEmpty()) {
1093             final Credential credentials = myCredentials.entrySet().iterator()
1094                     .next().getValue();
1095             return credentials.getUserName();
1096         }
1097         return null;
1098     }
1099 
1100     /**
1101      * Returns true if the user should authenticate as an administrative user.
1102      * 
1103      * @return True if the user should authenticate as an administrative user.
1104      * @deprecated Replaced with the more general {@link Credential} capability.
1105      *             Will be removed after the 1.3.0 release.
1106      */
1107     @Deprecated
1108     public boolean isAdminUser() {
1109         return myCredentials.containsKey(ADMIN_DB_NAME);
1110     }
1111 
1112     /**
1113      * Returns true if the connection is authenticating. If any credentials have
1114      * been added to this configuration then all connections will use
1115      * authentication.
1116      * 
1117      * @return True if the connections should authenticate with the server.
1118      */
1119     public boolean isAuthenticating() {
1120         return !myCredentials.isEmpty();
1121     }
1122 
1123     /**
1124      * Returns if additional servers are auto discovered or if connections are
1125      * limited to the ones manually configured.
1126      * <p>
1127      * Defaults to true, e.g., auto-discover.
1128      * </p>
1129      * 
1130      * @return True if additional servers are auto discovered
1131      */
1132     public boolean isAutoDiscoverServers() {
1133         return myAutoDiscoverServers;
1134     }
1135 
1136     /**
1137      * Returns if the {@link java.net.Socket#setKeepAlive(boolean) SO_KEEPALIVE}
1138      * socket option is set.
1139      * <p>
1140      * Defaults to true, e.g., use SO_KEEPALIVE.
1141      * </p>
1142      * 
1143      * @return True if the {@link java.net.Socket#setKeepAlive(boolean)
1144      *         SO_KEEPALIVE} socket option is set.
1145      */
1146     public boolean isUsingSoKeepalive() {
1147         return myUsingSoKeepalive;
1148     }
1149 
1150     /**
1151      * Removes a {@link PropertyChangeListener} from the configuration. The
1152      * listener will no longer receive notification of all changes to the
1153      * configuration.
1154      * 
1155      * @param listener
1156      *            The {@link PropertyChangeListener} to be removed
1157      */
1158     public synchronized void removePropertyChangeListener(
1159             final PropertyChangeListener listener) {
1160         myPropSupport.removePropertyChangeListener(listener);
1161     }
1162 
1163     /**
1164      * Removes a {@link PropertyChangeListener} from the configuration. The
1165      * listener will no longer receive notification of all changes to the
1166      * configuration's specified property.
1167      * 
1168      * @param propertyName
1169      *            The name of the property that was listened on.
1170      * @param listener
1171      *            The {@link PropertyChangeListener} to be removed
1172      */
1173 
1174     public synchronized void removePropertyChangeListener(
1175             final String propertyName, final PropertyChangeListener listener) {
1176         myPropSupport.removePropertyChangeListener(propertyName, listener);
1177     }
1178 
1179     /**
1180      * Sets if additional servers are auto discovered or if connections are
1181      * limited to the ones manually configured.
1182      * <p>
1183      * Defaults to true, e.g., auto-discover.
1184      * </p>
1185      * 
1186      * @param autoDiscoverServers
1187      *            The new value for auto-discovering servers.
1188      */
1189     public void setAutoDiscoverServers(final boolean autoDiscoverServers) {
1190         final boolean old = myAutoDiscoverServers;
1191 
1192         myAutoDiscoverServers = autoDiscoverServers;
1193 
1194         myPropSupport.firePropertyChange("autoDiscoverServers", old,
1195                 myAutoDiscoverServers);
1196     }
1197 
1198     /**
1199      * Sets the model the driver uses for managing connections.
1200      * <p>
1201      * Defaults to {@link ConnectionModel#RECEIVER_THREAD}.
1202      * </p>
1203      * 
1204      * @param connectionModel
1205      *            The new value for the model the driver uses for managing
1206      *            connections.
1207      */
1208     public void setConnectionModel(final ConnectionModel connectionModel) {
1209         final ConnectionModel old = myConnectionModel;
1210 
1211         myConnectionModel = connectionModel;
1212 
1213         myPropSupport.firePropertyChange("connectionModel", old,
1214                 myConnectionModel);
1215     }
1216 
1217     /**
1218      * Sets how long to wait (in milliseconds) for a socket connection to
1219      * complete.
1220      * 
1221      * @param connectTimeout
1222      *            The time to wait (in milliseconds) for a socket connection to
1223      *            complete.
1224      */
1225     public void setConnectTimeout(final int connectTimeout) {
1226         final int old = myConnectTimeout;
1227 
1228         myConnectTimeout = connectTimeout;
1229 
1230         myPropSupport.firePropertyChange("connectTimeout", old,
1231                 myConnectTimeout);
1232     }
1233 
1234     /**
1235      * Sets the credentials to use to access the server. This removes all
1236      * existing credentials.
1237      * 
1238      * @param credentials
1239      *            The credentials to use to access the server..
1240      * @throws IllegalArgumentException
1241      *             If the credentials refer to an unknown authentication type or
1242      *             the configuration already has a set of credentials for the
1243      *             credentials specified database.
1244      */
1245     public void setCredentials(final Collection<Credential> credentials) {
1246         final List<Credential> old = new ArrayList<Credential>(
1247                 myCredentials.values());
1248 
1249         myCredentials.clear();
1250         for (final Credential credential : credentials) {
1251             addCredential(credential);
1252         }
1253 
1254         myPropSupport.firePropertyChange("credentials", old,
1255                 new ArrayList<Credential>(myCredentials.values()));
1256     }
1257 
1258     /**
1259      * Sets the default database for the connection.
1260      * <p>
1261      * This is used as the database to authenticate against if the user is not
1262      * an administrative user.
1263      * </p>
1264      * <p>
1265      * Defaults to {@value #DEFAULT_DB_NAME}.
1266      * </p>
1267      * 
1268      * @param defaultDatabase
1269      *            The new default database value.
1270      * @deprecated Replaced with the more general {@link Credential} capability.
1271      *             Will be removed after the 1.3.0 release.
1272      */
1273     @Deprecated
1274     public void setDefaultDatabase(final String defaultDatabase) {
1275         final String old = myDefaultDatabase;
1276 
1277         myDefaultDatabase = defaultDatabase;
1278 
1279         if (myLegacyCredential != null) {
1280             final List<Credential> oldCredentials = new ArrayList<Credential>(
1281                     myCredentials.values());
1282             myCredentials.remove(myLegacyCredential.getDatabase());
1283             myPropSupport.firePropertyChange("credentials", oldCredentials,
1284                     new ArrayList<Credential>(myCredentials.values()));
1285 
1286             myLegacyCredential = Credential.builder()
1287                     .userName(myLegacyCredential.getUserName())
1288                     .password(myLegacyCredential.getPassword())
1289                     .database(defaultDatabase).mongodbCR().build();
1290             addCredential(myLegacyCredential);
1291         }
1292 
1293         myPropSupport.firePropertyChange("defaultDatabase", old,
1294                 myDefaultDatabase);
1295     }
1296 
1297     /**
1298      * Sets the default durability for write operations on the server to the new
1299      * value.
1300      * 
1301      * @param defaultDurability
1302      *            The default durability for write operations on the server.
1303      */
1304     public void setDefaultDurability(final Durability defaultDurability) {
1305         final Durability old = myDefaultDurability;
1306 
1307         myDefaultDurability = defaultDurability;
1308 
1309         myPropSupport.firePropertyChange("defaultDurability", old,
1310                 myDefaultDurability);
1311     }
1312 
1313     /**
1314      * Sets the value of the default read preference for a query.
1315      * <p>
1316      * Defaults to {@link ReadPreference#PRIMARY} if <code>null</code> is set.
1317      * </p>
1318      * 
1319      * @param defaultReadPreference
1320      *            The default read preference for a query.
1321      */
1322     public void setDefaultReadPreference(
1323             final ReadPreference defaultReadPreference) {
1324         final ReadPreference old = myDefaultReadPreference;
1325 
1326         if (defaultReadPreference == null) {
1327             myDefaultReadPreference = ReadPreference.PRIMARY;
1328         }
1329         else {
1330             myDefaultReadPreference = defaultReadPreference;
1331         }
1332 
1333         myPropSupport.firePropertyChange("defaultReadPreference", old,
1334                 myDefaultReadPreference);
1335     }
1336 
1337     /**
1338      * Sets the value of executor for replies from the server.
1339      * <p>
1340      * By default the executor is <code>null</code> which will cause the reply
1341      * handling to execute on the socket's receive thread.
1342      * </p>
1343      * <p>
1344      * Care should be taken to ensure that the executor does not drop requests.
1345      * This implies that the
1346      * {@link java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy} or
1347      * similar should be used as the
1348      * {@link java.util.concurrent.RejectedExecutionHandler}.
1349      * </p>
1350      * 
1351      * @param executor
1352      *            The new value for the executor.
1353      */
1354     public void setExecutor(final Executor executor) {
1355         final Executor old = myExecutor;
1356 
1357         myExecutor = executor;
1358 
1359         myPropSupport.firePropertyChange("executor", old, myExecutor);
1360     }
1361 
1362     /**
1363      * Sets the type of hand off lock to use between threads in the core of the
1364      * driver.
1365      * <p>
1366      * Defaults to {@link LockType#MUTEX}.
1367      * </p>
1368      * 
1369      * @param lockType
1370      *            The new value for the type of hand off lock used.
1371      */
1372     public void setLockType(final LockType lockType) {
1373         final LockType old = myLockType;
1374 
1375         myLockType = lockType;
1376 
1377         myPropSupport.firePropertyChange("lockType", old, myLockType);
1378     }
1379 
1380     /**
1381      * Sets the value of maximum number of strings that may have their encoded
1382      * form cached.
1383      * <p>
1384      * Defaults to {@value #DEFAULT_MAX_STRING_CACHE_ENTRIES}.
1385      * </p>
1386      * <p>
1387      * Note: The caches are maintained per {@link MongoClient} instance.
1388      * </p>
1389      * 
1390      * @param maxCacheEntries
1391      *            The new value for the maximum number of strings that may have
1392      *            their encoded form cached.
1393      */
1394     public void setMaxCachedStringEntries(final int maxCacheEntries) {
1395         final int old = myMaxCachedStringEntries;
1396 
1397         myMaxCachedStringEntries = maxCacheEntries;
1398 
1399         myPropSupport.firePropertyChange("maxCachedStringEntries", old,
1400                 myMaxCachedStringEntries);
1401     }
1402 
1403     /**
1404      * Sets the value of length for a string that may be cached. This can be
1405      * used to stop a single long string from pushing useful values out of the
1406      * cache. Setting this value to zero turns off the caching.
1407      * <p>
1408      * Defaults to {@value #DEFAULT_MAX_STRING_CACHE_LENGTH}.
1409      * </p>
1410      * <p>
1411      * Note: The caches are maintained per {@link MongoClient} instance.
1412      * </p>
1413      * 
1414      * @param maxlength
1415      *            The new value for the length for a string that the encoder is
1416      *            allowed to cache.
1417      */
1418     public void setMaxCachedStringLength(final int maxlength) {
1419         final int old = myMaxCachedStringLength;
1420 
1421         myMaxCachedStringLength = maxlength;
1422 
1423         myPropSupport.firePropertyChange("maxCachedStringLength", old,
1424                 myMaxCachedStringLength);
1425     }
1426 
1427     /**
1428      * Sets the maximum number of connections to use.
1429      * <p>
1430      * Defaults to 3.
1431      * </p>
1432      * <p>
1433      * <em>Note:</em> In the case of connecting to a replica set this setting
1434      * limits the number of connections to the primary server. The driver will
1435      * create single connections to the secondary servers if queries are issued
1436      * with a {@link ReadPreference} other than {@link ReadPreference#PRIMARY}.
1437      * </p>
1438      * 
1439      * @param maxConnectionCount
1440      *            New maximum number of connections to use.
1441      */
1442     public void setMaxConnectionCount(final int maxConnectionCount) {
1443         final int old = myMaxConnectionCount;
1444 
1445         myMaxConnectionCount = maxConnectionCount;
1446 
1447         myPropSupport.firePropertyChange("maxConnectionCount", old,
1448                 myMaxConnectionCount);
1449     }
1450 
1451     /**
1452      * Sets the value of the number of read timeouts (a tick) before closing the
1453      * connection.
1454      * 
1455      * @param idleTickCount
1456      *            The new value for the number of read timeouts (a tick) before
1457      *            closing the connection.
1458      */
1459     public void setMaxIdleTickCount(final int idleTickCount) {
1460         final int old = myMaxIdleTickCount;
1461 
1462         myMaxIdleTickCount = idleTickCount;
1463 
1464         myPropSupport.firePropertyChange("maxIdleTickCount", old,
1465                 myMaxIdleTickCount);
1466     }
1467 
1468     /**
1469      * Sets the maximum number of pending operations to allow per connection.
1470      * The higher the value the more "asynchronous" the driver can be but risks
1471      * more operations being in an unknown state on a connection error. When the
1472      * connection has this many pending connections additional requests will
1473      * block.
1474      * 
1475      * @param maxPendingOperationsPerConnection
1476      *            The new maximum number of pending operations to allow per
1477      *            connection.
1478      */
1479     public void setMaxPendingOperationsPerConnection(
1480             final int maxPendingOperationsPerConnection) {
1481         final int old = myMaxPendingOperationsPerConnection;
1482 
1483         myMaxPendingOperationsPerConnection = maxPendingOperationsPerConnection;
1484 
1485         myPropSupport.firePropertyChange("maxPendingOperationsPerConnection",
1486                 old, myMaxPendingOperationsPerConnection);
1487     }
1488 
1489     /**
1490      * Sets the maximum number of milliseconds that a secondary can be behind
1491      * the primary before they will be excluded from being used for queries on
1492      * secondaries.
1493      * <p>
1494      * Defaults to 5 minutes (300,000).
1495      * </p>
1496      * 
1497      * @param maxSecondaryLag
1498      *            The new value for the maximum number of milliseconds that a
1499      *            secondary can be behind the primary before they will be
1500      *            excluded from being used for queries on secondaries.
1501      */
1502     public void setMaxSecondaryLag(final long maxSecondaryLag) {
1503         final long old = myMaxSecondaryLag;
1504 
1505         myMaxSecondaryLag = maxSecondaryLag;
1506 
1507         myPropSupport.firePropertyChange("maxSecondaryLag", Long.valueOf(old),
1508                 Long.valueOf(myMaxSecondaryLag));
1509     }
1510 
1511     /**
1512      * Sets the value of the minimum number of connections to try and keep open.
1513      * 
1514      * @param minimumConnectionCount
1515      *            The new value for the minimum number of connections to try and
1516      *            keep open.
1517      */
1518     public void setMinConnectionCount(final int minimumConnectionCount) {
1519         final int old = myMinConnectionCount;
1520 
1521         myMinConnectionCount = minimumConnectionCount;
1522 
1523         myPropSupport.firePropertyChange("minConnectionCount", old,
1524                 myMinConnectionCount);
1525     }
1526 
1527     /**
1528      * @param readTimeout
1529      *            The time to wait (in milliseconds) for a socket read to
1530      *            complete.
1531      */
1532     public void setReadTimeout(final int readTimeout) {
1533         final int old = myReadTimeout;
1534 
1535         myReadTimeout = readTimeout;
1536 
1537         myPropSupport.firePropertyChange("readTimeout", old, myReadTimeout);
1538     }
1539 
1540     /**
1541      * Sets how long to wait (in milliseconds) for a broken connection to
1542      * reconnect.
1543      * 
1544      * @param connectTimeout
1545      *            The time to wait (in milliseconds) for a broken connection to
1546      *            reconnect.
1547      */
1548     public void setReconnectTimeout(final int connectTimeout) {
1549         final int old = myReconnectTimeout;
1550 
1551         myReconnectTimeout = connectTimeout;
1552 
1553         myPropSupport.firePropertyChange("reconnectTimeout", old,
1554                 myReconnectTimeout);
1555     }
1556 
1557     /**
1558      * Sets the servers to initially attempt to connect to.
1559      * 
1560      * @param servers
1561      *            The servers to connect to.
1562      */
1563     public void setServers(final List<InetSocketAddress> servers) {
1564         final List<InetSocketAddress> old = new ArrayList<InetSocketAddress>(
1565                 myServers);
1566 
1567         myServers.clear();
1568         if (servers != null) {
1569             for (final InetSocketAddress server : servers) {
1570                 addServer(server);
1571             }
1572         }
1573 
1574         myPropSupport.firePropertyChange("servers", old,
1575                 Collections.unmodifiableList(myServers));
1576     }
1577 
1578     /**
1579      * Sets the socket factory to use in making connections to the MongoDB
1580      * server. Setting the SocketFactory to null resets the factory to the
1581      * default.
1582      * <p>
1583      * Defaults to {@link SocketFactory#getDefault()
1584      * SocketFactory.getDefault().}
1585      * </p>
1586      * <p>
1587      * For SSL based connections this can be an appropriately configured
1588      * {@link javax.net.ssl.SSLSocketFactory}.
1589      * </p>
1590      * <p>
1591      * Other {@link Socket} and {@link InetSocketAddress} implementations with
1592      * an appropriate {@link SocketFactory} implementation can be used with the
1593      * driver. The driver only ever calls the
1594      * {@link SocketFactory#createSocket()} method and then connects the socket
1595      * passing the server's {@link InetSocketAddress}.
1596      * </p>
1597      * <p>
1598      * See the <a href="http://code.google.com/p/junixsocket">junixsocket
1599      * Project</a> for an example of a {@link Socket} and
1600      * {@link InetSocketAddress} implementations for UNIX Domain Sockets that
1601      * can be wrapped with SocketFactory similar to the following:<blockquote>
1602      * <code><pre>
1603      *  public class AFUNIXSocketFactory extends SocketFactory {
1604      *      public Socket createSocket() throws java.io.IOException {
1605      *          return new org.newsclub.net.unix.AFUNIXSocket.newInstance();
1606      *      }
1607      * 
1608      *      public Socket createSocket(String host, int port) throws SocketException {
1609      *          throw new SocketException("AFUNIX socket does not support connections to a host/port");
1610      *      }
1611      * 
1612      *      public Socket createSocket(InetAddress host, int port) throws SocketException {
1613      *          throw new SocketException("AFUNIX socket does not support connections to a host/port");
1614      *      }
1615      * 
1616      *      public Socket createSocket(String host, int port, InetAddress localHost,
1617      *              int localPort) throws SocketException {
1618      *          throw new SocketException("AFUNIX socket does not support connections to a host/port");
1619      *      }
1620      * 
1621      *      public Socket createSocket(InetAddress address, int port,
1622      *              InetAddress localAddress, int localPort) throws SocketException {
1623      *          throw new SocketException("AFUNIX socket does not support connections to a host/port");
1624      *      }
1625      *  }
1626      * </pre></code></blockquote>
1627      * </p>
1628      * 
1629      * @param socketFactory
1630      *            The socketFactory value.
1631      * 
1632      * @see <a href="http://code.google.com/p/junixsocket">junixsocket
1633      *      Project</a>
1634      */
1635     public void setSocketFactory(final SocketFactory socketFactory) {
1636         final SocketFactory old = mySocketFactory;
1637 
1638         if (socketFactory == null) {
1639             mySocketFactory = SocketFactory.getDefault();
1640         }
1641         else {
1642             mySocketFactory = socketFactory;
1643         }
1644 
1645         myPropSupport.firePropertyChange("socketFactory", old, mySocketFactory);
1646     }
1647 
1648     /**
1649      * Sets the thread factory for managing connections to the new value.
1650      * 
1651      * @param factory
1652      *            The thread factory for managing connections.
1653      */
1654     public void setThreadFactory(final ThreadFactory factory) {
1655         final ThreadFactory old = myThreadFactory;
1656 
1657         myThreadFactory = factory;
1658 
1659         myPropSupport.firePropertyChange("threadFactory", old, myThreadFactory);
1660     }
1661 
1662     /**
1663      * Sets if the {@link java.net.Socket#setKeepAlive(boolean) SO_KEEPALIVE}
1664      * socket option is set.
1665      * <p>
1666      * Defaults to true, e.g., use SO_KEEPALIVE.
1667      * </p>
1668      * 
1669      * @param usingSoKeepalive
1670      *            The new value for using SO_KEEPALIVE.
1671      */
1672     public void setUsingSoKeepalive(final boolean usingSoKeepalive) {
1673         final boolean old = myUsingSoKeepalive;
1674 
1675         myUsingSoKeepalive = usingSoKeepalive;
1676 
1677         myPropSupport.firePropertyChange("usingSoKeepalive", old,
1678                 myUsingSoKeepalive);
1679     }
1680 
1681     /**
1682      * Reads the serialized configuration and sets the transient field to known
1683      * values.
1684      * 
1685      * @param stream
1686      *            The stream to read from.
1687      * @throws IOException
1688      *             On a failure reading from the stream.
1689      * @throws ClassNotFoundException
1690      *             On a failure locating a type in the stream.
1691      */
1692     private void readObject(final java.io.ObjectInputStream stream)
1693             throws IOException, ClassNotFoundException {
1694         stream.defaultReadObject();
1695 
1696         myExecutor = null;
1697         mySocketFactory = null;
1698         myThreadFactory = null;
1699     }
1700 
1701     /**
1702      * Updates this configurations value based on the field's descriptor, name
1703      * and value.
1704      * 
1705      * @param descriptor
1706      *            The field's descriptor.
1707      * @param propName
1708      *            The name of the field.
1709      * @param propValue
1710      *            The value for the field.
1711      * @throws IllegalArgumentException
1712      *             On a number of errors.
1713      */
1714     @SuppressWarnings({ "unchecked", "rawtypes" })
1715     private void updateFieldValue(final PropertyDescriptor descriptor,
1716             final String propName, final String propValue)
1717             throws IllegalArgumentException {
1718 
1719         try {
1720             final Class<?> fieldType = descriptor.getPropertyType();
1721             final Method writer = descriptor.getWriteMethod();
1722             PropertyEditor editor;
1723             final Class<?> editorClass = descriptor.getPropertyEditorClass();
1724             if (editorClass != null) {
1725                 editor = (PropertyEditor) editorClass.newInstance();
1726             }
1727             else {
1728                 editor = PropertyEditorManager.findEditor(fieldType);
1729             }
1730 
1731             if (editor != null) {
1732                 final Method reader = descriptor.getReadMethod();
1733                 editor.setValue(reader.invoke(this));
1734                 editor.setAsText(propValue);
1735                 writer.invoke(this, editor.getValue());
1736             }
1737             else if (fieldType.isEnum()) {
1738                 final Class<? extends Enum> c = (Class<? extends Enum>) fieldType;
1739                 writer.invoke(this, Enum.valueOf(c, propValue));
1740             }
1741             else {
1742                 throw new IllegalArgumentException("The '" + propName
1743                         + "' parameter could not be set " + "to the value '"
1744                         + propValue + "'. No editor available.");
1745 
1746             }
1747         }
1748         catch (final InstantiationException e) {
1749             throw new IllegalArgumentException(e);
1750         }
1751         catch (final IllegalAccessException e) {
1752             throw new IllegalArgumentException(e);
1753         }
1754         catch (final InvocationTargetException e) {
1755             throw new IllegalArgumentException(e);
1756         }
1757     }
1758 }