View Javadoc
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 &lt;major&gt;.&lt;minor&gt;.&lt;bugfix&gt;) 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 }