View Javadoc
1   /*
2    * #%L
3    * Command.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  import java.util.Iterator;
24  
25  import com.allanbank.mongodb.ReadPreference;
26  import com.allanbank.mongodb.bson.Document;
27  import com.allanbank.mongodb.bson.Element;
28  import com.allanbank.mongodb.bson.impl.EmptyDocument;
29  import com.allanbank.mongodb.bson.io.BsonOutputStream;
30  import com.allanbank.mongodb.bson.io.BufferingBsonOutputStream;
31  import com.allanbank.mongodb.bson.io.StringEncoder;
32  import com.allanbank.mongodb.client.Operation;
33  import com.allanbank.mongodb.client.VersionRange;
34  import com.allanbank.mongodb.error.DocumentToLargeException;
35  
36  /**
37   * Helper class to make generating command queries easier. Commands are
38   * communicated to the server as {@link Operation#QUERY} messages. We don't use
39   * the Query class as a base class as it adds a lot of weight to the commands.
40   * 
41   * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
42   *         mutated in incompatible ways between any two releases of the driver.
43   * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
44   */
45  public class Command extends AbstractMessage {
46  
47      /** The collection to use when issuing commands to the database. */
48      public static final String COMMAND_COLLECTION = "$cmd";
49  
50      /** The amount of head room for jumbo documents. */
51      private static final int HEADROOM = 16 * 1024;
52  
53      /**
54       * If true then the command document is allowed to slightly exceed the
55       * document size limit.
56       */
57      private boolean myAllowJumbo = false;
58  
59      /** The command's document. */
60      private final Document myCommand;
61  
62      /** The command's document to use in routing decisions. */
63      private final Document myRoutingDocument;
64  
65      /**
66       * Create a new Command.
67       * 
68       * @param databaseName
69       *            The name of the database.
70       * @param collectionName
71       *            The name of the collection the command is using. This should
72       *            be the real collection and not {@value #COMMAND_COLLECTION} if
73       *            the real collection is known.
74       * @param commandDocument
75       *            The command document containing the command and options.
76       */
77      public Command(final String databaseName, final String collectionName,
78              final Document commandDocument) {
79          this(databaseName, collectionName, commandDocument,
80                  ReadPreference.PRIMARY);
81      }
82  
83      /**
84       * Create a new Command.
85       * 
86       * @param databaseName
87       *            The name of the database.
88       * @param collectionName
89       *            The name of the collection the command is using. This should
90       *            be the real collection and not {@value #COMMAND_COLLECTION} if
91       *            the real collection is known.
92       * @param commandDocument
93       *            The command document containing the command and options.
94       * @param routingDocument
95       *            The document that should be used for routing the command.
96       * @param readPreference
97       *            The preference for which servers to use to retrieve the
98       *            results.
99       * @param requiredServerVersion
100      *            The required version of the server to support processing the
101      *            message.
102      */
103     public Command(final String databaseName, final String collectionName,
104             final Document commandDocument, final Document routingDocument,
105             final ReadPreference readPreference,
106             final VersionRange requiredServerVersion) {
107         super(databaseName, collectionName, readPreference,
108                 requiredServerVersion);
109 
110         myCommand = commandDocument;
111         myRoutingDocument = routingDocument;
112     }
113 
114     /**
115      * Create a new Command.
116      * 
117      * @param databaseName
118      *            The name of the database.
119      * @param collectionName
120      *            The name of the collection the command is using. This should
121      *            be the real collection and not {@value #COMMAND_COLLECTION} if
122      *            the real collection is known.
123      * @param commandDocument
124      *            The command document containing the command and options.
125      * @param readPreference
126      *            The preference for which servers to use to retrieve the
127      *            results.
128      */
129     public Command(final String databaseName, final String collectionName,
130             final Document commandDocument, final ReadPreference readPreference) {
131         this(databaseName, collectionName, commandDocument, readPreference,
132                 null);
133     }
134 
135     /**
136      * Create a new Command.
137      * 
138      * @param databaseName
139      *            The name of the database.
140      * @param collectionName
141      *            The name of the collection the command is using. This should
142      *            be the real collection and not {@value #COMMAND_COLLECTION} if
143      *            the real collection is known.
144      * @param commandDocument
145      *            The command document containing the command and options.
146      * @param readPreference
147      *            The preference for which servers to use to retrieve the
148      *            results.
149      * @param requiredServerVersion
150      *            The required version of the server to support processing the
151      *            message.
152      */
153     public Command(final String databaseName, final String collectionName,
154             final Document commandDocument,
155             final ReadPreference readPreference,
156             final VersionRange requiredServerVersion) {
157         this(databaseName, collectionName, commandDocument,
158                 EmptyDocument.INSTANCE, readPreference, requiredServerVersion);
159     }
160 
161     /**
162      * Determines if the passed object is of this same type as this object and
163      * if so that its fields are equal.
164      * 
165      * @param object
166      *            The object to compare to.
167      * 
168      * @see java.lang.Object#equals(java.lang.Object)
169      */
170     @Override
171     public boolean equals(final Object object) {
172         boolean result = false;
173         if (this == object) {
174             result = true;
175         }
176         else if ((object != null) && (getClass() == object.getClass())) {
177             final Command other = (Command) object;
178 
179             result = super.equals(object) && myCommand.equals(other.myCommand);
180         }
181         return result;
182     }
183 
184     /**
185      * Returns the command's document.
186      * 
187      * @return The command's document.
188      */
189     public Document getCommand() {
190         return myCommand;
191     }
192 
193     /**
194      * {@inheritDoc}
195      * <p>
196      * Overridden to return the command name.
197      * </p>
198      */
199     @Override
200     public String getOperationName() {
201         // Return the value for the first element in the document.
202         final Iterator<Element> iter = myCommand.iterator();
203         if (iter.hasNext()) {
204             return iter.next().getName();
205         }
206         // Not expected. Command documents should have atleast one element. Just
207         // return a generic name here.
208         return "command";
209     }
210 
211     /**
212      * Returns the routingDocument value.
213      * 
214      * @return The routingDocument value.
215      */
216     public Document getRoutingDocument() {
217         return myRoutingDocument;
218     }
219 
220     /**
221      * Computes a reasonable hash code.
222      * 
223      * @return The hash code value.
224      */
225     @Override
226     public int hashCode() {
227         int result = 1;
228         result = (31 * result) + super.hashCode();
229         result = (31 * result) + myCommand.hashCode();
230         return result;
231     }
232 
233     /**
234      * Returns true if the command document is allowed to slightly exceed the
235      * document size limit.
236      * 
237      * @return True if the command document is allowed to slightly exceed the
238      *         document size limit.
239      */
240     public boolean isAllowJumbo() {
241         return myAllowJumbo;
242     }
243 
244     /**
245      * If set to true then the command document is allowed to slightly exceed
246      * the document size limit. This allows us to pack a full size document in a
247      * insert command.
248      * 
249      * @param allowJumbo
250      *            If true then the command document is allowed to slightly
251      *            exceed the document size limit.
252      */
253     public void setAllowJumbo(final boolean allowJumbo) {
254         myAllowJumbo = allowJumbo;
255     }
256 
257     /**
258      * {@inheritDoc}
259      * <p>
260      * Overridden to return the size of the {@link Command}.
261      * </p>
262      */
263     @Override
264     public int size() {
265         int size = HEADER_SIZE + 18; // See below.
266         // size += 4; // flags;
267         size += StringEncoder.utf8Size(myDatabaseName);
268         // size += 1; // StringEncoder.utf8Size(".");
269         // size += 4; // StringEncoder.utf8Size(COMMAND_COLLECTION);
270         // size += 1; // \0 on the CString.
271         // size += 4; // numberToSkip
272         // size += 4; // numberToReturn
273         size += myCommand.size();
274 
275         return size;
276     }
277 
278     /**
279      * {@inheritDoc}
280      * <p>
281      * Overridden to return a human readable form of the command.
282      * </p>
283      */
284     @Override
285     public String toString() {
286         final StringBuilder builder = new StringBuilder();
287         builder.append("Command[");
288         builder.append(getOperationName());
289         builder.append(", db=");
290         builder.append(myDatabaseName);
291         builder.append(", collection=");
292         builder.append(myCollectionName);
293         if (getReadPreference() != null) {
294             builder.append(", readPreference=");
295             builder.append(getReadPreference());
296         }
297         if (getRequiredVersionRange() != null) {
298             builder.append(", requiredVersionRange=");
299             builder.append(getRequiredVersionRange());
300         }
301         builder.append("]: ");
302         builder.append(myCommand);
303 
304         return builder.toString();
305     }
306 
307     /**
308      * {@inheritDoc}
309      * <p>
310      * Overridden to make sure the command document is not too large.
311      * </p>
312      */
313     @Override
314     public void validateSize(final int maxDocumentSize)
315             throws DocumentToLargeException {
316         final long size = myCommand.size();
317 
318         if (isAllowJumbo()) {
319             if ((maxDocumentSize + HEADROOM) < size) {
320                 throw new DocumentToLargeException((int) size, maxDocumentSize
321                         + HEADROOM, myCommand);
322             }
323         }
324         else if (maxDocumentSize < size) {
325             throw new DocumentToLargeException((int) size, maxDocumentSize,
326                     myCommand);
327         }
328     }
329 
330     /**
331      * {@inheritDoc}
332      * <p>
333      * Overridden to write the Command as a {@link Operation#QUERY} message.
334      * </p>
335      */
336     @Override
337     public void write(final int messageId, final BsonOutputStream out)
338             throws IOException {
339         final int numberToSkip = 0;
340         final int numberToReturn = -1; // Unlimited
341         final int flags = computeFlags();
342 
343         int size = HEADER_SIZE;
344         size += 4; // flags;
345         size += out.sizeOfCString(myDatabaseName, ".", COMMAND_COLLECTION);
346         size += 4; // numberToSkip
347         size += 4; // numberToReturn
348         size += myCommand.size();
349 
350         writeHeader(out, messageId, 0, Operation.QUERY, size);
351         out.writeInt(flags);
352         out.writeCString(myDatabaseName, ".", COMMAND_COLLECTION);
353         out.writeInt(numberToSkip);
354         out.writeInt(numberToReturn);
355         out.writeDocument(myCommand);
356     }
357 
358     /**
359      * {@inheritDoc}
360      * <p>
361      * Overridden to write the Command as a {@link Operation#QUERY} message.
362      * </p>
363      */
364     @Override
365     public void write(final int messageId, final BufferingBsonOutputStream out)
366             throws IOException {
367         final int numberToSkip = 0;
368         final int numberToReturn = -1; // Unlimited
369         final int flags = computeFlags();
370 
371         final long start = writeHeader(out, messageId, 0, Operation.QUERY);
372         out.writeInt(flags);
373         out.writeCString(myDatabaseName, ".", COMMAND_COLLECTION);
374         out.writeInt(numberToSkip);
375         out.writeInt(numberToReturn);
376         out.writeDocument(myCommand);
377         finishHeader(out, start);
378 
379         out.flushBuffer();
380     }
381 
382     /**
383      * Computes the message flags bit field.
384      * 
385      * @return The message flags bit field.
386      */
387     private int computeFlags() {
388         int flags = 0;
389         if (getReadPreference().isSecondaryOk()) {
390             flags += Query.REPLICA_OK_FLAG_BIT;
391         }
392         return flags;
393     }
394 }