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 }