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 }