View Javadoc
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 }