com.thetransactioncompany.jsonrpc2.JSONRPC2Parser Maven / Gradle / Ivy
Show all versions of jsonrpc2-base Show documentation
package com.thetransactioncompany.jsonrpc2;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import net.minidev.json.parser.ContainerFactory;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
/**
* 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
*
*
* The JSON-RPC 2.0 specification and user group forum can be found
* here.
*
* @author Vladimir Dzhuvinov
* @version 1.34.3 (2013-02-19)
*/
public class JSONRPC2Parser {
/**
* Reusable JSON parser. Not thread-safe!
*/
private JSONParser parser;
/**
* If {@code true} the order of the parsed JSON object members must be
* preserved.
*/
private boolean preserveOrder;
/**
* If {@code true} the {@code "jsonrpc":"2.0"} version attribute in the
* JSON-RPC 2.0 message must be ignored during parsing.
*/
private boolean ignoreVersion;
/**
* If {@code true} non-standard JSON-RPC 2.0 message attributes must be
* parsed too.
*/
private boolean parseNonStdAttributes;
/**
* Creates a new JSON-RPC 2.0 message parser.
*
*
The member order of parsed JSON objects in parameters and results
* will not be preserved; strict checking of the 2.0 JSON-RPC version
* attribute will be enforced; non-standard message attributes will be
* ignored. Check the other constructors if you want to specify
* different behaviour.
*/
public JSONRPC2Parser() {
this(false, false, false);
}
/**
* Creates a new JSON-RPC 2.0 message parser.
*
*
Strict checking of the 2.0 JSON-RPC version attribute will be
* enforced; non-standard message attributes will be ignored. Check the
* other constructors if you want to specify different behaviour.
*
* @param preserveOrder If {@code true} the member order of JSON objects
* in parameters and results will be preserved.
*/
public JSONRPC2Parser(final boolean preserveOrder) {
this(preserveOrder, false, false);
}
/**
* Creates a new JSON-RPC 2.0 message parser.
*
*
Non-standard message attributes will be ignored. Check the other
* constructors if you want to specify different behaviour.
*
* @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 attribute in the JSON-RPC 2.0 message
* will not be checked.
*/
public JSONRPC2Parser(final boolean preserveOrder,
final boolean ignoreVersion) {
this(preserveOrder, ignoreVersion, false);
}
/**
* Creates a new JSON-RPC 2.0 message parser.
*
*
This constructor allows full specification of the available
* JSON-RPC message parsing properties.
*
* @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
* attribute in the JSON-RPC 2.0 message
* will not be checked.
* @param parseNonStdAttributes If {@code true} non-standard attributes
* found in the JSON-RPC 2.0 messages will
* be parsed too.
*/
public JSONRPC2Parser(final boolean preserveOrder,
final boolean ignoreVersion,
final boolean parseNonStdAttributes) {
// Numbers parsed as long/double, requires JSON Smart 1.0.9+
parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
this.preserveOrder = preserveOrder;
this.ignoreVersion = ignoreVersion;
this.parseNonStdAttributes = parseNonStdAttributes;
}
/**
* Parses a JSON object string. Provides the initial parsing of
* JSON-RPC 2.0 messages. The member order of JSON objects will be
* preserved if {@link #preserveOrder} is set to {@code true}.
*
* @param jsonString The JSON string to parse. Must not be
* {@code null}.
*
* @return The parsed JSON object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
@SuppressWarnings("unchecked")
private Map parseJSONObject(final String jsonString)
throws JSONRPC2ParseException {
if (jsonString.trim().length()==0)
throw new JSONRPC2ParseException("Invalid JSON: Empty string",
JSONRPC2ParseException.JSON,
jsonString);
Object json;
// Parse the JSON string
try {
if (preserveOrder)
json = parser.parse(jsonString, ContainerFactory.FACTORY_ORDERED);
else
json = parser.parse(jsonString);
} catch (ParseException e) {
// 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;
}
/**
* Ensures the specified parameter is a {@code String} object set to
* "2.0". This method is intended to check the "jsonrpc" attribute
* during parsing of JSON-RPC messages.
*
* @param version The version parameter. Must not be {@code null}.
* @param jsonString The original JSON string.
*
* @throws JSONRPC2Exception If the parameter is not a string that
* equals "2.0".
*/
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 An instance of {@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 corresponding 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 (! ignoreVersion)
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).length() == 0)
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);
// Extract params
Object params = jsonObject.remove("params");
JSONRPC2Request request = null;
if (params == null)
request = new JSONRPC2Request((String)method, id);
else if (params instanceof List)
request = new JSONRPC2Request((String)method, (List