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 }