1 /*
2 * #%L
3 * Server.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.client.state;
21
22 import java.beans.PropertyChangeListener;
23 import java.beans.PropertyChangeSupport;
24 import java.net.InetSocketAddress;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.atomic.AtomicLong;
31
32 import com.allanbank.mongodb.Version;
33 import com.allanbank.mongodb.bson.Document;
34 import com.allanbank.mongodb.bson.Element;
35 import com.allanbank.mongodb.bson.NumericElement;
36 import com.allanbank.mongodb.bson.builder.BuilderFactory;
37 import com.allanbank.mongodb.bson.element.BooleanElement;
38 import com.allanbank.mongodb.bson.element.DocumentElement;
39 import com.allanbank.mongodb.bson.element.StringElement;
40 import com.allanbank.mongodb.bson.element.TimestampElement;
41 import com.allanbank.mongodb.client.Client;
42 import com.allanbank.mongodb.util.ServerNameUtils;
43
44 /**
45 * Server provides tracking of the state of a single MongoDB server.
46 *
47 * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
48 * mutated in incompatible ways between any two releases of the driver.
49 * @copyright 2013, Allanbank Consulting, Inc., All Rights Reserved
50 */
51 public class Server {
52
53 /** The name for the Server's canonical name property: '{@value} '. */
54 public static final String CANONICAL_NAME_PROP = "canonicalName";
55
56 /** The decay rate for the exponential average for the latency. */
57 public static final double DECAY_ALPHA;
58
59 /** The decay period (number of samples) for the average latency. */
60 public static final double DECAY_SAMPLES = 1000.0D;
61
62 /** The default MongoDB port. */
63 public static final int DEFAULT_PORT = ServerNameUtils.DEFAULT_PORT;
64
65 /** The document element type. */
66 public static final Class<DocumentElement> DOCUMENT_TYPE = DocumentElement.class;
67
68 /** The default number of max batched write operations. */
69 public static final int MAX_BATCHED_WRITE_OPERATIONS_DEFAULT = 1000;
70
71 /** The name for the Server's maximum BSON object size property: {@value} . */
72 public static final String MAX_BATCHED_WRITE_OPERATIONS_PROP = "maxWriteBatchSize";
73
74 /** The name for the Server's maximum BSON object size property: {@value} . */
75 public static final String MAX_BSON_OBJECT_SIZE_PROP = "maxBsonObjectSize";
76
77 /** The numeric element type. */
78 public static final Class<NumericElement> NUMERIC_TYPE = NumericElement.class;
79
80 /** The value for a primary server's state. */
81 public static final int PRIMARY_STATE = 1;
82
83 /** The value for a secondary (actively replicating) server's state. */
84 public static final int SECONDARY_STATE = 2;
85
86 /** The name for the Server's state property: {@value} . */
87 public static final String STATE_PROP = "state";
88
89 /** The string element type. */
90 public static final Class<StringElement> STRING_TYPE = StringElement.class;
91
92 /** The name for the Server's tags property: {@value} . */
93 public static final String TAGS_PROP = "tags";
94
95 /** The timestamp element type. */
96 public static final Class<TimestampElement> TIMESTAMP_TYPE = TimestampElement.class;
97
98 /** The name for the Server's version property: {@value} . */
99 public static final String VERSION_PROP = "version";
100
101 /** The number of nano-seconds per milli-second. */
102 private static final double NANOS_PER_MILLI = TimeUnit.MILLISECONDS
103 .toNanos(1);
104
105 static {
106 DECAY_ALPHA = (2.0D / (DECAY_SAMPLES + 1));
107 }
108
109 /**
110 * Tracks the average latency for the server connection. This is set when
111 * the connection to the server is first created and then updated
112 * periodically using an exponential moving average.
113 */
114 private volatile double myAverageLatency;
115
116 /**
117 * The socket address provided by the user. This address will not be
118 * updated.
119 */
120 private final InetSocketAddress myCanonicalAddress;
121
122 /**
123 * The host name for the {@link #myCanonicalAddress}. This is use to
124 * re-resolve the IP address when a connection failure is experienced.
125 */
126 private final String myCanonicalHostName;
127
128 /** The normalized name of the server being tracked. */
129 private volatile String myCanonicalName;
130
131 /** Provides support for the sending of property change events. */
132 private final PropertyChangeSupport myEventSupport;
133
134 /** The time of the last version update. */
135 private long myLastVersionUpdate = 0;
136
137 /**
138 * The maximum number of write operations allowed in a single write command.
139 * Defaults to {@value #MAX_BATCHED_WRITE_OPERATIONS_DEFAULT}.
140 */
141 private volatile int myMaxBatchedWriteOperations = MAX_BATCHED_WRITE_OPERATIONS_DEFAULT;
142
143 /**
144 * The maximum BSON object size the server will accept. Defaults to
145 * {@link Client#MAX_DOCUMENT_SIZE}.
146 */
147 private volatile int myMaxBsonObjectSize = Client.MAX_DOCUMENT_SIZE;
148
149 /** The number of messages sent to the server. */
150 private final AtomicLong myMessagesSent;
151
152 /** The number of messages received from the server. */
153 private final AtomicLong myRepliesReceived;
154
155 /**
156 * Tracks the last report of how many seconds the server is behind the
157 * primary.
158 */
159 private volatile double mySecondsBehind;
160
161 /** Tracking the state of the server. */
162 private volatile State myState;
163
164 /** Tracking the tags for the server. */
165 private volatile Document myTags;
166
167 /** The total amount of latency for sending messages to the server. */
168 private final AtomicLong myTotalLatency;
169
170 /** The version of the server. */
171 private Version myVersion;
172
173 /**
174 * The socket address being actively used. This will be re-created using the
175 * server's hostname if a connection attempt fails.
176 */
177 private volatile InetSocketAddress myWorkingAddress;
178
179 /**
180 * Creates a new {@link Server}. Package private to force creation through
181 * the {@link Cluster}.
182 *
183 * @param server
184 * The server being tracked.
185 */
186 /* package */Server(final InetSocketAddress server) {
187 myCanonicalAddress = server;
188 myCanonicalHostName = server.getHostName();
189 myCanonicalName = ServerNameUtils.normalize(server);
190 myWorkingAddress = myCanonicalAddress;
191
192 myEventSupport = new PropertyChangeSupport(this);
193
194 myMessagesSent = new AtomicLong(0);
195 myRepliesReceived = new AtomicLong(0);
196 myTotalLatency = new AtomicLong(0);
197
198 myState = State.UNKNOWN;
199 myAverageLatency = Double.MAX_VALUE;
200 mySecondsBehind = Double.MAX_VALUE;
201 myTags = null;
202
203 myVersion = Version.UNKNOWN;
204 }
205
206 /**
207 * Add a PropertyChangeListener to receive all future property changes for
208 * the {@link Server}.
209 *
210 * @param listener
211 * The PropertyChangeListener to be added
212 *
213 * @see PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)
214 */
215 public void addListener(final PropertyChangeListener listener) {
216 myEventSupport.addPropertyChangeListener(listener);
217
218 }
219
220 /**
221 * Notification that an attempt to connect to the server via the all of the
222 * {@link #getAddresses() addresses provided} failed.
223 */
224 public void connectFailed() {
225 final State oldValue = myState;
226
227 myWorkingAddress = null;
228 myState = State.UNAVAILABLE;
229
230 myEventSupport.firePropertyChange(STATE_PROP, oldValue, myState);
231 }
232
233 /**
234 * Notification that a connection has closed normally. This will leave the
235 * connection in the last known state even if it is the last open
236 * connection.
237 */
238 public void connectionClosed() {
239 // Nothing for now....
240 }
241
242 /**
243 * Notification that a connection was successfully opened to the server. The
244 * {@link InetSocketAddress} provided becomes the preferred address to use
245 * when connecting to the server.
246 *
247 * @param addressUsed
248 * The address that was used to connect to the server.
249 */
250 public void connectionOpened(final InetSocketAddress addressUsed) {
251 myWorkingAddress = addressUsed;
252 }
253
254 /**
255 * Notification that a connection has closed abruptly. This will normally
256 * transition the connection to an unknown state.
257 */
258 public void connectionTerminated() {
259 final State oldValue = myState;
260
261 myWorkingAddress = null;
262 myState = State.UNAVAILABLE;
263
264 myEventSupport.firePropertyChange(STATE_PROP, oldValue, myState);
265 }
266
267 /**
268 * {@inheritDoc}
269 * <p>
270 * Overridden to return a stable equality check. This is based only on the
271 * server object's identity. The {@link Cluster} class will de-duplicate
272 * once the canonical host names are determined.
273 * </p>
274 */
275 @Override
276 public boolean equals(final Object object) {
277 return (this == object);
278 }
279
280 /**
281 * Returns the address of the server being tracked.
282 *
283 * @return The address of the server being tracked.
284 */
285 public Collection<InetSocketAddress> getAddresses() {
286 if (myWorkingAddress == null) {
287 myWorkingAddress = InetSocketAddress.createUnresolved(
288 myCanonicalHostName, myCanonicalAddress.getPort());
289 }
290
291 if (myCanonicalAddress == myWorkingAddress) {
292 return Collections.singleton(myCanonicalAddress);
293 }
294 return Arrays.asList(myWorkingAddress, myCanonicalAddress);
295 }
296
297 /**
298 * Returns the current average latency (in milliseconds) seen in issuing
299 * requests to the server. If the latency returns {@link Double#MAX_VALUE}
300 * then we have no basis for determining the latency.
301 * <p>
302 * This average is over the recent replies not over all replies received.
303 * </p>
304 *
305 * @return The current average latency (in milliseconds) seen in issuing
306 * requests to the server.
307 */
308 public double getAverageLatency() {
309 return myAverageLatency;
310 }
311
312 /**
313 * Returns the name of the server as reported by the server itself.
314 *
315 * @return The name of the server as reported by the server itself.
316 */
317 public String getCanonicalName() {
318 return myCanonicalName;
319 }
320
321 /**
322 * Returns the maximum number of write operations allowed in a single write
323 * command. Defaults to {@value #MAX_BATCHED_WRITE_OPERATIONS_DEFAULT}.
324 *
325 * @return The maximum number of write operations allowed in a single write
326 * command.
327 */
328 public int getMaxBatchedWriteOperations() {
329 return myMaxBatchedWriteOperations;
330 }
331
332 /**
333 * Returns the maximum BSON object size the server will accept. Defaults to
334 * {@link Client#MAX_DOCUMENT_SIZE}.
335 *
336 * @return The maximum BSON object size the server will accept.
337 */
338 public int getMaxBsonObjectSize() {
339 return myMaxBsonObjectSize;
340 }
341
342 /**
343 * Returns the number of messages sent to the server.
344 *
345 * @return The number of messages sent to the server.
346 */
347 public long getMessagesSent() {
348 return myMessagesSent.get();
349 }
350
351 /**
352 * Returns the number of messages received from the server.
353 *
354 * @return The number of messages received from the server.
355 */
356 public long getRepliesReceived() {
357 return myRepliesReceived.get();
358 }
359
360 /**
361 * Sets the last reported seconds behind the primary.
362 *
363 * @return The seconds behind the primary server.
364 */
365 public double getSecondsBehind() {
366 return mySecondsBehind;
367 }
368
369 /**
370 * Returns the state value.
371 *
372 * @return The state value.
373 */
374 public State getState() {
375 return myState;
376 }
377
378 /**
379 * Returns the tags for the server.
380 *
381 * @return The tags for the server.
382 */
383 public Document getTags() {
384 return myTags;
385 }
386
387 /**
388 * Returns the total amount of time messages waited for a reply from the
389 * server in nanoseconds. The average latency is approximately
390 * {@link #getTotalLatencyNanoSeconds()}/{@link #getRepliesReceived()}.
391 *
392 * @return The total amount of time messages waited for a reply from the
393 * server in nanoseconds.
394 */
395 public long getTotalLatencyNanoSeconds() {
396 return myTotalLatency.get();
397 }
398
399 /**
400 * Returns the version of the server.
401 *
402 * @return The version of the server.
403 */
404 public Version getVersion() {
405 return myVersion;
406 }
407
408 /**
409 * {@inheritDoc}
410 * <p>
411 * Overridden to return a stable hash for the server. This is based only on
412 * the server object's {@link System#identityHashCode(Object) identity hash
413 * code}. The {@link Cluster} class will de-duplicate once the canonical
414 * host names are determined.
415 * </p>
416 */
417 @Override
418 public int hashCode() {
419 return System.identityHashCode(this);
420 }
421
422 /**
423 * Increments the number of messages sent to the server.
424 */
425 public void incrementMessagesSent() {
426 myMessagesSent.incrementAndGet();
427 }
428
429 /**
430 * Increments the number of messages received from the server.
431 */
432 public void incrementRepliesReceived() {
433 myRepliesReceived.incrementAndGet();
434 }
435
436 /**
437 * Returns true if the server can be written to, false otherwise.
438 * <p>
439 * If writable it might be a standalone server, the primary in a replica
440 * set, or a mongos in a sharded configuration. If not writable it is a
441 * secondary server in a replica set.
442 * </p>
443 *
444 * @return True if the server can be written to, false otherwise.
445 */
446 public boolean isWritable() {
447 return (myState == State.WRITABLE);
448 }
449
450 /**
451 * Returns true if there has not been a recent update to the server's
452 * version or maximum document size.
453 *
454 * @return True if there has not been a recent update to the server's
455 * version or maximum document size.
456 */
457 public boolean needBuildInfo() {
458 final long now = System.currentTimeMillis();
459 final long tenMinutesAgo = now - TimeUnit.MINUTES.toMillis(10);
460
461 return Version.UNKNOWN.equals(myVersion)
462 || (myLastVersionUpdate < tenMinutesAgo);
463 }
464
465 /**
466 * Remove a PropertyChangeListener to stop receiving future property changes
467 * for the {@link Server}.
468 *
469 * @param listener
470 * The PropertyChangeListener to be removed
471 *
472 * @see PropertyChangeSupport#removePropertyChangeListener(PropertyChangeListener)
473 */
474 public void removeListener(final PropertyChangeListener listener) {
475 myEventSupport.removePropertyChangeListener(listener);
476 }
477
478 /**
479 * Notification that a status request message on the connection failed.
480 * <p>
481 * In the case of an exception the seconds behind is set to
482 * {@link Integer#MAX_VALUE}. The value is configurable as a long so in
483 * theory a user can ignore this case using a large
484 * {@link com.allanbank.mongodb.MongoClientConfiguration#setMaxSecondaryLag(long)}
485 * .
486 * </p>
487 */
488 public void requestFailed() {
489 mySecondsBehind = Integer.MAX_VALUE;
490 }
491
492 /**
493 * {@inheritDoc}
494 * <p>
495 * Overridden to to return a human readable version of the server state.
496 * </p>
497 */
498 @Override
499 public String toString() {
500 final StringBuilder builder = new StringBuilder();
501
502 builder.append(getCanonicalName());
503 builder.append("(");
504 builder.append(myState);
505 builder.append(",");
506 if (myTags != null) {
507 builder.append("T,");
508 }
509 builder.append(getAverageLatency());
510 builder.append(")");
511
512 return builder.toString();
513 }
514
515 /**
516 * Updates the state of the server based on the document provided. The
517 * document should be the reply to either a {@code ismaster} or
518 * {@code replSetGetStatus} command.
519 *
520 * @param document
521 * The document with the state of the server.
522 */
523 public void update(final Document document) {
524 updateState(document);
525 updateSecondsBehind(document);
526 updateTags(document);
527 updateName(document);
528 updateVersion(document);
529 updateMaxBsonObjectSize(document);
530 updateMaxWriteOperations(document);
531 }
532
533 /**
534 * Updates the average latency (in nano-seconds) for the server.
535 *
536 * @param latencyNanoSeconds
537 * The latency seen sending a request and receiving a reply from
538 * the server.
539 */
540 public void updateAverageLatency(final long latencyNanoSeconds) {
541 myTotalLatency.addAndGet(latencyNanoSeconds);
542
543 final double latency = latencyNanoSeconds / NANOS_PER_MILLI;
544 final double oldAverage = myAverageLatency;
545 if (Double.MAX_VALUE == oldAverage) {
546 myAverageLatency = latency;
547 if (mySecondsBehind == Double.MAX_VALUE) {
548 mySecondsBehind = 0.0;
549 }
550 }
551 else {
552 myAverageLatency = (DECAY_ALPHA * latency)
553 + ((1.0D - DECAY_ALPHA) * oldAverage);
554 }
555 }
556
557 /**
558 * Extract any {@code maxBsonObjectSize} from the reply.
559 *
560 * @param isMasterReply
561 * The reply to the {@code ismaster} command.
562 */
563 private void updateMaxBsonObjectSize(final Document isMasterReply) {
564 final int oldValue = myMaxBsonObjectSize;
565
566 final NumericElement maxSize = isMasterReply.findFirst(NUMERIC_TYPE,
567 MAX_BSON_OBJECT_SIZE_PROP);
568 if (maxSize != null) {
569 myMaxBsonObjectSize = maxSize.getIntValue();
570 }
571
572 myEventSupport.firePropertyChange(MAX_BSON_OBJECT_SIZE_PROP, oldValue,
573 myMaxBsonObjectSize);
574 }
575
576 /**
577 * Extract any {@code maxWriteBatchSize} from the reply.
578 *
579 * @param isMasterReply
580 * The reply to the {@code ismaster} command.
581 */
582 private void updateMaxWriteOperations(final Document isMasterReply) {
583 final int oldValue = myMaxBatchedWriteOperations;
584
585 final NumericElement maxSize = isMasterReply.findFirst(NUMERIC_TYPE,
586 MAX_BATCHED_WRITE_OPERATIONS_PROP);
587 if (maxSize != null) {
588 myMaxBatchedWriteOperations = maxSize.getIntValue();
589 }
590
591 myEventSupport.firePropertyChange(MAX_BATCHED_WRITE_OPERATIONS_PROP,
592 oldValue, myMaxBatchedWriteOperations);
593 }
594
595 /**
596 * Updates the canonical name for the server based on the response to the
597 * {@code ismaster} command.
598 *
599 * @param isMasterReply
600 * The reply to the {@code ismaster} command.
601 */
602 private void updateName(final Document isMasterReply) {
603 final String oldValue = myCanonicalName;
604
605 final Element element = isMasterReply.findFirst("me");
606 if (element != null) {
607 final String name = element.getValueAsString();
608 if ((name != null) && !myCanonicalName.equals(name)) {
609 myCanonicalName = name;
610 }
611 }
612
613 myEventSupport.firePropertyChange(CANONICAL_NAME_PROP, oldValue,
614 myCanonicalName);
615 }
616
617 /**
618 * Extract the number of seconds this Server is behind the primary by
619 * comparing its latest optime with that of the absolute latest optime.
620 * <p>
621 * To account for idle servers we use the optime for each server and assign
622 * a value of zero to the "latest" optime and then subtract the remaining
623 * servers from that optime.
624 * </p>
625 * <p>
626 * Lastly, the state of the server is also checked and the seconds behind is
627 * set to {@link Double#MAX_VALUE} if not in the primary (
628 * {@value #PRIMARY_STATE}) or secondary ({@value #SECONDARY_STATE}).
629 * </p>
630 *
631 * @param replicaStateDoc
632 * The document to extract the seconds behind from.
633 */
634 private void updateSecondsBehind(final Document replicaStateDoc) {
635 final State oldValue = myState;
636
637 final NumericElement state = replicaStateDoc.get(NUMERIC_TYPE,
638 "myState");
639 if (state != null) {
640 final int value = state.getIntValue();
641 if (value == PRIMARY_STATE) {
642 myState = State.WRITABLE;
643 mySecondsBehind = 0;
644 }
645 else if (value == SECONDARY_STATE) {
646 myState = State.READ_ONLY;
647
648 TimestampElement serverTimestamp = null;
649 final StringElement expectedName = new StringElement("name",
650 myCanonicalName);
651 for (final DocumentElement member : replicaStateDoc.find(
652 DOCUMENT_TYPE, "members", ".*")) {
653 if (expectedName.equals(member.get("name"))
654 && (member.get(TIMESTAMP_TYPE, "optimeDate") != null)) {
655
656 serverTimestamp = member.get(TIMESTAMP_TYPE,
657 "optimeDate");
658 }
659 }
660
661 if (serverTimestamp != null) {
662 TimestampElement latestTimestamp = serverTimestamp;
663 for (final TimestampElement time : replicaStateDoc.find(
664 TIMESTAMP_TYPE, "members", ".*", "optimeDate")) {
665 if (latestTimestamp.getTime() < time.getTime()) {
666 latestTimestamp = time;
667 }
668 }
669
670 final double msBehind = latestTimestamp.getTime()
671 - serverTimestamp.getTime();
672 mySecondsBehind = (msBehind / TimeUnit.SECONDS.toMillis(1));
673 }
674 }
675 else {
676 // "myState" != 1 and "myState" != 2
677 mySecondsBehind = Double.MAX_VALUE;
678 myState = State.UNAVAILABLE;
679 }
680 }
681
682 myEventSupport.firePropertyChange(STATE_PROP, oldValue, myState);
683 }
684
685 /**
686 * Extract the if the result implies that the server is writable.
687 *
688 * @param isMasterReply
689 * The document to extract the seconds behind from.
690 */
691 private void updateState(final Document isMasterReply) {
692 final State oldValue = myState;
693
694 BooleanElement element = isMasterReply.findFirst(BooleanElement.class,
695 "ismaster");
696 if (element != null) {
697 if (element.getValue()) {
698 myState = State.WRITABLE;
699 mySecondsBehind = 0.0;
700 }
701 else {
702 element = isMasterReply.findFirst(BooleanElement.class,
703 "secondary");
704 if ((element != null) && element.getValue()) {
705 myState = State.READ_ONLY;
706 // Check the seconds behind for default values.
707 // This protects from not being able to get the replica set
708 // status due to permissions.
709 if ((mySecondsBehind == Double.MAX_VALUE)
710 || (mySecondsBehind == Integer.MAX_VALUE)) {
711 mySecondsBehind = 0.0;
712 }
713 }
714 else {
715 myState = State.UNAVAILABLE;
716 }
717 }
718 }
719
720 myEventSupport.firePropertyChange(STATE_PROP, oldValue, myState);
721 }
722
723 /**
724 * Extract any tags from the reply.
725 *
726 * @param isMasterReply
727 * The reply to the {@code ismaster} command.
728 */
729 private void updateTags(final Document isMasterReply) {
730 final Document oldValue = myTags;
731
732 Document tags = isMasterReply.findFirst(DOCUMENT_TYPE, TAGS_PROP);
733 if (tags != null) {
734 // Strip to a pure Document from a DocumentElement.
735 tags = BuilderFactory.start(tags.asDocument()).build();
736 if (tags.getElements().isEmpty()) {
737 myTags = null;
738 }
739 else if (!tags.equals(myTags)) {
740 myTags = tags;
741 }
742 }
743
744 myEventSupport.firePropertyChange(TAGS_PROP, oldValue, myTags);
745 }
746
747 /**
748 * Extract any {@code versionArray} from the reply.
749 *
750 * @param buildInfoReply
751 * The reply to the {@code buildinfo} command.
752 */
753 private void updateVersion(final Document buildInfoReply) {
754 final Version oldValue = myVersion;
755
756 final List<NumericElement> versionElements = buildInfoReply.find(
757 NUMERIC_TYPE, "versionArray", ".*");
758 if (!versionElements.isEmpty()) {
759 myVersion = Version.parse(versionElements);
760 myLastVersionUpdate = System.currentTimeMillis();
761 }
762 else {
763 // Use the String version if present.
764 final StringElement stringVersion = buildInfoReply.findFirst(
765 STRING_TYPE, "version");
766 if (stringVersion != null) {
767 myVersion = Version.parse(stringVersion.getValue());
768 myLastVersionUpdate = System.currentTimeMillis();
769 }
770 else {
771 // Use the wire version if present.
772 final NumericElement wireVersion = buildInfoReply.findFirst(
773 NUMERIC_TYPE, "maxWireVersion");
774 if (wireVersion != null) {
775 final Version version = Version.forWireVersion(wireVersion
776 .getIntValue());
777
778 // Don't want to update the version if we are getting the
779 // value
780 // some other way since the wire protocol version requires
781 // interpretation and really just provides a "floor"
782 // version.
783 // Check for an unknown or lower version.
784 if (oldValue.equals(Version.UNKNOWN)
785 || (oldValue.compareTo(version) < 0)) {
786 myVersion = version;
787 // Don't update the myLastVersionUpdate time so we still
788 // try and get the precise version.
789 }
790 }
791 }
792 }
793
794 myEventSupport.firePropertyChange(VERSION_PROP, oldValue, myVersion);
795 }
796
797 /**
798 * State provides the possible sttes for a server within the MongoDB
799 * cluster.
800 *
801 * @api.no This class is <b>NOT</b> part of the drivers API. This class may
802 * be mutated in incompatible ways between any two releases of the
803 * driver.
804 * @copyright 2013, Allanbank Consulting, Inc., All Rights Reserved
805 */
806 public enum State {
807 /**
808 * We can send reads to the server. It is running, we can connect to it
809 * and is a secondary in the replica set.
810 */
811 READ_ONLY,
812
813 /** We cannot connect to the server. */
814 UNAVAILABLE,
815
816 /**
817 * A transient state for the server. We have either never connected to
818 * the server or have lost all of the connections to the server.
819 */
820 UNKNOWN,
821
822 /**
823 * We can send writes to the server. It is running, we can connect to it
824 * and is either a stand-alone instance, the primary in the replica set
825 * or a mongos.
826 */
827 WRITABLE;
828 }
829 }