View Javadoc
1   /*
2    * #%L
3    * Update.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.client.message;
21  
22  import java.io.IOException;
23  
24  import com.allanbank.mongodb.ReadPreference;
25  import com.allanbank.mongodb.bson.Document;
26  import com.allanbank.mongodb.bson.io.BsonInputStream;
27  import com.allanbank.mongodb.bson.io.BsonOutputStream;
28  import com.allanbank.mongodb.bson.io.BufferingBsonOutputStream;
29  import com.allanbank.mongodb.bson.io.StringEncoder;
30  import com.allanbank.mongodb.client.Message;
31  import com.allanbank.mongodb.client.Operation;
32  import com.allanbank.mongodb.error.DocumentToLargeException;
33  
34  /**
35   * Message to apply an <a href=
36   * "http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-OPUPDATE"
37   * >update</a> to a document.
38   * 
39   * <pre>
40   * <code>
41   * struct OP_UPDATE {
42   *     MsgHeader header;             // standard message header
43   *     int32     ZERO;               // 0 - reserved for future use
44   *     cstring   fullCollectionName; // "dbname.collectionname"
45   *     int32     flags;              // bit vector. see below
46   *     document  selector;           // the query to select the document
47   *     document  update;             // specification of the update to perform
48   * }
49   * </code>
50   * </pre>
51   * 
52   * 
53   * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
54   *         mutated in incompatible ways between any two releases of the driver.
55   * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
56   */
57  public class Update extends AbstractMessage {
58  
59      /** Flag bit for a multi-update. */
60      public static final int MULTIUPDATE_FLAG_BIT = 0x02;
61  
62      /** Flag bit for an upsert. */
63      public static final int UPSERT_FLAG_BIT = 0x01;
64  
65      /**
66       * If true then all documents matching the query should be updated,
67       * otherwise only the first document should be updated.
68       */
69      private final boolean myMultiUpdate;
70  
71      /** The query to select the document to update. */
72      private final Document myQuery;
73  
74      /** The updates to apply to the selected document(s). */
75      private final Document myUpdate;
76  
77      /**
78       * If true then a document should be inserted if none match the query
79       * criteria.
80       */
81      private final boolean myUpsert;
82  
83      /**
84       * Creates a new Update.
85       * 
86       * @param in
87       *            The input stream to read the update message from.
88       * @throws IOException
89       *             On an error reading the update message.
90       */
91      public Update(final BsonInputStream in) throws IOException {
92          in.readInt(); // 0 - reserved.
93          init(in.readCString());
94          final int flags = in.readInt();
95          myQuery = in.readDocument();
96          myUpdate = in.readDocument();
97  
98          myUpsert = (flags & UPSERT_FLAG_BIT) == UPSERT_FLAG_BIT;
99          myMultiUpdate = (flags & MULTIUPDATE_FLAG_BIT) == MULTIUPDATE_FLAG_BIT;
100     }
101 
102     /**
103      * Creates a new Update.
104      * 
105      * @param databaseName
106      *            The name of the database.
107      * @param collectionName
108      *            The name of the collection.
109      * @param query
110      *            The query to select the document to update.
111      * @param update
112      *            The updates to apply to the selected document(s).
113      * @param multiUpdate
114      *            If true then all documents matching the query should be
115      *            updated, otherwise only the first document should be updated.
116      * @param upsert
117      *            If true then a document should be inserted if none match the
118      *            query criteria.
119      */
120     public Update(final String databaseName, final String collectionName,
121             final Document query, final Document update,
122             final boolean multiUpdate, final boolean upsert) {
123         super(databaseName, collectionName, ReadPreference.PRIMARY);
124         myQuery = query;
125         myUpdate = update;
126         myMultiUpdate = multiUpdate;
127         myUpsert = upsert;
128     }
129 
130     /**
131      * Determines if the passed object is of this same type as this object and
132      * if so that its fields are equal.
133      * 
134      * @param object
135      *            The object to compare to.
136      * 
137      * @see java.lang.Object#equals(java.lang.Object)
138      */
139     @Override
140     public boolean equals(final Object object) {
141         boolean result = false;
142         if (this == object) {
143             result = true;
144         }
145         else if ((object != null) && (getClass() == object.getClass())) {
146             final Update other = (Update) object;
147 
148             result = super.equals(object)
149                     && (myMultiUpdate == other.myMultiUpdate)
150                     && (myUpsert == other.myUpsert)
151                     && myUpdate.equals(other.myUpdate)
152                     && myQuery.equals(other.myQuery);
153         }
154         return result;
155     }
156 
157     /**
158      * {@inheritDoc}
159      * <p>
160      * Overridden to return the name of the operation: "UPDATE".
161      * </p>
162      */
163     @Override
164     public String getOperationName() {
165         return Operation.UPDATE.name();
166     }
167 
168     /**
169      * Returns the query to select the document to update.
170      * 
171      * @return The query to select the document to update.
172      */
173     public Document getQuery() {
174         return myQuery;
175     }
176 
177     /**
178      * Returns the updates to apply to the selected document(s).
179      * 
180      * @return The updates to apply to the selected document(s).
181      */
182     public Document getUpdate() {
183         return myUpdate;
184     }
185 
186     /**
187      * Computes a reasonable hash code.
188      * 
189      * @return The hash code value.
190      */
191     @Override
192     public int hashCode() {
193         int result = 1;
194         result = (31 * result) + super.hashCode();
195         result = (31 * result) + (myMultiUpdate ? 1 : 3);
196         result = (31 * result) + (myUpsert ? 1 : 3);
197         result = (31 * result) + myUpdate.hashCode();
198         result = (31 * result) + myQuery.hashCode();
199         return result;
200     }
201 
202     /**
203      * Returns true if all documents matching the query should be updated,
204      * otherwise only the first document should be updated.
205      * 
206      * @return True if all documents matching the query should be updated,
207      *         otherwise only the first document should be updated.
208      */
209     public boolean isMultiUpdate() {
210         return myMultiUpdate;
211     }
212 
213     /**
214      * Returns true if the document should be inserted if none match the query
215      * criteria.
216      * 
217      * @return True if the document should be inserted if none match the query
218      *         criteria.
219      */
220     public boolean isUpsert() {
221         return myUpsert;
222     }
223 
224     /**
225      * {@inheritDoc}
226      * <p>
227      * Overridden to return the size of the {@link Query}.
228      * </p>
229      */
230     @Override
231     public int size() {
232 
233         int size = HEADER_SIZE + 10; // See below.
234         // size += 4; // 0 - reserved.
235         size += StringEncoder.utf8Size(myDatabaseName);
236         // size += 1; // StringEncoder.utf8Size(".");
237         size += StringEncoder.utf8Size(myCollectionName);
238         // size += 1; // \0 on the CString.
239         // size += 4; // flags
240         size += myQuery.size();
241         size += myUpdate.size();
242 
243         return size;
244     }
245 
246     /**
247      * {@inheritDoc}
248      * <p>
249      * Overridden to ensure the inserted documents are not too large in
250      * aggregate.
251      * </p>
252      */
253     @Override
254     public void validateSize(final int maxDocumentSize)
255             throws DocumentToLargeException {
256         final long querySize = (myQuery != null) ? myQuery.size() : 0;
257         if (maxDocumentSize < querySize) {
258             throw new DocumentToLargeException((int) querySize,
259                     maxDocumentSize, myQuery);
260         }
261         final long updateSize = (myUpdate != null) ? myUpdate.size() : 0;
262         if (maxDocumentSize < updateSize) {
263             throw new DocumentToLargeException((int) updateSize,
264                     maxDocumentSize, myUpdate);
265         }
266     }
267 
268     /**
269      * {@inheritDoc}
270      * <p>
271      * Overridden to write the update message.
272      * </p>
273      * 
274      * @see Message#write(int, BsonOutputStream)
275      */
276     @Override
277     public void write(final int messageId, final BsonOutputStream out)
278             throws IOException {
279 
280         final int flags = computeFlags();
281 
282         int size = HEADER_SIZE;
283         size += 4; // 0 - reserved.
284         size += out.sizeOfCString(myDatabaseName, ".", myCollectionName);
285         size += 4; // flags
286         size += myQuery.size();
287         size += myUpdate.size();
288 
289         writeHeader(out, messageId, 0, Operation.UPDATE, size);
290         out.writeInt(0);
291         out.writeCString(myDatabaseName, ".", myCollectionName);
292         out.writeInt(flags);
293         out.writeDocument(myQuery);
294         out.writeDocument(myUpdate);
295     }
296 
297     /**
298      * {@inheritDoc}
299      * <p>
300      * Overridden to write the update message.
301      * </p>
302      * 
303      * @see Message#write(int, BsonOutputStream)
304      */
305     @Override
306     public void write(final int messageId, final BufferingBsonOutputStream out)
307             throws IOException {
308 
309         final int flags = computeFlags();
310 
311         final long start = writeHeader(out, messageId, 0, Operation.UPDATE);
312         out.writeInt(0);
313         out.writeCString(myDatabaseName, ".", myCollectionName);
314         out.writeInt(flags);
315         out.writeDocument(myQuery);
316         out.writeDocument(myUpdate);
317         finishHeader(out, start);
318 
319         out.flushBuffer();
320     }
321 
322     /**
323      * Computes the message flags bit field.
324      * 
325      * @return The message flags bit field.
326      */
327     private int computeFlags() {
328         int flags = 0;
329         if (myMultiUpdate) {
330             flags += MULTIUPDATE_FLAG_BIT;
331         }
332         if (myUpsert) {
333             flags += UPSERT_FLAG_BIT;
334         }
335         return flags;
336     }
337 }