Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.thetransactioncompany.jsonrpc2.JSONRPC2Parser Maven / Gradle / Ivy
package com.thetransactioncompany.jsonrpc2;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.writer.JsonReader;
import java.util.*;
/**
* Parses JSON-RPC 2.0 request, notification and response messages.
*
* Parsing of batched requests / notifications is not supported.
*
*
This class is not thread-safe. A parser instance should not be used by
* more than one thread unless properly synchronised. Alternatively, you may
* use the thread-safe {@link JSONRPC2Message#parse} and its sister methods.
*
*
Example:
*
*
* String jsonString = "{\"method\":\"makePayment\"," +
* "\"params\":{\"recipient\":\"Penny Adams\",\"amount\":175.05}," +
* "\"id\":\"0001\","+
* "\"jsonrpc\":\"2.0\"}";
*
* JSONRPC2Request req = null;
*
* JSONRPC2Parser parser = new JSONRPC2Parser();
*
* try {
* req = parser.parseJSONRPC2Request(jsonString);
*
* } catch (JSONRPC2ParseException e) {
* // handle exception
* }
*
*
*
* The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
*
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
*
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Parser {
/**
* JSON-RPC 2.0 parse options.
*/
public enum Option {
/**
* The order of the parsed JSON object members in parameters
* and results must be preserved.
*/
PRESERVE_ORDER,
/**
* The {@code "jsonrpc":"2.0"} version member in the JSON-RPC
* 2.0 message must be ignored.
*/
IGNORE_VERSION,
/**
* Non-standard JSON-RPC 2.0 message members must be parsed.
*/
PARSE_NON_STD_MEMBERS,
/**
* Allow a {@code null} "error" member in JSON-RPC 2.0
* responses.
*/
ALLOW_NULL_ERROR_IN_RESPONSE
}
/**
* Reusable JSON parser. Not thread-safe!
*/
private final JSONParser parser;
/**
* The parse options.
*/
private final Set options = new HashSet ();
/**
* Creates a new JSON-RPC 2.0 message parser. No {@link Option parse
* options} are specified.
*/
public JSONRPC2Parser() {
this(new Option[0]);
}
/**
* Creates a new JSON-RPC 2.0 message parser.
*
* @param preserveOrder If {@code true} the member order of JSON
* objects in parameters and results will be
* preserved.
*/
@Deprecated
public JSONRPC2Parser(final boolean preserveOrder) {
this(preserveOrder, false, false);
}
/**
* Creates a new JSON-RPC 2.0 message parser.
*
* @param preserveOrder If {@code true} the member order of JSON
* objects in parameters and results will be
* preserved.
* @param ignoreVersion If {@code true} the {@code "jsonrpc":"2.0"}
* version member in the JSON-RPC 2.0 message will
* not be checked.
*/
@Deprecated
public JSONRPC2Parser(final boolean preserveOrder,
final boolean ignoreVersion) {
this(booleanOptionFlagsToArray(preserveOrder, ignoreVersion, false));
}
/**
* Creates a new JSON-RPC 2.0 message parser with the specified
* options.
*
* @param preserveOrder If {@code true} the member order of JSON
* objects in parameters and results will be
* preserved.
* @param ignoreVersion If {@code true} the
* {@code "jsonrpc":"2.0"} version member in
* the JSON-RPC 2.0 message will not be
* checked.
* @param parseNonStdMembers If {@code true} non-standard members found
* in the JSON-RPC 2.0 messages will be
* parsed.
*/
@Deprecated
public JSONRPC2Parser(final boolean preserveOrder,
final boolean ignoreVersion,
final boolean parseNonStdMembers) {
this(booleanOptionFlagsToArray(preserveOrder, ignoreVersion, parseNonStdMembers));
}
private static Option[] booleanOptionFlagsToArray(final boolean preserveOrder,
final boolean ignoreVersion,
final boolean parseNonStdMembers) {
List optionList = new LinkedList ();
if (preserveOrder) {
optionList.add(Option.PRESERVE_ORDER);
}
if (ignoreVersion) {
optionList.add(Option.IGNORE_VERSION);
}
if (parseNonStdMembers) {
optionList.add(Option.PARSE_NON_STD_MEMBERS);
}
return optionList.toArray(new Option[0]);
}
/**
* Creates a new JSON-RPC 2.0 message parser with the specified
* options.
*
* @param options The parse options.
*/
public JSONRPC2Parser(final Option ... options) {
// Numbers parsed as long/double, requires JSON Smart 1.0.9+
parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
this.options.addAll(Arrays.asList(options));
}
@SuppressWarnings("unchecked")
private Map parseJSONObject(final String jsonString)
throws JSONRPC2ParseException {
if (jsonString.trim().isEmpty())
throw new JSONRPC2ParseException("Invalid JSON: Empty string",
JSONRPC2ParseException.JSON,
jsonString);
Object json;
// Parse the JSON string
try {
if (options.contains(Option.PRESERVE_ORDER))
json = parser.parse(jsonString, new JsonReader().DEFAULT_ORDERED);
else
json = parser.parse(jsonString);
} catch (Exception e) {
// ParseException, NumberFormatException, NullPointerException
// Terse message, do not include full parse exception message
throw new JSONRPC2ParseException("Invalid JSON",
JSONRPC2ParseException.JSON,
jsonString);
}
if (json instanceof List)
throw new JSONRPC2ParseException("JSON-RPC 2.0 batch requests/notifications not supported", jsonString);
if (! (json instanceof Map))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 message: Message must be a JSON object", jsonString);
return (Map)json;
}
private static void ensureVersion2(final Object version, final String jsonString)
throws JSONRPC2ParseException {
if (version == null)
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0: Version string missing", jsonString);
else if (! (version instanceof String))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0: Version not a JSON string", jsonString);
else if (! version.equals("2.0"))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0: Version must be \"2.0\"", jsonString);
}
/**
* Provides common parsing of JSON-RPC 2.0 requests, notifications and
* responses. Use this method if you don't know which type of JSON-RPC
* message the input string represents.
*
* If a particular message type is expected use the dedicated
* {@link #parseJSONRPC2Request}, {@link #parseJSONRPC2Notification}
* and {@link #parseJSONRPC2Response} methods. They are more efficient
* and would provide you with more detailed parse error reporting.
*
* @param jsonString A JSON string representing a JSON-RPC 2.0 request,
* notification or response, UTF-8 encoded. Must not
* be {@code null}.
*
* @return A {@link JSONRPC2Request}, {@link JSONRPC2Notification} or
* {@link JSONRPC2Response}.
*
* @throws JSONRPC2ParseException With detailed message if the parsing
* failed.
*/
public JSONRPC2Message parseJSONRPC2Message(final String jsonString)
throws JSONRPC2ParseException {
// Try each of the parsers until one succeeds (or all fail)
try {
return parseJSONRPC2Request(jsonString);
} catch (JSONRPC2ParseException e) {
// throw on JSON error, ignore on protocol error
if (e.getCauseType() == JSONRPC2ParseException.JSON)
throw e;
}
try {
return parseJSONRPC2Notification(jsonString);
} catch (JSONRPC2ParseException e) {
// throw on JSON error, ignore on protocol error
if (e.getCauseType() == JSONRPC2ParseException.JSON)
throw e;
}
try {
return parseJSONRPC2Response(jsonString);
} catch (JSONRPC2ParseException e) {
// throw on JSON error, ignore on protocol error
if (e.getCauseType() == JSONRPC2ParseException.JSON)
throw e;
}
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 message",
JSONRPC2ParseException.PROTOCOL,
jsonString);
}
/**
* Parses a JSON-RPC 2.0 request string.
*
* @param jsonString The JSON-RPC 2.0 request string, UTF-8 encoded.
* Must not be {@code null}.
*
* @return The JSON-RPC 2.0 request object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
@SuppressWarnings("unchecked")
public JSONRPC2Request parseJSONRPC2Request(final String jsonString)
throws JSONRPC2ParseException {
// Initial JSON object parsing
Map jsonObject = parseJSONObject(jsonString);
// Check for JSON-RPC version "2.0"
Object version = jsonObject.remove("jsonrpc");
if (! options.contains(Option.IGNORE_VERSION))
ensureVersion2(version, jsonString);
// Extract method name
Object method = jsonObject.remove("method");
if (method == null)
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Method name missing", jsonString);
else if (! (method instanceof String))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Method name not a JSON string", jsonString);
else if (((String) method).isEmpty())
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Method name is an empty string", jsonString);
// Extract ID
if (! jsonObject.containsKey("id"))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Missing identifier", jsonString);
Object id = jsonObject.remove("id");
if ( id != null &&
!(id instanceof Number ) &&
!(id instanceof Boolean) &&
!(id instanceof String ) )
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Identifier not a JSON scalar", jsonString);
if (jsonObject.containsKey("result"))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Must not include result", jsonString);
// Extract params
Object params = jsonObject.remove("params");
JSONRPC2Request request;
if (params == null)
request = new JSONRPC2Request((String)method, id);
else if (params instanceof List)
request = new JSONRPC2Request((String)method, (List)params, id);
else if (params instanceof Map)
request = new JSONRPC2Request((String)method, (Map)params, id);
else
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Method parameters have unexpected JSON type", jsonString);
// Extract remaining non-std members?
if (options.contains(Option.PARSE_NON_STD_MEMBERS)) {
for (Map.Entry entry: jsonObject.entrySet()) {
request.appendNonStdMember(entry.getKey(), entry.getValue());
}
}
return request;
}
/**
* Parses a JSON-RPC 2.0 notification string.
*
* @param jsonString The JSON-RPC 2.0 notification string, UTF-8
* encoded. Must not be {@code null}.
*
* @return The JSON-RPC 2.0 notification object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
@SuppressWarnings("unchecked")
public JSONRPC2Notification parseJSONRPC2Notification(final String jsonString)
throws JSONRPC2ParseException {
// Initial JSON object parsing
Map jsonObject = parseJSONObject(jsonString);
// Check for JSON-RPC version "2.0"
Object version = jsonObject.remove("jsonrpc");
if (! options.contains(Option.IGNORE_VERSION))
ensureVersion2(version, jsonString);
if (jsonObject.containsKey("id"))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 notification: Must not include identifier", jsonString);
// Extract method name
Object method = jsonObject.remove("method");
if (method == null)
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 notification: Method name missing", jsonString);
else if (! (method instanceof String))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 notification: Method name not a JSON string", jsonString);
else if (((String) method).isEmpty())
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 notification: Method name is an empty string", jsonString);
// Extract params
Object params = jsonObject.get("params");
JSONRPC2Notification notification;
if (params == null)
notification = new JSONRPC2Notification((String)method);
else if (params instanceof List)
notification = new JSONRPC2Notification((String)method, (List)params);
else if (params instanceof Map)
notification = new JSONRPC2Notification((String)method, (Map)params);
else
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 notification: Method parameters have unexpected JSON type", jsonString);
// Extract remaining non-std members?
if (options.contains(Option.PARSE_NON_STD_MEMBERS)) {
for (Map.Entry entry: jsonObject.entrySet()) {
notification.appendNonStdMember(entry.getKey(), entry.getValue());
}
}
return notification;
}
private boolean indicatesError(final Map jsonObject) {
if (getOptions().contains(Option.ALLOW_NULL_ERROR_IN_RESPONSE)) {
return jsonObject.get("error") != null;
}
return jsonObject.containsKey("error");
}
/**
* Parses a JSON-RPC 2.0 response string.
*
* @param jsonString The JSON-RPC 2.0 response string, UTF-8 encoded.
* Must not be {@code null}.
*
* @return The JSON-RPC 2.0 response object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
@SuppressWarnings("unchecked")
public JSONRPC2Response parseJSONRPC2Response(final String jsonString)
throws JSONRPC2ParseException {
// Initial JSON object parsing
Map jsonObject = parseJSONObject(jsonString);
// Check for JSON-RPC version "2.0"
Object version = jsonObject.remove("jsonrpc");
if (! options.contains(Option.IGNORE_VERSION))
ensureVersion2(version, jsonString);
// Extract request ID
Object id = jsonObject.remove("id");
if ( id != null &&
! (id instanceof Boolean) &&
! (id instanceof Number ) &&
! (id instanceof String ) )
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Identifier not a JSON scalar", jsonString);
// Extract result/error and create response object
// Note: result and error are mutually exclusive
JSONRPC2Response response;
if (jsonObject.containsKey("result") && ! indicatesError(jsonObject)) {
// Success
Object res = jsonObject.remove("result");
response = new JSONRPC2Response(res, id);
} else if (! jsonObject.containsKey("result") && indicatesError(jsonObject)) {
// Error JSON object
Object errorJSON = jsonObject.remove("error");
if (errorJSON == null)
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Missing error object", jsonString);
if (! (errorJSON instanceof Map))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Error object not a JSON object");
Map error = (Map)errorJSON;
int errorCode;
try {
errorCode = ((Number)error.get("code")).intValue();
} catch (Exception e) {
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Error code missing or not an integer", jsonString);
}
String errorMessage;
try {
errorMessage = (String)error.get("message");
} catch (Exception e) {
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Error message missing or not a string", jsonString);
}
Object errorData = error.get("data");
response = new JSONRPC2Response(new JSONRPC2Error(errorCode, errorMessage, errorData), id);
} else if (jsonObject.containsKey("result") && indicatesError(jsonObject)) {
// Invalid response
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Must not include both result and error", jsonString);
} else {
// Invalid response
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Neither result nor error specified", jsonString);
}
// Extract remaining non-std members?
if (options.contains(Option.PARSE_NON_STD_MEMBERS)) {
for (Map.Entry entry: jsonObject.entrySet()) {
response.appendNonStdMember(entry.getKey(), entry.getValue());
}
}
return response;
}
/**
* Returns the parse options.
*
* @return The parse options, as unmodifiable set, empty if none.
*/
public Set getOptions() {
return Collections.unmodifiableSet(options);
}
/**
* Controls the preservation of JSON object member order in parsed
* JSON-RPC 2.0 messages.
*
* @param preserveOrder {@code true} to preserve the order of JSON
* object members, else {@code false}.
*/
@Deprecated
public void preserveOrder(final boolean preserveOrder) {
if (preserveOrder) {
options.add(Option.PRESERVE_ORDER);
} else {
options.remove(Option.PRESERVE_ORDER);
}
}
/**
* Returns {@code true} if the order of JSON object members in parsed
* JSON-RPC 2.0 messages is preserved, else {@code false}.
*
* @return {@code true} if order is preserved, else {@code false}.
*/
@Deprecated
public boolean preservesOrder() {
return options.contains(Option.PRESERVE_ORDER);
}
/**
* Specifies whether to ignore the {@code "jsonrpc":"2.0"} version
* member during parsing of JSON-RPC 2.0 messages.
*
* You may want to disable strict 2.0 version checking if the parsed
* JSON-RPC 2.0 messages don't include a version member or if you wish
* to achieve limited compatibility with older JSON-RPC protocol
* versions.
*
* @param ignore {@code true} to skip checks of the
* {@code "jsonrpc":"2.0"} version member in parsed
* JSON-RPC 2.0 messages, else {@code false}.
*/
@Deprecated
public void ignoreVersion(final boolean ignore) {
if (ignore) {
options.add(Option.IGNORE_VERSION);
} else {
options.remove(Option.IGNORE_VERSION);
}
}
/**
* Returns {@code true} if the {@code "jsonrpc":"2.0"} version
* member in parsed JSON-RPC 2.0 messages is ignored, else
* {@code false}.
*
* @return {@code true} if the {@code "jsonrpc":"2.0"} version
* member in parsed JSON-RPC 2.0 messages is ignored, else
* {@code false}.
*/
@Deprecated
public boolean ignoresVersion() {
return options.contains(Option.IGNORE_VERSION);
}
/**
* Specifies whether to parse non-standard members found in JSON-RPC
* 2.0 messages.
*
* @param enable {@code true} to parse non-standard members, else
* {@code false}.
*/
@Deprecated
public void parseNonStdAttributes(final boolean enable) {
if (enable) {
options.add(Option.PARSE_NON_STD_MEMBERS);
} else {
options.remove(Option.PARSE_NON_STD_MEMBERS);
}
}
/**
* Returns {@code true} if non-standard members in JSON-RPC 2.0
* messages are parsed.
*
* @return {@code true} if non-standard members are parsed, else
* {@code false}.
*/
@Deprecated
public boolean parsesNonStdAttributes() {
return options.contains(Option.PARSE_NON_STD_MEMBERS);
}
}