Coverage Report - com.allanbank.mongodb.client.callback.AbstractValidatingReplyCallback
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractValidatingReplyCallback
100%
72/72
90%
58/64
4.462
 
 1  
 /*
 2  
  * #%L
 3  
  * AbstractValidatingReplyCallback.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.callback;
 21  
 
 22  
 import java.util.ArrayList;
 23  
 import java.util.Collections;
 24  
 import java.util.List;
 25  
 
 26  
 import com.allanbank.mongodb.Callback;
 27  
 import com.allanbank.mongodb.MongoDbException;
 28  
 import com.allanbank.mongodb.bson.Document;
 29  
 import com.allanbank.mongodb.bson.Element;
 30  
 import com.allanbank.mongodb.bson.NumericElement;
 31  
 import com.allanbank.mongodb.bson.element.NullElement;
 32  
 import com.allanbank.mongodb.bson.element.StringElement;
 33  
 import com.allanbank.mongodb.client.Message;
 34  
 import com.allanbank.mongodb.client.message.Reply;
 35  
 import com.allanbank.mongodb.error.CursorNotFoundException;
 36  
 import com.allanbank.mongodb.error.DuplicateKeyException;
 37  
 import com.allanbank.mongodb.error.DurabilityException;
 38  
 import com.allanbank.mongodb.error.MaximumTimeLimitExceededException;
 39  
 import com.allanbank.mongodb.error.QueryFailedException;
 40  
 import com.allanbank.mongodb.error.ReplyException;
 41  
 import com.allanbank.mongodb.error.ShardConfigStaleException;
 42  
 
 43  
 /**
 44  
  * Helper class for constructing callbacks that convert a {@link Reply} message
 45  
  * into a different type of result.
 46  
  * 
 47  
  * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
 48  
  *         mutated in incompatible ways between any two releases of the driver.
 49  
  * @copyright 2011-2014, Allanbank Consulting, Inc., All Rights Reserved
 50  
  */
 51  233
 public abstract class AbstractValidatingReplyCallback implements ReplyCallback {
 52  
 
 53  
     /** The fields that may contain the error code. */
 54  
     public static final String ERROR_CODE_FIELD = "code";
 55  
 
 56  
     /** The fields that may contain the error message. */
 57  
     public static final List<String> ERROR_MESSAGE_FIELDS;
 58  
 
 59  
     static {
 60  1
         final List<String> fields = new ArrayList<String>(3);
 61  1
         fields.add("jnote");
 62  1
         fields.add("wnote");
 63  1
         fields.add("$err");
 64  1
         fields.add("errmsg");
 65  1
         fields.add("err");
 66  
 
 67  1
         ERROR_MESSAGE_FIELDS = Collections.unmodifiableList(fields);
 68  1
     }
 69  
 
 70  
     /**
 71  
      * Creates a new AbstractValidatingReplyCallback.
 72  
      */
 73  
     public AbstractValidatingReplyCallback() {
 74  469
         super();
 75  469
     }
 76  
 
 77  
     /**
 78  
      * {@inheritDoc}
 79  
      * <p>
 80  
      * Overridden to {@link #verify(Reply) verify} the reply and then
 81  
      * {@link #handle(Reply) handle} it.
 82  
      * </p>
 83  
      * 
 84  
      * @see Callback#callback
 85  
      */
 86  
     @Override
 87  
     public void callback(final Reply result) {
 88  
 
 89  
         try {
 90  268
             verify(result);
 91  245
             handle(result);
 92  
         }
 93  23
         catch (final MongoDbException error) {
 94  23
             exception(error);
 95  245
         }
 96  268
     }
 97  
 
 98  
     /**
 99  
      * {@inheritDoc}
 100  
      */
 101  
     @Override
 102  
     public abstract void exception(final Throwable thrown);
 103  
 
 104  
     /**
 105  
      * Creates an exception from the {@link Reply}.
 106  
      * 
 107  
      * @param reply
 108  
      *            The raw reply.
 109  
      * @return The exception created.
 110  
      */
 111  
     protected MongoDbException asError(final Reply reply) {
 112  336
         return asError(reply, false);
 113  
     }
 114  
 
 115  
     /**
 116  
      * Creates an exception from the {@link Reply}.
 117  
      * 
 118  
      * @param reply
 119  
      *            The raw reply.
 120  
      * @param knownError
 121  
      *            If true then the reply is assumed to be an error reply.
 122  
      * @return The exception created.
 123  
      */
 124  
     protected MongoDbException asError(final Reply reply,
 125  
             final boolean knownError) {
 126  320
         final List<Document> results = reply.getResults();
 127  320
         if (results.size() == 1) {
 128  258
             final Document doc = results.get(0);
 129  258
             final Element okElem = doc.get("ok");
 130  258
             final Element errorNumberElem = doc.get(ERROR_CODE_FIELD);
 131  
 
 132  258
             Element errorMessageElem = null;
 133  258
             for (int i = 0; (errorMessageElem == null)
 134  2814
                     && (i < ERROR_MESSAGE_FIELDS.size()); ++i) {
 135  1278
                 errorMessageElem = doc.get(ERROR_MESSAGE_FIELDS.get(i));
 136  
             }
 137  
 
 138  258
             if (okElem != null) {
 139  105
                 final int okValue = toInt(okElem);
 140  105
                 if (okValue != 1) {
 141  20
                     return asError(reply, okValue, toInt(errorNumberElem),
 142  
                             asString(errorMessageElem));
 143  
                 }
 144  85
                 else if ((errorMessageElem != null)
 145  
                         && !(errorMessageElem instanceof NullElement)) {
 146  5
                     return asError(reply, okValue, toInt(errorNumberElem),
 147  
                             asString(errorMessageElem));
 148  
                 }
 149  80
             }
 150  153
             else if (knownError) {
 151  4
                 return asError(reply, -1, toInt(errorNumberElem),
 152  
                         asString(errorMessageElem));
 153  
 
 154  
             }
 155  
         }
 156  291
         return null;
 157  
     }
 158  
 
 159  
     /**
 160  
      * Creates an exception from the parsed reply fields.
 161  
      * 
 162  
      * @param reply
 163  
      *            The raw reply.
 164  
      * @param okValue
 165  
      *            The 'ok' field.
 166  
      * @param errorNumber
 167  
      *            The 'errno' field.
 168  
      * @param knownDurabilityError
 169  
      *            Set to true when we know the error is a durability failure.
 170  
      * @param errorMessage
 171  
      *            The 'errmsg' field.
 172  
      * @param message
 173  
      *            The message that triggered the error, if known.
 174  
      * @return The exception created.
 175  
      */
 176  
     protected final MongoDbException asError(final Reply reply,
 177  
             final int okValue, final int errorNumber,
 178  
             final boolean knownDurabilityError, final String errorMessage,
 179  
             final Message message) {
 180  
 
 181  40
         if (isDurabilityFailure(reply, knownDurabilityError, errorMessage)) {
 182  2
             return new DurabilityException(okValue, errorNumber, errorMessage,
 183  
                     message, reply);
 184  
         }
 185  38
         else if ((errorNumber == 11000) || (errorNumber == 11001)
 186  
                 || errorMessage.startsWith("E11000")
 187  
                 || errorMessage.startsWith("E11001")) {
 188  4
             return new DuplicateKeyException(okValue, errorNumber,
 189  
                     errorMessage, message, reply);
 190  
         }
 191  34
         else if ((errorNumber == 50) || // Standard
 192  
                 (errorNumber == 13475) || // M/R 2.5-ish
 193  
                 (errorNumber == 16711)) { // GroupBy 2.5-ish
 194  3
             return new MaximumTimeLimitExceededException(okValue, errorNumber,
 195  
                     errorMessage, message, reply);
 196  
         }
 197  31
         return new ReplyException(okValue, errorNumber, errorMessage, message,
 198  
                 reply);
 199  
     }
 200  
 
 201  
     /**
 202  
      * Creates an exception from the parsed reply fields.
 203  
      * 
 204  
      * @param reply
 205  
      *            The raw reply.
 206  
      * @param okValue
 207  
      *            The 'ok' field.
 208  
      * @param errorNumber
 209  
      *            The 'errno' field.
 210  
      * @param errorMessage
 211  
      *            The 'errmsg' field.
 212  
      * @return The exception created.
 213  
      */
 214  
     protected MongoDbException asError(final Reply reply, final int okValue,
 215  
             final int errorNumber, final String errorMessage) {
 216  24
         return asError(reply, okValue, errorNumber, false, errorMessage, null);
 217  
     }
 218  
 
 219  
     /**
 220  
      * Converts the {@link Element} to a string. If a {@link StringElement} the
 221  
      * value of the element is returned. In all other cases the toString()
 222  
      * result for the element is returned.
 223  
      * 
 224  
      * @param errorMessageElem
 225  
      *            The element to convert to a string.
 226  
      * @return The {@link Element}'s string value.
 227  
      */
 228  
     protected String asString(final Element errorMessageElem) {
 229  42
         if (errorMessageElem instanceof StringElement) {
 230  26
             return ((StringElement) errorMessageElem).getValue();
 231  
         }
 232  16
         return String.valueOf(errorMessageElem);
 233  
     }
 234  
 
 235  
     /**
 236  
      * Checks for a non-flag error in the reply.
 237  
      * 
 238  
      * @param reply
 239  
      *            The reply to check.
 240  
      * @throws MongoDbException
 241  
      *             On an error represented in the reply.
 242  
      */
 243  
     protected void checkForError(final Reply reply) throws MongoDbException {
 244  251
         final MongoDbException exception = asError(reply);
 245  251
         if (exception != null) {
 246  6
             throw exception;
 247  
         }
 248  245
     }
 249  
 
 250  
     /**
 251  
      * Called once the {@link Reply} has been validated.
 252  
      * 
 253  
      * @param reply
 254  
      *            The {@link Reply} to be handled.
 255  
      */
 256  
     protected abstract void handle(Reply reply);
 257  
 
 258  
     /**
 259  
      * Converts a {@link NumericElement}into an <tt>int</tt> value. If not a
 260  
      * {@link NumericElement} then -1 is returned.
 261  
      * 
 262  
      * @param element
 263  
      *            The element to convert.
 264  
      * @return The element's integer value or -1.
 265  
      */
 266  
     protected int toInt(final Element element) {
 267  224
         if (element instanceof NumericElement) {
 268  194
             return ((NumericElement) element).getIntValue();
 269  
         }
 270  
 
 271  30
         return -1;
 272  
     }
 273  
 
 274  
     /**
 275  
      * Checks the reply for an error message.
 276  
      * 
 277  
      * @param reply
 278  
      *            The Reply to verify is successful.
 279  
      * @throws MongoDbException
 280  
      *             On a failure message in the reply.
 281  
      */
 282  
     protected void verify(final Reply reply) throws MongoDbException {
 283  268
         if (reply.isCursorNotFound()) {
 284  2
             throw new CursorNotFoundException(reply, asError(reply, true));
 285  
         }
 286  266
         else if (reply.isQueryFailed()) {
 287  7
             final MongoDbException error = asError(reply, true);
 288  7
             if ((error == null) || (error.getClass() == ReplyException.class)) {
 289  4
                 throw new QueryFailedException(reply, error);
 290  
             }
 291  
 
 292  3
             throw error;
 293  
         }
 294  259
         else if (reply.isShardConfigStale()) {
 295  8
             throw new ShardConfigStaleException(reply, asError(reply, true));
 296  
         }
 297  
         else {
 298  251
             checkForError(reply);
 299  
         }
 300  245
     }
 301  
 
 302  
     /**
 303  
      * Check if the failure is a failure of the durability of the write.
 304  
      * 
 305  
      * @param reply
 306  
      *            The reply to the message.
 307  
      * @param knownDurabilityError
 308  
      *            If true then the result is already known to be a durability
 309  
      *            failure.
 310  
      * @param errorMessage
 311  
      *            The error message extracted from the document.
 312  
      * @return True if the durability has failed.
 313  
      */
 314  
     private boolean isDurabilityFailure(final Reply reply,
 315  
             final boolean knownDurabilityError, final String errorMessage) {
 316  40
         boolean durabilityError = knownDurabilityError;
 317  
 
 318  40
         final List<Document> results = reply.getResults();
 319  40
         if ((results.size() == 1) && !knownDurabilityError) {
 320  37
             final Document doc = results.get(0);
 321  
 
 322  37
             durabilityError = doc.contains("wtimeout")
 323  
                     || doc.contains("wnote")
 324  
                     || doc.contains("jnote")
 325  
                     || doc.contains("badGLE")
 326  
                     || errorMessage.startsWith("cannot use 'j' option")
 327  
                     || errorMessage
 328  
                             .startsWith("could not enforce write concern");
 329  
         }
 330  40
         return durabilityError;
 331  
     }
 332  
 
 333  
 }