Coverage Report - com.allanbank.mongodb.client.message.Command
 
Classes in this File Line Coverage Branch Coverage Complexity
Command
98%
85/86
100%
24/24
1.824
 
 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  5864
     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  5269
         this(databaseName, collectionName, commandDocument,
 80  
                 ReadPreference.PRIMARY);
 81  5269
     }
 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  5864
         super(databaseName, collectionName, readPreference,
 108  
                 requiredServerVersion);
 109  
 
 110  5864
         myCommand = commandDocument;
 111  5864
         myRoutingDocument = routingDocument;
 112  5864
     }
 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  5305
         this(databaseName, collectionName, commandDocument, readPreference,
 132  
                 null);
 133  5305
     }
 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  5830
         this(databaseName, collectionName, commandDocument,
 158  
                 EmptyDocument.INSTANCE, readPreference, requiredServerVersion);
 159  5830
     }
 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  3228
         boolean result = false;
 173  3228
         if (this == object) {
 174  120
             result = true;
 175  
         }
 176  3108
         else if ((object != null) && (getClass() == object.getClass())) {
 177  3010
             final Command other = (Command) object;
 178  
 
 179  3010
             result = super.equals(object) && myCommand.equals(other.myCommand);
 180  
         }
 181  3228
         return result;
 182  
     }
 183  
 
 184  
     /**
 185  
      * Returns the command's document.
 186  
      * 
 187  
      * @return The command's document.
 188  
      */
 189  
     public Document getCommand() {
 190  19
         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  2264
         final Iterator<Element> iter = myCommand.iterator();
 203  2264
         if (iter.hasNext()) {
 204  2263
             return iter.next().getName();
 205  
         }
 206  
         // Not expected. Command documents should have atleast one element. Just
 207  
         // return a generic name here.
 208  1
         return "command";
 209  
     }
 210  
 
 211  
     /**
 212  
      * Returns the routingDocument value.
 213  
      * 
 214  
      * @return The routingDocument value.
 215  
      */
 216  
     public Document getRoutingDocument() {
 217  0
         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  5376
         int result = 1;
 228  5376
         result = (31 * result) + super.hashCode();
 229  5376
         result = (31 * result) + myCommand.hashCode();
 230  5376
         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  257
         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  70
         myAllowJumbo = allowJumbo;
 255  70
     }
 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  1
         int size = HEADER_SIZE + 18; // See below.
 266  
         // size += 4; // flags;
 267  1
         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  1
         size += myCommand.size();
 274  
 
 275  1
         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  2259
         final StringBuilder builder = new StringBuilder();
 287  2259
         builder.append("Command[");
 288  2259
         builder.append(getOperationName());
 289  2259
         builder.append(", db=");
 290  2259
         builder.append(myDatabaseName);
 291  2259
         builder.append(", collection=");
 292  2259
         builder.append(myCollectionName);
 293  2259
         if (getReadPreference() != null) {
 294  2258
             builder.append(", readPreference=");
 295  2258
             builder.append(getReadPreference());
 296  
         }
 297  2259
         if (getRequiredVersionRange() != null) {
 298  2257
             builder.append(", requiredVersionRange=");
 299  2257
             builder.append(getRequiredVersionRange());
 300  
         }
 301  2259
         builder.append("]: ");
 302  2259
         builder.append(myCommand);
 303  
 
 304  2259
         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  257
         final long size = myCommand.size();
 317  
 
 318  257
         if (isAllowJumbo()) {
 319  2
             if ((maxDocumentSize + HEADROOM) < size) {
 320  1
                 throw new DocumentToLargeException((int) size, maxDocumentSize
 321  
                         + HEADROOM, myCommand);
 322  
             }
 323  
         }
 324  255
         else if (maxDocumentSize < size) {
 325  1
             throw new DocumentToLargeException((int) size, maxDocumentSize,
 326  
                     myCommand);
 327  
         }
 328  255
     }
 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  2
         final int flags = computeFlags();
 342  
 
 343  2
         int size = HEADER_SIZE;
 344  2
         size += 4; // flags;
 345  2
         size += out.sizeOfCString(myDatabaseName, ".", COMMAND_COLLECTION);
 346  2
         size += 4; // numberToSkip
 347  2
         size += 4; // numberToReturn
 348  2
         size += myCommand.size();
 349  
 
 350  2
         writeHeader(out, messageId, 0, Operation.QUERY, size);
 351  2
         out.writeInt(flags);
 352  2
         out.writeCString(myDatabaseName, ".", COMMAND_COLLECTION);
 353  2
         out.writeInt(numberToSkip);
 354  2
         out.writeInt(numberToReturn);
 355  2
         out.writeDocument(myCommand);
 356  2
     }
 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  253
         final int flags = computeFlags();
 370  
 
 371  253
         final long start = writeHeader(out, messageId, 0, Operation.QUERY);
 372  253
         out.writeInt(flags);
 373  253
         out.writeCString(myDatabaseName, ".", COMMAND_COLLECTION);
 374  253
         out.writeInt(numberToSkip);
 375  253
         out.writeInt(numberToReturn);
 376  253
         out.writeDocument(myCommand);
 377  253
         finishHeader(out, start);
 378  
 
 379  253
         out.flushBuffer();
 380  253
     }
 381  
 
 382  
     /**
 383  
      * Computes the message flags bit field.
 384  
      * 
 385  
      * @return The message flags bit field.
 386  
      */
 387  
     private int computeFlags() {
 388  255
         int flags = 0;
 389  255
         if (getReadPreference().isSecondaryOk()) {
 390  2
             flags += Query.REPLICA_OK_FLAG_BIT;
 391  
         }
 392  255
         return flags;
 393  
     }
 394  
 }