001package com.thetransactioncompany.jsonrpc2;
002
003
004import java.util.List;
005import java.util.Map;
006
007import 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 */
117public 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                return error == null;
361        }
362        
363        
364        /**
365         * Sets the request identifier echoed back to the caller.
366         *
367         * @param id The value must <a href="#map">map</a> to a JSON scalar. 
368         *           Pass a {@code null} if the request identifier couldn't 
369         *           be determined (e.g. due to a parse error).
370         */
371        public void setID(final Object id) {
372                
373                if (   id != null             &&
374                    ! (id instanceof Boolean) &&
375                    ! (id instanceof Number ) &&
376                    ! (id instanceof String )    )
377                        throw new IllegalArgumentException("The request identifier must map to a JSON scalar");
378                
379                this.id = id;
380        }
381        
382        
383        /** 
384         * Gets the request identifier that is echoed back to the caller.
385         *
386         * @return The request identifier. If there was an error during the
387         *         the request retrieval (e.g. parse error) and the identifier 
388         *         couldn't be determined, the value will be {@code null}.
389         */
390         public Object getID() {
391         
392                return id;
393        }
394        
395        
396        @Override
397        public JSONObject toJSONObject() {
398                
399                JSONObject out = new JSONObject();
400                
401                // Result and error are mutually exclusive
402                if (error != null) {
403                        out.put("error", error.toJSONObject());
404                }
405                else {
406                        out.put("result", result);
407                }
408                
409                out.put("id", id);
410                
411                out.put("jsonrpc", "2.0");
412                
413                
414                Map <String,Object> nonStdAttributes = getNonStdAttributes();
415                
416                if (nonStdAttributes != null) {
417                
418                        for (final Map.Entry<String,Object> attr: nonStdAttributes.entrySet())
419                                out.put(attr.getKey(), attr.getValue());
420                }
421                
422                return out;
423        }
424}