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