001    package com.thetransactioncompany.jsonrpc2;
002    
003    
004    import java.util.List;
005    import java.util.Map;
006    
007    import net.minidev.json.JSONObject;
008    
009    
010    /** 
011     * Represents a JSON-RPC 2.0 response.
012     *
013     * <p>A response is returned to the caller after a JSON-RPC 2.0 request has 
014     * been processed (notifications, however, don't produce a response). The
015     * response can take two different forms depending on the outcome:
016     *
017     * <ul>
018     *     <li>The request was successful. The corresponding response returns
019     *         a JSON object with the following information:
020     *         <ul>
021     *             <li>{@code result} The result, which can be of any JSON type
022     *                 - a number, a boolean value, a string, an array, an object 
023     *                 or null.
024     *             <li>{@code id} The request identifier which is echoed back back 
025     *                 to the caller.
026     *             <li>{@code jsonrpc} A string indicating the JSON-RPC protocol 
027     *                 version set to "2.0".
028     *         </ul>
029     *     <li>The request failed. The returned JSON object contains:
030     *         <ul>
031     *             <li>{@code error} An object with:
032     *                 <ul>
033     *                     <li>{@code code} An integer indicating the error type.
034     *                     <li>{@code message} A brief error messsage.
035     *                     <li>{@code data} Optional error data.
036     *                 </ul>
037     *             <li>{@code id} The request identifier. If it couldn't be 
038     *                 determined, e.g. due to a request parse error, the ID is
039     *                 set to {@code null}.
040     *             <li>{@code jsonrpc} A string indicating the JSON-RPC protocol 
041     *                 version set to "2.0".
042     *         </ul>
043     * </ul>
044     *
045     * <p>Here is an example JSON-RPC 2.0 response string where the request
046     * has succeeded:
047     *
048     * <pre>
049     * {  
050     *    "result"  : true,
051     *    "id"      : "req-002",
052     *    "jsonrpc" : "2.0"  
053     * }
054     * </pre>
055     *
056     *
057     * <p>And here is an example JSON-RPC 2.0 response string indicating a failure:
058     *
059     * <pre>
060     * {  
061     *    "error"   : { "code" : -32601, "message" : "Method not found" },
062     *    "id"      : "req-003",
063     *    "jsonrpc" : "2.0"
064     * }
065     * </pre>
066     *
067     * <p>A response object is obtained either by passing a valid JSON-RPC 2.0
068     * response string to the static {@link #parse} method or by invoking the
069     * appropriate constructor.
070     *
071     * <p>Here is how parsing is done:
072     * 
073     * <pre>
074     * String jsonString = "{\"result\":true,\"id\":\"req-002\",\"jsonrpc\":\"2.0\"}";
075     * 
076     * JSONRPC2Response response = null;
077     * 
078     * try {
079     *         response = JSONRPC2Response.parse(jsonString);
080     *
081     * } catch (JSONRPC2Exception e) {
082     *         // handle exception
083     * }
084     * </pre>
085     *
086     * <p>And here is how you can replicate the above example response strings:
087     *
088     * <pre>
089     * // success example
090     * JSONRPC2Response resp = new JSONRPC2Response(true, "req-002");
091     * System.out.println(resp);
092     * 
093     * // failure example
094     * JSONRPC2Error err = new JSONRPC2Error(-32601, "Method not found");
095     * resp = new JSONRPC2Response(err, "req-003");
096     * System.out.println(resp);
097     * 
098     * </pre>
099     *
100     * <p id="map">The mapping between JSON and Java entities (as defined by the 
101     * underlying JSON Smart library): 
102     *
103     * <pre>
104     *     true|false  <--->  java.lang.Boolean
105     *     number      <--->  java.lang.Number
106     *     string      <--->  java.lang.String
107     *     array       <--->  java.util.List
108     *     object      <--->  java.util.Map
109     *     null        <--->  null
110     * </pre>
111     *
112     * <p>The JSON-RPC 2.0 specification and user group forum can be found 
113     * <a href="http://groups.google.com/group/json-rpc">here</a>.
114     *
115     * @author Vladimir Dzhuvinov
116     */
117    public class JSONRPC2Response extends JSONRPC2Message {
118            
119            
120            /** 
121             * The result. 
122             */
123            private Object result = null;
124            
125            
126            /** 
127             * The error object.
128             */
129            private JSONRPC2Error error = null;
130            
131            
132            /** 
133             * The echoed request identifier. 
134             */
135            private Object id = null;
136            
137            
138            /** 
139             * Parses a JSON-RPC 2.0 response string. This method is thread-safe.
140             *
141             * @param jsonString The JSON-RPC 2.0 response string, UTF-8 encoded.
142             *                   Must not be {@code null}.
143             *
144             * @return The corresponding JSON-RPC 2.0 response object.
145             *
146             * @throws JSONRPC2ParseException With detailed message if parsing 
147             *                                failed.
148             */
149            public static JSONRPC2Response parse(final String jsonString)
150                    throws JSONRPC2ParseException {
151            
152                    return parse(jsonString, false, false, false);
153            }
154            
155            
156            /** 
157             * Parses a JSON-RPC 2.0 response string. This method is thread-safe.
158             *
159             * @param jsonString    The JSON-RPC 2.0 response string, UTF-8 encoded.
160             *                      Must not be {@code null}.
161             * @param preserveOrder {@code true} to preserve the order of JSON 
162             *                      object members in results.
163             *
164             * @return The corresponding JSON-RPC 2.0 response object.
165             *
166             * @throws JSONRPC2ParseException With detailed message if parsing 
167             *                                failed.
168             */
169            public static JSONRPC2Response parse(final String jsonString, 
170                                                 final boolean preserveOrder)
171                    throws JSONRPC2ParseException {
172            
173                    return parse(jsonString, preserveOrder, false, false);
174            }
175            
176            
177            /** 
178             * Parses a JSON-RPC 2.0 response string. This method is thread-safe.
179             *
180             * @param jsonString    The JSON-RPC 2.0 response string, UTF-8 encoded.
181             *                      Must not be {@code null}.
182             * @param preserveOrder {@code true} to preserve the order of JSON 
183             *                      object members in results.
184             * @param ignoreVersion {@code true} to skip a check of the 
185             *                      {@code "jsonrpc":"2.0"} version attribute in the 
186             *                      JSON-RPC 2.0 message.
187             *
188             * @return The corresponding JSON-RPC 2.0 response object.
189             *
190             * @throws JSONRPC2ParseException With detailed message if the parsing 
191             *                                failed.
192             */
193            public static JSONRPC2Response parse(final String jsonString, 
194                                                 final boolean preserveOrder, 
195                                                 final boolean ignoreVersion)
196                    throws JSONRPC2ParseException {
197            
198                    return parse(jsonString, preserveOrder, ignoreVersion, false);
199            }
200            
201            
202            /** 
203             * Parses a JSON-RPC 2.0 response string. This method is thread-safe.
204             *
205             * @param jsonString            The JSON-RPC 2.0 response string, UTF-8 
206             *                              encoded. Must not be {@code null}.
207             * @param preserveOrder         {@code true} to preserve the order of  
208             *                              JSON object members in results.
209             * @param ignoreVersion         {@code true} to skip a check of the 
210             *                              {@code "jsonrpc":"2.0"} version 
211             *                              attribute in the JSON-RPC 2.0 message.
212             * @param parseNonStdAttributes {@code true} to parse non-standard
213             *                              attributes found in the JSON-RPC 2.0 
214             *                              message.
215             *
216             * @return The corresponding JSON-RPC 2.0 response object.
217             *
218             * @throws JSONRPC2ParseException With detailed message if the parsing 
219             *                                failed.
220             */
221            public static JSONRPC2Response parse(final String jsonString, 
222                                                 final boolean preserveOrder, 
223                                                 final boolean ignoreVersion,
224                                                 final boolean parseNonStdAttributes)
225                    throws JSONRPC2ParseException {
226            
227                    JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder, ignoreVersion, parseNonStdAttributes);
228                    
229                    return parser.parseJSONRPC2Response(jsonString);
230            }
231            
232            
233            /** 
234             * Creates a new JSON-RPC 2.0 response to a successful request.
235             *
236             * @param result The result. The value can <a href="#map">map</a> 
237             *               to any JSON type. May be {@code null}.
238             * @param id     The request identifier echoed back to the caller. May 
239             *               be {@code null} though not recommended.
240             */
241            public JSONRPC2Response(final Object result, final Object id) {
242            
243                    setResult(result);
244                    setID(id);
245            }
246            
247            
248            /** 
249             * Creates a new JSON-RPC 2.0 response to a successful request which
250             * result is {@code null}.
251             *
252             * @param id The request identifier echoed back to the caller. May be 
253             *           {@code null} though not recommended.
254             */
255            public JSONRPC2Response(final Object id) {
256            
257                    setResult(null);
258                    setID(id);
259            }
260            
261            
262            /** 
263             * Creates a new JSON-RPC 2.0 response to a failed request.
264             * 
265             * @param error A JSON-RPC 2.0 error instance indicating the
266             *              cause of the failure. Must not be {@code null}.
267             * @param id    The request identifier echoed back to the caller.
268             *              Pass a {@code null} if the request identifier couldn't
269             *              be determined (e.g. due to a parse error).
270             */
271            public JSONRPC2Response(final JSONRPC2Error error, final Object id) {
272            
273                    setError(error);
274                    setID(id);
275            }
276            
277            
278            /** 
279             * Indicates a successful JSON-RPC 2.0 request and sets the result. 
280             * Note that if the response was previously indicating failure this
281             * will turn it into a response indicating success. Any previously set
282             * error data will be invalidated.
283             *
284             * @param result The result. The value can <a href="#map">map</a> to 
285             *               any JSON type. May be {@code null}.
286             */
287            public void setResult(final Object result) {
288                    
289                    if (   result != null             &&
290                        ! (result instanceof Boolean) &&
291                        ! (result instanceof Number ) &&
292                        ! (result instanceof String ) &&
293                        ! (result instanceof List   ) &&
294                        ! (result instanceof Map    )    )
295                            throw new IllegalArgumentException("The result must map to a JSON type");
296                    
297                    // result and error are mutually exclusive
298                    this.result = result;
299                    this.error = null;
300            }       
301            
302            
303            /** 
304             * Gets the result of the request. The returned value has meaning
305             * only if the request was successful. Use the 
306             * {@link #getError getError} method to check this.
307             *
308             * @return The result.
309             */
310            public Object getResult() {
311                    
312                    return result;
313            }
314            
315            
316            /** 
317             * Indicates a failed JSON-RPC 2.0 request and sets the error details.
318             * Note that if the response was previously indicating success this
319             * will turn it into a response indicating failure. Any previously set 
320             * result data will be invalidated.
321             *
322             * @param error A JSON-RPC 2.0 error instance indicating the cause of 
323             *              the failure. Must not be {@code null}.
324             */
325            public void setError(final JSONRPC2Error error) {
326                    
327                    if (error == null)
328                            throw new IllegalArgumentException("The error object cannot be null");
329                    
330                    // result and error are mutually exclusive
331                    this.error = error;
332                    this.result = null;             
333            }
334            
335            
336            /** 
337             * Gets the error object indicating the cause of the request failure. 
338             * If a {@code null} is returned, the request succeeded and there was
339             * no error.
340             *
341             * @return A JSON-RPC 2.0 error object, {@code null} if the response
342             *         indicates success.
343             */
344            public JSONRPC2Error getError() {
345                    
346                    return error;
347            }
348            
349            
350            /**
351             * A convinience method to check if the response indicates success or
352             * failure of the request. Alternatively, you can use the 
353             * {@code #getError} method for this purpose.
354             *
355             * @return {@code true} if the request succeeded, {@code false} if
356             *         there was an error.
357             */
358            public boolean indicatesSuccess() {
359                    
360                    if (error == null)
361                            return true;
362                    else
363                            return false;
364            }
365            
366            
367            /**
368             * Sets the request identifier echoed back to the caller.
369             *
370             * @param id The value must <a href="#map">map</a> to a JSON scalar. 
371             *           Pass a {@code null} if the request identifier couldn't 
372             *           be determined (e.g. due to a parse error).
373             */
374            public void setID(final Object id) {
375                    
376                    if (   id != null             &&
377                        ! (id instanceof Boolean) &&
378                        ! (id instanceof Number ) &&
379                        ! (id instanceof String )    )
380                            throw new IllegalArgumentException("The request identifier must map to a JSON scalar");
381                    
382                    this.id = id;
383            }
384            
385            
386            /** 
387             * Gets the request identifier that is echoed back to the caller.
388             *
389             * @return The request identifier. If there was an error during the
390             *         the request retrieval (e.g. parse error) and the identifier 
391             *         couldn't be determined, the value will be {@code null}.
392             */
393             public Object getID() {
394             
395                    return id;
396            }
397            
398            
399            @Override
400            public JSONObject toJSONObject() {
401                    
402                    JSONObject out = new JSONObject();
403                    
404                    // Result and error are mutually exclusive
405                    if (error != null) {
406                            out.put("error", error.toJSONObject());
407                    }
408                    else {
409                            out.put("result", result);
410                    }
411                    
412                    out.put("id", id);
413                    
414                    out.put("jsonrpc", "2.0");
415                    
416                    
417                    Map <String,Object> nonStdAttributes = getNonStdAttributes();
418                    
419                    if (nonStdAttributes != null) {
420                    
421                            for (final Map.Entry<String,Object> attr: nonStdAttributes.entrySet())
422                                    out.put(attr.getKey(), attr.getValue());
423                    }
424                    
425                    return out;
426            }
427    }