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 }