1 /* 2 * #%L 3 * CommandCursorTranslator.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 21 package com.allanbank.mongodb.client.callback; 22 23 import java.util.ArrayList; 24 import java.util.Collections; 25 import java.util.List; 26 27 import com.allanbank.mongodb.bson.Document; 28 import com.allanbank.mongodb.bson.NumericElement; 29 import com.allanbank.mongodb.bson.builder.BuilderFactory; 30 import com.allanbank.mongodb.bson.element.ArrayElement; 31 import com.allanbank.mongodb.bson.element.DocumentElement; 32 import com.allanbank.mongodb.client.message.Reply; 33 34 /** 35 * CommandCursorTranslator provides static utility methods to translate cursor 36 * documents. 37 * 38 * @copyright 2013, Allanbank Consulting, Inc., All Rights Reserved 39 */ 40 public class CommandCursorTranslator { 41 42 /** 43 * Translates the reply from a single document command reply into a standard 44 * reply with the appropriate bit flips. 45 * <p> 46 * There are two possible formats we can receive: 47 * <ul> 48 * <li> 49 * Traditional embedded documents under a {@code results} array. For this 50 * format all of the Documents are in the single reply and there is no 51 * cursor established.<blockquote> 52 * 53 * <pre> 54 * <code> 55 * { 56 * results : [ 57 * { ... }, 58 * ... 59 * ], 60 * ok : 1 61 * } 62 * </code> 63 * </pre> 64 * 65 * </blockquote></li> 66 * <li> 67 * A {@code cursor} sub-document containing the cursor's {@code id} and 68 * {@code firstBatch}. This reply establishes (possibly) a cursor for the 69 * results.<blockquote> 70 * 71 * <pre> 72 * <code> 73 * { 74 * cursor : { 75 * id : 1234567, 76 * firstBatch : [ 77 * { ... }, 78 * ... 79 * ] 80 * } 81 * ok : 1 82 * } 83 * </code> 84 * </pre> 85 * 86 * </blockquote></li> 87 * 88 * 89 * @param reply 90 * The reply to translate. 91 * @return The translated reply. 92 */ 93 /* package */static Reply translate(final Reply reply) { 94 Reply result = reply; 95 96 final List<Reply> replies = translateAll(reply); 97 if (replies.size() == 1) { 98 result = replies.get(0); 99 } 100 101 return result; 102 } 103 104 /** 105 * Translates the reply from a single document command reply into a list of 106 * standard replies with the appropriate bit flips. 107 * <p> 108 * There are three possible formats we can receive: 109 * <ul> 110 * <li> 111 * Traditional embedded documents under a {@code results} array. For this 112 * format all of the Documents are in the single reply and there is no 113 * cursor established.<blockquote> 114 * 115 * <pre> 116 * <code> 117 * { 118 * results : [ 119 * { ... }, 120 * ... 121 * ], 122 * ok : 1 123 * } 124 * </code> 125 * </pre> 126 * 127 * </blockquote></li> 128 * <li> 129 * A {@code cursor} sub-document containing the cursor's {@code id} and 130 * {@code firstBatch}. This reply establishes (possibly) a cursor for the 131 * results.<blockquote> 132 * 133 * <pre> 134 * <code> 135 * { 136 * cursor : { 137 * id : 1234567, 138 * firstBatch : [ 139 * { ... }, 140 * ... 141 * ] 142 * } 143 * ok : 1 144 * } 145 * </code> 146 * </pre> 147 * 148 * </blockquote></li> 149 * <li> 150 * A {@code cursor} sub-array with a sub-document as each element of the 151 * array. Each sub-document contains a {@code cursor} document as described 152 * above.<blockquote> 153 * 154 * <pre> 155 * <code> 156 * { 157 * cursors: [ 158 * { 159 * cursor : { 160 * id : 1234567, 161 * firstBatch : [ 162 * { ... }, 163 * ... 164 * ] 165 * } 166 * }, 167 * { 168 * cursor : { 169 * id : 1234568, 170 * firstBatch : [ 171 * { ... }, 172 * ... 173 * ] 174 * } 175 * }, 176 * ... 177 * ] 178 * ok : 1 179 * } 180 * </code> 181 * </pre> 182 * 183 * </blockquote></li> 184 * 185 * 186 * @param reply 187 * The reply to translate. 188 * @return The translated reply. 189 */ 190 /* package */static List<Reply> translateAll(final Reply reply) { 191 List<Reply> results = Collections.singletonList(reply); 192 193 // Check for a single Document. All formats to translate are single 194 // documents. 195 final List<Document> docs = reply.getResults(); 196 if (docs.size() == 1) { 197 final Document replyDoc = docs.get(0); 198 199 List<DocumentElement> resultDocs; 200 201 // Traditional first since it is more probable in the short term. 202 final ArrayElement resultArray = replyDoc.get(ArrayElement.class, 203 "result"); 204 if (resultArray != null) { 205 resultDocs = replyDoc.find(DocumentElement.class, "result", 206 ".*"); 207 results = Collections.singletonList(translate(reply, 0L, 208 resultDocs)); 209 } 210 else { 211 final DocumentElement cursor = replyDoc.get( 212 DocumentElement.class, "cursor"); 213 if (cursor != null) { 214 results = translate(reply, 215 Collections.singletonList(cursor)); 216 } 217 else { 218 final List<DocumentElement> cursors = replyDoc.find( 219 DocumentElement.class, "cursors", ".*", "cursor"); 220 if (!cursors.isEmpty()) { 221 results = translate(reply, cursors); 222 } 223 } 224 } 225 } 226 227 return results; 228 } 229 230 /** 231 * Translates a list of cursor documents into a list of {@link Reply} 232 * objects. 233 * 234 * @param reply 235 * The original reply. 236 * @param cursors 237 * The cursor sub documents. 238 * @return The translated replies. 239 */ 240 private static List<Reply> translate(final Reply reply, 241 final List<DocumentElement> cursors) { 242 243 final List<Reply> results = new ArrayList<Reply>(cursors.size()); 244 for (final DocumentElement cursor : cursors) { 245 final NumericElement id = cursor.get(NumericElement.class, "id"); 246 final List<DocumentElement> resultDocs = cursor.find( 247 DocumentElement.class, "firstBatch", ".*"); 248 249 results.add(translate(reply, (id == null) ? 0 : id.getLongValue(), 250 resultDocs)); 251 } 252 return results; 253 } 254 255 /** 256 * Creates a new reply based on the original reply and the specified cursor 257 * id and document list. 258 * 259 * @param reply 260 * The original reply to copy from. 261 * @param cursorId 262 * The cursor id that has been established. 263 * @param results 264 * The results to include in the reply. 265 * @return The translated reply. 266 */ 267 private static Reply translate(final Reply reply, final long cursorId, 268 final List<DocumentElement> results) { 269 270 // Strip off the element-ness of the documents. 271 final List<Document> docs = new ArrayList<Document>(results.size()); 272 for (final DocumentElement docElement : results) { 273 docs.add(BuilderFactory.start(docElement).build()); 274 } 275 276 return new Reply(reply.getResponseToId(), cursorId, 277 reply.getCursorOffset(), docs, reply.isAwaitCapable(), 278 reply.isCursorNotFound(), reply.isQueryFailed(), 279 reply.isShardConfigStale()); 280 } 281 282 /** 283 * Creates a new CommandCursorTranslator. 284 */ 285 private CommandCursorTranslator() { 286 // Static class. 287 } 288 289 }