1 /*
2 * #%L
3 * Durability.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.io.Serializable;
23 import java.io.StringWriter;
24
25 import com.allanbank.mongodb.bson.Document;
26 import com.allanbank.mongodb.bson.Element;
27 import com.allanbank.mongodb.bson.NumericElement;
28 import com.allanbank.mongodb.bson.builder.BuilderFactory;
29 import com.allanbank.mongodb.bson.builder.DocumentBuilder;
30 import com.allanbank.mongodb.bson.element.JsonSerializationVisitor;
31 import com.allanbank.mongodb.bson.element.StringElement;
32 import com.allanbank.mongodb.bson.element.SymbolElement;
33 import com.allanbank.mongodb.bson.impl.ImmutableDocument;
34 import com.allanbank.mongodb.bson.json.Json;
35 import com.allanbank.mongodb.error.JsonParseException;
36
37 /**
38 * Represents the required durability of writes (inserts, updates, and deletes)
39 * on the server.
40 * <ul>
41 * <li>The lowest durability ({@link #NONE}) has no guarantees that the data
42 * will survive a catastrophic server failure.
43 * <li>The next level of durability ({@link #ACK}) ensures that the data has
44 * been received by the server.
45 * <li>The next level of durability ({@link #journalDurable(int)}) ensures that
46 * the data is written to the server's journal before returning.
47 * <li>Next level ({@link #fsyncDurable(int)}) is to ensure that the data has
48 * been fsync()'d to the server's disk.
49 * <li>For the highest level of durability ({@link #replicaDurable(int)}), the
50 * server ensure that the data has been received by 1 to
51 * {@link #replicaDurable(int, int) N} replicas in the replica set. Fine grain
52 * control can be achieved by specifying a {@link #replicaDurable(String, int)
53 * replication mode} instead of a count.</li>
54 * </ul>
55 * <p>
56 * Generally, increasing the level of durability decreases performance.
57 * </p>
58 *
59 * @see <a href="http://www.mongodb.org/display/DOCS/Data+Center+Awareness">Data
60 * Center Awareness</a>
61 * @api.yes This class is part of the driver's API. Public and protected members
62 * will be deprecated for at least 1 non-bugfix release (version
63 * numbers are <major>.<minor>.<bugfix>) before being
64 * removed or modified.
65 * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
66 */
67 public class Durability implements Serializable {
68
69 /** The durability that says no durability is required. */
70 public final static Durability ACK = new Durability(true, false, false, 1,
71 null, 0);
72
73 /**
74 * Built in replication mode indicating that more than 50% of the MongoDB
75 * replica set servers have received a write.
76 */
77 public final static String MAJORITY_MODE = "majority";
78
79 /** The durability that says no durability is required. */
80 public final static Durability NONE = new Durability(false, false, false,
81 0, null, 0);
82
83 /** Serialization version for the class. */
84 private static final long serialVersionUID = -6474266523435876385L;
85
86 /**
87 * Creates an fsync() durability.
88 * <p>
89 * Will cause the server to wait for the write to be sync'd to disk. If the
90 * server is running with journaling enabled then only the journal will have
91 * been sync'd to disk. If running without journaling enabled then will wait
92 * for all data files to be sync'd to disk.
93 *
94 * @param waitTimeoutMillis
95 * The number of milliseconds to wait for the durability
96 * requirements to be satisfied.
97 * @return A durability that will ensure that the data has been fsync()'d to
98 * the server's disk.
99 */
100 public static Durability fsyncDurable(final int waitTimeoutMillis) {
101 return new Durability(true, false, 0, waitTimeoutMillis);
102 }
103
104 /**
105 * Creates an journal durability.
106 * <p>
107 * Will cause the server to wait for the write to be sync'd to disk as part
108 * of the journal. As of MongoDB 2.6 this mode will cause a TBD exception to
109 * be thrown if the server is configured without journaling enabled. Prior
110 * to MongoDB 2.6 this mode would silently degrade to {@link #ACK}.
111 * </p>
112 *
113 * @param waitTimeoutMillis
114 * The number of milliseconds to wait for the durability
115 * requirements to be satisfied.
116 * @return A durability that will ensure the data is written to the server's
117 * journal before returning.
118 */
119 public static Durability journalDurable(final int waitTimeoutMillis) {
120 return new Durability(false, true, 0, waitTimeoutMillis);
121 }
122
123 /**
124 * Creates a multiple replica durability.
125 *
126 * @param ensureJournaled
127 * If true then ensure that the write has been committed to the
128 * journal in addition to replicated.
129 * @param minimumReplicas
130 * The minimum number of replicas to wait for.
131 * @param waitTimeoutMillis
132 * The number of milliseconds to wait for the durability
133 * requirements to be satisfied.
134 * @return A durability that will ensure the data is written to at least
135 * <tt>minimumReplicas</tt> of server's replicas before returning.
136 */
137 public static Durability replicaDurable(final boolean ensureJournaled,
138 final int minimumReplicas, final int waitTimeoutMillis) {
139 return new Durability(false, ensureJournaled, minimumReplicas,
140 waitTimeoutMillis);
141 }
142
143 /**
144 * Creates a multiple replica durability.
145 *
146 * @param ensureJournaled
147 * If true then ensure that the write has been committed to the
148 * journal in addition to replicated.
149 * @param waitForReplicasByMode
150 * If the value is non-<code>null</code> then wait for the
151 * specified replication mode configured on the server. A
152 * built-in mode of {@link #MAJORITY_MODE} is also supported.
153 * @param waitTimeoutMillis
154 * The number of milliseconds to wait for the durability
155 * requirements to be satisfied.
156 * @return A durability that will ensure the data is written to at least
157 * <tt>minimumReplicas</tt> of server's replicas before returning.
158 */
159 public static Durability replicaDurable(final boolean ensureJournaled,
160 final String waitForReplicasByMode, final int waitTimeoutMillis) {
161 return new Durability(false, ensureJournaled, waitForReplicasByMode,
162 waitTimeoutMillis);
163 }
164
165 /**
166 * Creates a single replica durability. This is a 'w' value of 2.
167 *
168 * @param waitTimeoutMillis
169 * The number of milliseconds to wait for the durability
170 * requirements to be satisfied.
171 * @return A durability that will ensure the data is written to at least one
172 * of server's replicas before returning.
173 */
174 public static Durability replicaDurable(final int waitTimeoutMillis) {
175 return new Durability(false, false, 2, waitTimeoutMillis);
176 }
177
178 /**
179 * Creates a multiple replica durability.
180 *
181 * @param minimumReplicas
182 * The minimum number of replicas to wait for.
183 * @param waitTimeoutMillis
184 * The number of milliseconds to wait for the durability
185 * requirements to be satisfied.
186 * @return A durability that will ensure the data is written to at least
187 * <tt>minimumReplicas</tt> of server's replicas before returning.
188 */
189 public static Durability replicaDurable(final int minimumReplicas,
190 final int waitTimeoutMillis) {
191 return new Durability(false, false, minimumReplicas, waitTimeoutMillis);
192 }
193
194 /**
195 * Creates a multiple replica durability.
196 *
197 * @param waitForReplicasByMode
198 * If the value is non-<code>null</code> then wait for the
199 * specified replication mode configured on the server. A
200 * built-in mode of {@link #MAJORITY_MODE} is also supported.
201 * @param waitTimeoutMillis
202 * The number of milliseconds to wait for the durability
203 * requirements to be satisfied.
204 * @return A durability that will ensure the data is written to at least
205 * <tt>minimumReplicas</tt> of server's replicas before returning.
206 */
207 public static Durability replicaDurable(final String waitForReplicasByMode,
208 final int waitTimeoutMillis) {
209 return new Durability(false, false, waitForReplicasByMode,
210 waitTimeoutMillis);
211 }
212
213 /**
214 * Converts a string into a Durability, if possible.
215 * <p>
216 * Two forms of strings can be converted:
217 * <ul>
218 * <li>A name of the constant (ignoring case):
219 * <ul>
220 * <li>{@code ACK}</li>
221 * <li>{@code NONE}</li>
222 * <li>{@code SAFE} - for compatibility with the MongoDB Inc. driver,
223 * returns {@link #ACK}.</li>
224 * </ul>
225 * </li>
226 * <li>A JSON document representation of the Durability. The following
227 * fields are allowed in the document and the values for each should match
228 * those for a {@code getlasterror} command:
229 * <ul>
230 * <li>{@code w}</li>
231 * <li>{@code wtimeout}</li>
232 * <li>{@code fsync}</li>
233 * <li>{@code j}</li>
234 * <li>{@code getlasterror}</li>
235 * </ul>
236 * If present the {@code getlasterror} field is ignored. An example JSON
237 * document might look like: <blockquote>
238 *
239 * <pre>
240 * <code>
241 * { w : 'majority', wtimeout : 10000 }
242 * </code>
243 * </pre>
244 *
245 * <blockquote></li>
246 * </ul>
247 * </p>
248 * <p>
249 * If the string is not parse-able in either of these forms then null is
250 * returned.
251 * </p>
252 *
253 * @param value
254 * The string representation of the Durability.
255 * @return The Durability represented by the string.
256 */
257 public static Durability valueOf(final String value) {
258
259 Durability result = null;
260
261 if ("ACK".equalsIgnoreCase(value) || "SAFE".equalsIgnoreCase(value)) {
262 result = ACK;
263 }
264 else if ("NONE".equalsIgnoreCase(value)) {
265 result = NONE;
266 }
267 else {
268 // Try and parse as JSON.
269 try {
270 boolean waitForReply = false;
271 boolean waitForFsync = false;
272 boolean waitForJournal = false;
273 int waitForReplicas = 0;
274 String waitForReplicasByMode = null;
275 int waitTimeoutMillis = 0;
276
277 final Document d = Json.parse(value);
278 for (final Element e : d) {
279 // Skip the getlasterror element.
280 if (!"getlasterror".equalsIgnoreCase(e.getName())) {
281 if ("w".equalsIgnoreCase(e.getName())) {
282 waitForReply = true;
283 if (e instanceof NumericElement) {
284 waitForReplicas = ((NumericElement) e)
285 .getIntValue();
286 }
287 else if (e instanceof StringElement) {
288 waitForReplicasByMode = ((StringElement) e)
289 .getValue();
290 }
291 else if (e instanceof SymbolElement) {
292 waitForReplicasByMode = ((SymbolElement) e)
293 .getSymbol();
294 }
295 else {
296 // Unknown 'w' value type.
297 return null;
298 }
299 }
300 else if ("wtimeout".equalsIgnoreCase(e.getName())) {
301 if (e instanceof NumericElement) {
302 waitTimeoutMillis = ((NumericElement) e)
303 .getIntValue();
304 }
305 else {
306 // Unknown 'wtimeout' value type.
307 return null;
308 }
309 }
310 else if ("fsync".equalsIgnoreCase(e.getName())) {
311 waitForReply = true;
312 waitForFsync = true;
313 }
314 else if ("j".equalsIgnoreCase(e.getName())) {
315 waitForReply = true;
316 waitForJournal = true;
317 }
318 else {
319 // Unknown field.
320 return null;
321 }
322 }
323 }
324
325 result = new Durability(waitForReply, waitForFsync,
326 waitForJournal, waitForReplicas, waitForReplicasByMode,
327 waitTimeoutMillis);
328 }
329 catch (final JsonParseException error) {
330 // Ignore and return null.
331 error.getCause(); // Shhh PMD.
332 }
333 }
334
335 return result;
336 }
337
338 /** The durability in document form. */
339 private Document myAsDocument;
340
341 /**
342 * True if the durability requires that the response wait for an fsync() of
343 * the data to complete, false otherwise.
344 */
345 private final boolean myWaitForFsync;
346
347 /**
348 * True if if the durability requires that the response wait for the data to
349 * be written to the server's journal, false otherwise.
350 */
351 private final boolean myWaitForJournal;
352
353 /**
354 * If the value is value greater than zero the durability requires that the
355 * response wait for the data to be received by a replica and the number of
356 * replicas of the data to wait for.
357 */
358 private final int myWaitForReplicas;
359
360 /**
361 * If the value is non-<code>null</code> then wait for the specified
362 * replication mode configured on the server. A built-in mode of
363 * {@link #MAJORITY_MODE} is also supported.
364 *
365 * @see <a
366 * href="http://www.mongodb.org/display/DOCS/Data+Center+Awareness">Data
367 * Center Awareness</a>
368 */
369 private final String myWaitForReplicasByMode;
370
371 /**
372 * True if the durability requires that the response wait for a reply from
373 * the server but no special server processing.
374 */
375 private final boolean myWaitForReply;
376
377 /**
378 * The number of milliseconds to wait for the durability requirements to be
379 * satisfied.
380 */
381 private final int myWaitTimeoutMillis;
382
383 /**
384 * Create a new Durability.
385 *
386 * @param waitForFsync
387 * True if the durability requires that the response wait for an
388 * fsync() of the data to complete, false otherwise.
389 * @param waitForJournal
390 * True if if the durability requires that the response wait for
391 * the data to be written to the server's journal, false
392 * otherwise.
393 * @param waitForReplicas
394 * If the value is value greater than zero the durability
395 * requires that the response wait for the data to be received by
396 * a replica and the number of replicas of the data to wait for.
397 * @param waitTimeoutMillis
398 * The number of milliseconds to wait for the durability
399 * requirements to be satisfied.
400 */
401 protected Durability(final boolean waitForFsync,
402 final boolean waitForJournal, final int waitForReplicas,
403 final int waitTimeoutMillis) {
404 this(true, waitForFsync, waitForJournal, waitForReplicas, null,
405 waitTimeoutMillis);
406 }
407
408 /**
409 * Create a new Durability.
410 *
411 * @param waitForFsync
412 * True if the durability requires that the response wait for an
413 * fsync() of the data to complete, false otherwise.
414 * @param waitForJournal
415 * True if if the durability requires that the response wait for
416 * the data to be written to the server's journal, false
417 * otherwise.
418 * @param waitForReplicasByMode
419 * If the value is non-<code>null</code> then wait for the
420 * specified replication mode configured on the server. A
421 * built-in mode of {@link #MAJORITY_MODE} is also supported.
422 * @param waitTimeoutMillis
423 * The number of milliseconds to wait for the durability
424 * requirements to be satisfied.
425 */
426 protected Durability(final boolean waitForFsync,
427 final boolean waitForJournal, final String waitForReplicasByMode,
428 final int waitTimeoutMillis) {
429 this(true, waitForFsync, waitForJournal, 0, waitForReplicasByMode,
430 waitTimeoutMillis);
431 }
432
433 /**
434 * Create a new Durability.
435 *
436 * @param waitForReply
437 * True if the durability requires a reply from the server.
438 * @param waitForFsync
439 * True if the durability requires that the response wait for an
440 * fsync() of the data to complete, false otherwise.
441 * @param waitForJournal
442 * True if if the durability requires that the response wait for
443 * the data to be written to the server's journal, false
444 * otherwise.
445 * @param waitForReplicas
446 * If the value is value greater than zero the durability
447 * requires that the response wait for the data to be received by
448 * a replica and the number of replicas of the data to wait for.
449 * @param waitForReplicasByMode
450 * If the value is non-<code>null</code> then wait for the
451 * specified replication mode configured on the server. A
452 * built-in mode of {@link #MAJORITY_MODE} is also supported.
453 * @param waitTimeoutMillis
454 * The number of milliseconds to wait for the durability
455 * requirements to be satisfied.
456 */
457 private Durability(final boolean waitForReply, final boolean waitForFsync,
458 final boolean waitForJournal, final int waitForReplicas,
459 final String waitForReplicasByMode, final int waitTimeoutMillis) {
460 myWaitForReply = waitForReply;
461 myWaitForFsync = waitForFsync;
462 myWaitForJournal = waitForJournal;
463 myWaitForReplicas = waitForReplicas;
464 myWaitTimeoutMillis = waitTimeoutMillis;
465 myWaitForReplicasByMode = waitForReplicasByMode;
466 }
467
468 /**
469 * Returns a suitable getlasterror command's document.
470 *
471 * @return The getlasterror command's document.
472 */
473 public Document asDocument() {
474 if (myAsDocument == null) {
475 final DocumentBuilder builder = BuilderFactory.start();
476 builder.addInteger("getlasterror", 1);
477 if (isWaitForJournal()) {
478 builder.addBoolean("j", true);
479 }
480 if (isWaitForFsync()) {
481 builder.addBoolean("fsync", true);
482 }
483 if (getWaitTimeoutMillis() > 0) {
484 builder.addInteger("wtimeout", getWaitTimeoutMillis());
485 }
486
487 if (getWaitForReplicas() >= 1) {
488 builder.addInteger("w", getWaitForReplicas());
489 }
490 else if (getWaitForReplicasByMode() != null) {
491 builder.addString("w", getWaitForReplicasByMode());
492 }
493
494 myAsDocument = new ImmutableDocument(builder);
495 }
496 return myAsDocument;
497 }
498
499 /**
500 * Determines if the passed object is of this same type as this object and
501 * if so that its fields are equal.
502 *
503 * @param object
504 * The object to compare to.
505 *
506 * @see java.lang.Object#equals(java.lang.Object)
507 */
508 @Override
509 public boolean equals(final Object object) {
510 boolean result = false;
511 if (this == object) {
512 result = true;
513 }
514 else if ((object != null) && (getClass() == object.getClass())) {
515 final Durability other = (Durability) object;
516
517 result = (myWaitForReply == other.myWaitForReply)
518 && (myWaitForFsync == other.myWaitForFsync)
519 && (myWaitForJournal == other.myWaitForJournal)
520 && (myWaitForReplicas == other.myWaitForReplicas)
521 && (myWaitTimeoutMillis == other.myWaitTimeoutMillis)
522 && nullSafeEquals(myWaitForReplicasByMode,
523 other.myWaitForReplicasByMode);
524 }
525 return result;
526 }
527
528 /**
529 * Returns if (value greater than zero) the durability requires that the
530 * response wait for the data to be received by a replica and the number of
531 * replicas of the data to wait for.
532 *
533 * @return If (value greater than zero) the durability requires that the
534 * response wait for the data to be received by a replica and the
535 * number of replicas of the data to wait for.
536 */
537 public int getWaitForReplicas() {
538 return myWaitForReplicas;
539 }
540
541 /**
542 * If the value is non-<code>null</code> then wait for the specified
543 * replication mode configured on the server. A built-in mode of
544 * {@link #MAJORITY_MODE} is also supported.
545 *
546 * @return If the value is non-null then wait for the specified replication
547 * mode configured on the server.
548 * @see <a
549 * href="http://www.mongodb.org/display/DOCS/Data+Center+Awareness">Data
550 * Center Awareness</a>
551 */
552 public String getWaitForReplicasByMode() {
553 return myWaitForReplicasByMode;
554 }
555
556 /**
557 * Returns the number of milliseconds to wait for the durability
558 * requirements to be satisfied.
559 *
560 * @return The number of milliseconds to wait for the durability
561 * requirements to be satisfied.
562 */
563 public int getWaitTimeoutMillis() {
564 return myWaitTimeoutMillis;
565 }
566
567 /**
568 * Computes a reasonable hash code.
569 *
570 * @return The hash code value.
571 */
572 @Override
573 public int hashCode() {
574 int result = 1;
575 result = (31 * result) + (myWaitForReply ? 1 : 3);
576 result = (31 * result) + (myWaitForFsync ? 1 : 3);
577 result = (31 * result) + (myWaitForJournal ? 1 : 3);
578 result = (31 * result)
579 + ((myWaitForReplicasByMode != null) ? myWaitForReplicasByMode
580 .hashCode() : 3);
581 result = (31 * result) + myWaitForReplicas;
582 result = (31 * result) + myWaitTimeoutMillis;
583 return result;
584 }
585
586 /**
587 * Returns if the durability requires that the response wait for an fsync()
588 * of the data on the server to complete.
589 *
590 * @return True if the durability requires that the response wait for an
591 * fsync() of the data to complete, false otherwise.
592 */
593 public boolean isWaitForFsync() {
594 return myWaitForFsync;
595 }
596
597 /**
598 * Returns if the durability requires that the response wait for the data to
599 * be written to the server's journal.
600 *
601 * @return True if if the durability requires that the response wait for the
602 * data to be written to the server's journal, false otherwise.
603 */
604 public boolean isWaitForJournal() {
605 return myWaitForJournal;
606 }
607
608 /**
609 * Returns if the durability requires that the response wait for a reply
610 * from the server but potentially no special server processing.
611 *
612 * @return True if the durability requires that the response wait for a
613 * reply from the server but potentially no special server
614 * processing.
615 */
616 public boolean isWaitForReply() {
617 return myWaitForReply;
618 }
619
620 /**
621 * {@inheritDoc}
622 * <p>
623 * Overriden to return the durability as JSON text.
624 * </p>
625 */
626 @Override
627 public String toString() {
628 String result;
629 if (NONE.equals(this)) {
630 result = "NONE";
631 }
632 else if (ACK.equals(this)) {
633 result = "ACK";
634 }
635 else {
636 // Render as a JSON Document on a single line.
637 final StringWriter sink = new StringWriter();
638 final JsonSerializationVisitor visitor = new JsonSerializationVisitor(
639 sink, true);
640 asDocument().accept(visitor);
641
642 result = sink.toString();
643 }
644 return result;
645 }
646
647 /**
648 * Does a null safe equals comparison.
649 *
650 * @param rhs
651 * The right-hand-side of the comparison.
652 * @param lhs
653 * The left-hand-side of the comparison.
654 * @return True if the rhs equals the lhs. Note: nullSafeEquals(null, null)
655 * returns true.
656 */
657 protected boolean nullSafeEquals(final Object rhs, final Object lhs) {
658 return (rhs == lhs) || ((rhs != null) && rhs.equals(lhs));
659 }
660
661 /**
662 * Hook into serialization to replace <tt>this</tt> object with the local
663 * {@link #ACK} or {@link #NONE} instance as appropriate.
664 *
665 * @return Either the {@link #ACK} or {@link #NONE} instance if
666 * <tt>this</tt> instance equals one of those instances otherwise
667 * <tt>this</tt> instance.
668 */
669 private Object readResolve() {
670 if (this.equals(ACK)) {
671 return ACK;
672 }
673 else if (this.equals(NONE)) {
674 return NONE;
675 }
676 else {
677 return this;
678 }
679 }
680 }