1 /* 2 * #%L 3 * FindAndModify.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 21 package com.allanbank.mongodb.builder; 22 23 import static com.allanbank.mongodb.util.Assertions.assertNotNull; 24 import static com.allanbank.mongodb.util.Assertions.assertThat; 25 26 import java.util.concurrent.TimeUnit; 27 28 import com.allanbank.mongodb.MongoCollection; 29 import com.allanbank.mongodb.Version; 30 import com.allanbank.mongodb.bson.Document; 31 import com.allanbank.mongodb.bson.DocumentAssignable; 32 import com.allanbank.mongodb.bson.builder.BuilderFactory; 33 import com.allanbank.mongodb.bson.builder.DocumentBuilder; 34 import com.allanbank.mongodb.bson.element.IntegerElement; 35 36 /** 37 * Represents the state of a single {@link MongoCollection#findAndModify} 38 * command. Objects of this class are created using the nested {@link Builder}. 39 * 40 * @api.yes This class is part of the driver's API. Public and protected members 41 * will be deprecated for at least 1 non-bugfix release (version 42 * numbers are <major>.<minor>.<bugfix>) before being 43 * removed or modified. 44 * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved 45 */ 46 public class FindAndModify { 47 /** An (empty) query document to find all documents. */ 48 public static final Document ALL = MongoCollection.ALL; 49 50 /** 51 * The first version of MongoDB to support the {@code findAndModify} command 52 * with the ability to limit the execution time on the server. 53 */ 54 public static final Version MAX_TIMEOUT_VERSION = Find.MAX_TIMEOUT_VERSION; 55 56 /** An (empty) update document to perform no actual modifications. */ 57 public static final Document NONE = MongoCollection.NONE; 58 59 /** 60 * Creates a new builder for a {@link FindAndModify}. 61 * 62 * @return The builder to construct a {@link FindAndModify}. 63 */ 64 public static Builder builder() { 65 return new Builder(); 66 } 67 68 /** The subset of fields to retrieve from the matched document. */ 69 private final Document myFields; 70 71 /** The maximum amount of time to allow the command to run. */ 72 private final long myMaximumTimeMilliseconds; 73 74 /** The query to locate the document to update. */ 75 private final Document myQuery; 76 77 /** Set to a true to remove the object before returning */ 78 private final boolean myRemove; 79 80 /** 81 * Set to true if you want to return the modified object rather than the 82 * original. Ignored for remove. 83 */ 84 private final boolean myReturnNew; 85 86 /** 87 * If multiple docs match, choose the first one in the specified sort order 88 * as the object to manipulate. 89 */ 90 private final Document mySort; 91 92 /** The updates to be applied to the document. */ 93 private final Document myUpdate; 94 95 /** If true create the document if it doesn't exist. */ 96 private final boolean myUpsert; 97 98 /** 99 * Create a new FindAndModify. 100 * 101 * @param builder 102 * The builder to copy from. 103 */ 104 protected FindAndModify(final Builder builder) { 105 assertNotNull(builder.myQuery, 106 "The findAndModify's query document cannot be null or empty."); 107 assertNotNull(builder.myQuery, 108 "The findAndModify's query document cannot be null or empty."); 109 assertThat((builder.myUpdate != null) || builder.myRemove, 110 "The findAndModify must have an update document or be a remove."); 111 112 myQuery = builder.myQuery; 113 myUpdate = builder.myUpdate; 114 mySort = builder.mySort; 115 myFields = builder.myFields; 116 myUpsert = builder.myUpsert; 117 myReturnNew = builder.myReturnNew; 118 myRemove = builder.myRemove; 119 myMaximumTimeMilliseconds = builder.myMaximumTimeMilliseconds; 120 } 121 122 /** 123 * Returns the subset of fields to retrieve from the matched document. 124 * 125 * @return The subset of fields to retrieve from the matched document. 126 */ 127 public Document getFields() { 128 return myFields; 129 } 130 131 /** 132 * Returns the maximum amount of time to allow the command to run on the 133 * Server before it is aborted. 134 * 135 * @return The maximum amount of time to allow the command to run on the 136 * Server before it is aborted. 137 * 138 * @since MongoDB 2.6 139 */ 140 public long getMaximumTimeMilliseconds() { 141 return myMaximumTimeMilliseconds; 142 } 143 144 /** 145 * Returns the query to locate the document to update. 146 * 147 * @return The query to locate the document to update. 148 */ 149 public Document getQuery() { 150 return myQuery; 151 } 152 153 /** 154 * Returns the sort to apply if multiple docs match, choose the first one as 155 * the object to manipulate. 156 * 157 * @return The sort to apply if multiple docs match, choose the first one as 158 * the object to manipulate. 159 */ 160 public Document getSort() { 161 return mySort; 162 } 163 164 /** 165 * Returns the updates to be applied to the document. 166 * 167 * @return The updates to be applied to the document. 168 */ 169 public Document getUpdate() { 170 return myUpdate; 171 } 172 173 /** 174 * Returns true if the document should be removed. 175 * 176 * @return True if the document should be removed. 177 */ 178 public boolean isRemove() { 179 return myRemove; 180 } 181 182 /** 183 * Returns true if the updated document should be returned instead of the 184 * document before the update. 185 * 186 * @return True if the updated document should be returned instead of the 187 * document before the update. 188 */ 189 public boolean isReturnNew() { 190 return myReturnNew; 191 } 192 193 /** 194 * Returns true to create the document if it doesn't exist. 195 * 196 * @return True to create the document if it doesn't exist. 197 */ 198 public boolean isUpsert() { 199 return myUpsert; 200 } 201 202 /** 203 * Helper for creating immutable {@link FindAndModify} commands. 204 * 205 * @api.yes This class is part of the driver's API. Public and protected 206 * members will be deprecated for at least 1 non-bugfix release 207 * (version numbers are <major>.<minor>.<bugfix>) 208 * before being removed or modified. 209 * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved 210 */ 211 public static class Builder { 212 /** Retrieve a subset of fields from the matched document. */ 213 protected Document myFields; 214 215 /** The maximum amount of time to allow the command to run. */ 216 protected long myMaximumTimeMilliseconds; 217 218 /** A query to locate the document to update. */ 219 protected Document myQuery; 220 221 /** Set to a true to remove the object before returning */ 222 protected boolean myRemove; 223 224 /** 225 * Set to true if you want to return the modified object rather than the 226 * original. Ignored for remove. 227 */ 228 protected boolean myReturnNew; 229 230 /** 231 * If multiple docs match, choose the first one in the specified sort 232 * order as the object to manipulate. 233 */ 234 protected Document mySort; 235 236 /** Updates to be applied to the document. */ 237 protected Document myUpdate; 238 239 /** Create object if it doesn't exist. */ 240 protected boolean myUpsert; 241 242 /** 243 * Creates a new Builder. 244 */ 245 public Builder() { 246 reset(); 247 } 248 249 /** 250 * Constructs a new {@link FindAndModify} object from the state of the 251 * builder. 252 * 253 * @return The new {@link FindAndModify} object. 254 */ 255 public FindAndModify build() { 256 return new FindAndModify(this); 257 } 258 259 /** 260 * Sets the subset of fields to retrieve from the matched document. 261 * <p> 262 * This method delegates to {@link #setFields(DocumentAssignable)}. 263 * </p> 264 * 265 * @param fields 266 * The subset of fields to retrieve from the matched 267 * document. 268 * @return This builder for chaining method calls. 269 */ 270 public Builder fields(final DocumentAssignable fields) { 271 return setFields(fields); 272 } 273 274 /** 275 * Sets the maximum number of milliseconds to allow the command to run 276 * before aborting the request on the server. 277 * <p> 278 * This method equivalent to {@link #setMaximumTimeMilliseconds(long) 279 * setMaximumTimeMilliseconds(timeLimitUnits.toMillis(timeLimit)}. 280 * </p> 281 * 282 * @param timeLimit 283 * The new maximum amount of time to allow the command to 284 * run. 285 * @param timeLimitUnits 286 * The units for the maximum amount of time to allow the 287 * command to run. 288 * 289 * @return This {@link Builder} for method call chaining. 290 * 291 * @since MongoDB 2.6 292 */ 293 public Builder maximumTime(final long timeLimit, 294 final TimeUnit timeLimitUnits) { 295 return setMaximumTimeMilliseconds(timeLimitUnits 296 .toMillis(timeLimit)); 297 } 298 299 /** 300 * Sets the query to locate the document to update. 301 * <p> 302 * This method delegates to {@link #setQuery(DocumentAssignable)}. 303 * </p> 304 * 305 * @param query 306 * The query to locate the document to update. 307 * @return This builder for chaining method calls. 308 */ 309 public Builder query(final DocumentAssignable query) { 310 return setQuery(query); 311 } 312 313 /** 314 * Sets to true if the document should be removed. 315 * <p> 316 * This method delegates to {@link #setRemove(boolean) setRemove(true)}. 317 * </p> 318 * 319 * @return This builder for chaining method calls. 320 */ 321 public Builder remove() { 322 return setRemove(true); 323 } 324 325 /** 326 * Sets to true if the document should be removed. 327 * <p> 328 * This method delegates to {@link #setRemove(boolean)}. 329 * </p> 330 * 331 * @param remove 332 * True if the document should be removed. 333 * @return This builder for chaining method calls. 334 */ 335 public Builder remove(final boolean remove) { 336 return setRemove(remove); 337 } 338 339 /** 340 * Resets the builder back to its initial state. 341 * 342 * @return This {@link Builder} for method call chaining. 343 */ 344 public Builder reset() { 345 myFields = null; 346 myQuery = null; 347 myRemove = false; 348 myReturnNew = false; 349 mySort = null; 350 myUpdate = null; 351 myUpsert = false; 352 myMaximumTimeMilliseconds = 0; 353 354 return this; 355 } 356 357 /** 358 * Sets to true if the updated document should be returned instead of 359 * the document before the update. 360 * <p> 361 * This method delegates to {@link #setReturnNew(boolean) 362 * setReturnNew(true)}. 363 * </p> 364 * 365 * @return This builder for chaining method calls. 366 */ 367 public Builder returnNew() { 368 return setReturnNew(true); 369 } 370 371 /** 372 * Sets to true if the updated document should be returned instead of 373 * the document before the update. 374 * <p> 375 * This method delegates to {@link #setReturnNew(boolean)}. 376 * </p> 377 * 378 * @param returnNew 379 * True if the updated document should be returned instead of 380 * the document before the update. 381 * @return This builder for chaining method calls. 382 */ 383 public Builder returnNew(final boolean returnNew) { 384 return setReturnNew(returnNew); 385 } 386 387 /** 388 * Sets the subset of fields to retrieve from the matched document. 389 * 390 * @param fields 391 * The subset of fields to retrieve from the matched 392 * document. 393 * @return This builder for chaining method calls. 394 */ 395 public Builder setFields(final DocumentAssignable fields) { 396 myFields = fields.asDocument(); 397 return this; 398 } 399 400 /** 401 * Sets the maximum number of milliseconds to allow the command to run 402 * before aborting the request on the server. 403 * 404 * @param maximumTimeMilliseconds 405 * The new maximum number of milliseconds to allow the 406 * command to run. 407 * @return This {@link Builder} for method call chaining. 408 * 409 * @since MongoDB 2.6 410 */ 411 public Builder setMaximumTimeMilliseconds( 412 final long maximumTimeMilliseconds) { 413 myMaximumTimeMilliseconds = maximumTimeMilliseconds; 414 return this; 415 } 416 417 /** 418 * Sets the query to locate the document to update. 419 * 420 * @param query 421 * The query to locate the document to update. 422 * @return This builder for chaining method calls. 423 */ 424 public Builder setQuery(final DocumentAssignable query) { 425 myQuery = query.asDocument(); 426 return this; 427 } 428 429 /** 430 * Sets to true if the document should be removed. 431 * 432 * @param remove 433 * True if the document should be removed. 434 * @return This builder for chaining method calls. 435 */ 436 public Builder setRemove(final boolean remove) { 437 myRemove = remove; 438 return this; 439 } 440 441 /** 442 * Sets to true if the updated document should be returned instead of 443 * the document before the update. 444 * 445 * @param returnNew 446 * True if the updated document should be returned instead of 447 * the document before the update. 448 * @return This builder for chaining method calls. 449 */ 450 public Builder setReturnNew(final boolean returnNew) { 451 myReturnNew = returnNew; 452 return this; 453 } 454 455 /** 456 * Sets the sort to apply if multiple docs match, choose the first one 457 * as the object to manipulate. 458 * 459 * @param sort 460 * The sort to apply if multiple docs match, choose the first 461 * one as the object to manipulate. 462 * @return This builder for chaining method calls. 463 */ 464 public Builder setSort(final DocumentAssignable sort) { 465 mySort = sort.asDocument(); 466 return this; 467 } 468 469 /** 470 * Sets the sort to apply if multiple docs match, choose the first one 471 * as the object to manipulate. 472 * <p> 473 * This method is intended to be used with the {@link Sort} class's 474 * static methods: <blockquote> 475 * 476 * <pre> 477 * <code> 478 * import static {@link Sort#asc(String) com.allanbank.mongodb.builder.Sort.asc}; 479 * import static {@link Sort#desc(String) com.allanbank.mongodb.builder.Sort.desc}; 480 * 481 * FindAndModify.Builder builder = new Find.Builder(); 482 * 483 * builder.setSort( asc("f"), desc("g") ); 484 * ... 485 * </code> 486 * </pre> 487 * 488 * </blockquote> 489 * 490 * @param sortFields 491 * The sort to apply if multiple docs match, choose the first 492 * one as the object to manipulate. 493 * @return This builder for chaining method calls. 494 */ 495 public Builder setSort(final IntegerElement... sortFields) { 496 final DocumentBuilder builder = BuilderFactory.start(); 497 for (final IntegerElement sortField : sortFields) { 498 builder.add(sortField); 499 } 500 mySort = builder.build(); 501 return this; 502 } 503 504 /** 505 * Sets the updates to be applied to the document. 506 * 507 * @param update 508 * The updates to be applied to the document. 509 * @return This builder for chaining method calls. 510 */ 511 public Builder setUpdate(final DocumentAssignable update) { 512 myUpdate = update.asDocument(); 513 return this; 514 } 515 516 /** 517 * Sets to true to create the document if it doesn't exist. 518 * 519 * @param upsert 520 * True to create the document if it doesn't exist. 521 * @return This builder for chaining method calls. 522 */ 523 public Builder setUpsert(final boolean upsert) { 524 myUpsert = upsert; 525 return this; 526 } 527 528 /** 529 * Sets the sort to apply if multiple docs match, choose the first one 530 * as the object to manipulate. 531 * <p> 532 * This method delegates to {@link #setSort(DocumentAssignable)}. 533 * </p> 534 * 535 * @param sort 536 * The sort to apply if multiple docs match, choose the first 537 * one as the object to manipulate. 538 * @return This builder for chaining method calls. 539 */ 540 public Builder sort(final DocumentAssignable sort) { 541 return setSort(sort); 542 } 543 544 /** 545 * Sets the sort to apply if multiple docs match, choose the first one 546 * as the object to manipulate. 547 * <p> 548 * This method delegates to {@link #setSort(IntegerElement...)}. 549 * </p> 550 * <p> 551 * This method is intended to be used with the {@link Sort} class's 552 * static methods: <blockquote> 553 * 554 * <pre> 555 * <code> 556 * import static {@link Sort#asc(String) com.allanbank.mongodb.builder.Sort.asc}; 557 * import static {@link Sort#desc(String) com.allanbank.mongodb.builder.Sort.desc}; 558 * 559 * FindAndModify.Builder builder = new Find.Builder(); 560 * 561 * builder.sort( asc("f"), desc("g") ); 562 * ... 563 * </code> 564 * </pre> 565 * 566 * </blockquote> 567 * 568 * @param sortFields 569 * The sort to apply if multiple docs match, choose the first 570 * one as the object to manipulate. 571 * @return This builder for chaining method calls. 572 */ 573 public Builder sort(final IntegerElement... sortFields) { 574 return setSort(sortFields); 575 } 576 577 /** 578 * Sets the updates to be applied to the document. 579 * <p> 580 * This method delegates to {@link #setUpdate(DocumentAssignable)}. 581 * </p> 582 * 583 * @param update 584 * The updates to be applied to the document. 585 * @return This builder for chaining method calls. 586 */ 587 public Builder update(final DocumentAssignable update) { 588 return setUpdate(update); 589 } 590 591 /** 592 * Sets to true to create the document if it doesn't exist. 593 * <p> 594 * This method delegates to {@link #setUpsert(boolean) setUpsert(true)}. 595 * </p> 596 * 597 * @return This builder for chaining method calls. 598 */ 599 public Builder upsert() { 600 return setUpsert(true); 601 } 602 603 /** 604 * Sets to true to create the document if it doesn't exist. 605 * <p> 606 * This method delegates to {@link #setUpsert(boolean)}. 607 * </p> 608 * 609 * @param upsert 610 * True to create the document if it doesn't exist. 611 * @return This builder for chaining method calls. 612 */ 613 public Builder upsert(final boolean upsert) { 614 return setUpsert(upsert); 615 } 616 } 617 }