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}