View Javadoc
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 }