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 }