Bundle I2PControl 0.12, as a console webapp

Includes mods to use org.json.simple package.
See licenses/LICENSE-Apache2.0.txt
Includes jBCrypt:
Copyright (c) 2006 Damien Miller <djm@mindrot.org>
See licenses/LICENSE-jBCrypt.txt
Includes jsonrpc2 libs:
See licenses/LICENSE-Apache2.0.txt
http://software.dzhuvinov.com/json-rpc-2.0-server.html
Jars from maven central:
jsonrpc2-base-1.38.1-sources.jar  22-Oct-2017
jsonrpc2-server-1.11-sources.jar  16-Mar-2015
This commit is contained in:
zzz
2018-11-25 13:26:43 +00:00
parent d6e350184c
commit d4caafb592
44 changed files with 10640 additions and 1 deletions

View File

@@ -0,0 +1,274 @@
package com.thetransactioncompany.jsonrpc2;
import org.json.simple.JSONObject;
/**
* Represents a JSON-RPC 2.0 error that occurred during the processing of a
* request. This class is immutable.
*
* <p>The protocol expects error objects to be structured like this:
*
* <ul>
* <li>{@code code} An integer that indicates the error type.
* <li>{@code message} A string providing a short description of the
* error. The message should be limited to a concise single sentence.
* <li>{@code data} Additional information, which may be omitted. Its
* contents is entirely defined by the application.
* </ul>
*
* <p>Note that the "Error" word in the class name was put there solely to
* comply with the parlance of the JSON-RPC spec. This class doesn't inherit
* from {@code java.lang.Error}. It's a regular subclass of
* {@code java.lang.Exception} and, if thrown, it's to indicate a condition
* that a reasonable application might want to catch.
*
* <p>This class also includes convenient final static instances for all
* standard JSON-RPC 2.0 errors:
*
* <ul>
* <li>{@link #PARSE_ERROR} JSON parse error (-32700)
* <li>{@link #INVALID_REQUEST} Invalid JSON-RPC 2.0 Request (-32600)
* <li>{@link #METHOD_NOT_FOUND} Method not found (-32601)
* <li>{@link #INVALID_PARAMS} Invalid parameters (-32602)
* <li>{@link #INTERNAL_ERROR} Internal error (-32603)
* </ul>
*
* <p>Note that the range -32099..-32000 is reserved for additional server
* errors.
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Error extends Exception {
/**
* Serial version UID.
*/
private static final long serialVersionUID = 4682571044532698806L;
/**
* JSON parse error (-32700).
*/
public static final JSONRPC2Error PARSE_ERROR = new JSONRPC2Error(-32700, "JSON parse error");
/**
* Invalid JSON-RPC 2.0 request error (-32600).
*/
public static final JSONRPC2Error INVALID_REQUEST = new JSONRPC2Error(-32600, "Invalid request");
/**
* Method not found error (-32601).
*/
public static final JSONRPC2Error METHOD_NOT_FOUND = new JSONRPC2Error(-32601, "Method not found");
/**
* Invalid parameters error (-32602).
*/
public static final JSONRPC2Error INVALID_PARAMS = new JSONRPC2Error(-32602, "Invalid parameters");
/**
* Internal JSON-RPC 2.0 error (-32603).
*/
public static final JSONRPC2Error INTERNAL_ERROR = new JSONRPC2Error(-32603, "Internal error");
/**
* The error code.
*/
private final int code;
/**
* The optional error data.
*/
private final Object data;
/**
* Appends the specified string to the message of a JSON-RPC 2.0 error.
*
* @param err The JSON-RPC 2.0 error. Must not be {@code null}.
* @param apx The string to append to the original error message.
*
* @return A new JSON-RPC 2.0 error with the appended message.
*/
@Deprecated
public static JSONRPC2Error appendMessage(final JSONRPC2Error err, final String apx) {
return new JSONRPC2Error(err.getCode(), err.getMessage() + apx, err.getData());
}
/**
* Sets the specified data to a JSON-RPC 2.0 error.
*
* @param err The JSON-RPC 2.0 error to have its data field set. Must
* not be {@code null}.
* @param data Optional error data, must <a href="#map">map</a> to a
* valid JSON type.
*
* @return A new JSON-RPC 2.0 error with the set data.
*/
@Deprecated
public static JSONRPC2Error setData(final JSONRPC2Error err, final Object data) {
return new JSONRPC2Error(err.getCode(), err.getMessage(), data);
}
/**
* Creates a new JSON-RPC 2.0 error with the specified code and
* message. The optional data is omitted.
*
* @param code The error code (standard pre-defined or
* application-specific).
* @param message The error message.
*/
public JSONRPC2Error(int code, String message) {
this(code, message, null);
}
/**
* Creates a new JSON-RPC 2.0 error with the specified code,
* message and data.
*
* @param code The error code (standard pre-defined or
* application-specific).
* @param message The error message.
* @param data Optional error data, must <a href="#map">map</a>
* to a valid JSON type.
*/
public JSONRPC2Error(int code, String message, Object data) {
super(message);
this.code = code;
this.data = data;
}
/**
* Gets the JSON-RPC 2.0 error code.
*
* @return The error code.
*/
public int getCode() {
return code;
}
/**
* Gets the JSON-RPC 2.0 error data.
*
* @return The error data, {@code null} if none was specified.
*/
public Object getData() {
return data;
}
/**
* Sets the specified data to a JSON-RPC 2.0 error.
*
* @param data Optional error data, must <a href="#map">map</a> to a
* valid JSON type.
*
* @return A new JSON-RPC 2.0 error with the set data.
*/
public JSONRPC2Error setData(final Object data) {
return new JSONRPC2Error(code, getMessage(), data);
}
/**
* Appends the specified string to the message of this JSON-RPC 2.0
* error.
*
* @param apx The string to append to the original error message.
*
* @return A new JSON-RPC 2.0 error with the appended message.
*/
public JSONRPC2Error appendMessage(final String apx) {
return new JSONRPC2Error(code, getMessage() + apx, data);
}
/**
* @see #toJSONObject
*/
@Deprecated
public JSONObject toJSON() {
return toJSONObject();
}
/**
* Returns a JSON object representation of this JSON-RPC 2.0 error.
*
* @return A JSON object representing this error object.
*/
public JSONObject toJSONObject() {
JSONObject out = new JSONObject();
out.put("code", code);
out.put("message", super.getMessage());
if (data != null)
out.put("data", data);
return out;
}
/**
* Serialises the error object to a JSON string.
*
* @return A JSON-encoded string representing this error object.
*/
@Override
public String toString() {
return toJSON().toString();
}
/**
* Overrides {@code Object.equals()}.
*
* @param object The object to compare to.
*
* @return {@code true} if both objects are instances if this class and
* their error codes are identical, {@code false} if not.
*/
@Override
public boolean equals(Object object) {
return object != null &&
object instanceof JSONRPC2Error &&
code == ((JSONRPC2Error)object).getCode();
}
}

View File

@@ -0,0 +1,251 @@
package com.thetransactioncompany.jsonrpc2;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONAware;
import org.json.simple.JSONObject;
/**
* The base abstract class for JSON-RPC 2.0 requests, notifications and
* responses. Provides common methods for parsing (from JSON string) and
* serialisation (to JSON string) of these three message types.
*
* <p>Example parsing and serialisation back to JSON:
*
* <pre>
* String jsonString = "{\"method\":\"progressNotify\",\"params\":[\"75%\"],\"jsonrpc\":\"2.0\"}";
*
* JSONRPC2Message message = null;
*
* // parse
* try {
* message = JSONRPC2Message.parse(jsonString);
* } catch (JSONRPC2ParseException e) {
* // handle parse exception
* }
*
* if (message instanceof JSONRPC2Request)
* System.out.println("The message is a request");
* else if (message instanceof JSONRPC2Notification)
* System.out.println("The message is a notification");
* else if (message instanceof JSONRPC2Response)
* System.out.println("The message is a response");
*
* // serialise back to JSON string
* System.out.println(message);
*
* </pre>
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public abstract class JSONRPC2Message implements JSONAware {
/**
* Map of non-standard JSON-RPC 2.0 message attributes, {@code null} if
* none.
*/
private Map <String,Object> nonStdAttributes = null;
/**
* 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 JSON string represents.
*
* <p>Batched requests / notifications are not supported.
*
* <p>This method is thread-safe.
*
* <p>If you are certain about the message type use the dedicated
* {@link JSONRPC2Request#parse}, {@link JSONRPC2Notification#parse}
* or {@link JSONRPC2Response#parse} methods. They are more efficient
* and provide a more detailed parse error reporting.
*
* <p>The member order of parsed JSON objects will not be preserved
* (for efficiency reasons) and the JSON-RPC 2.0 version field must be
* set to "2.0". To change this behaviour check the optional {@link
* #parse(String,boolean,boolean)} method.
*
* @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 parsing
* failed.
*/
public static JSONRPC2Message parse(final String jsonString)
throws JSONRPC2ParseException {
return parse(jsonString, false, false);
}
/**
* 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.
*
* <p>Batched requests / notifications are not supported.
*
* <p>This method is thread-safe.
*
* <p>If you are certain about the message type use the dedicated
* {@link JSONRPC2Request#parse}, {@link JSONRPC2Notification#parse}
* or {@link JSONRPC2Response#parse} methods. They are more efficient
* and provide a 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}.
* @param preserveOrder If {@code true} the member order of JSON objects
* in parameters and results must be preserved.
* @param ignoreVersion If {@code true} the {@code "jsonrpc":"2.0"}
* version field in the JSON-RPC 2.0 message will
* not be checked.
*
* @return An instance of {@link JSONRPC2Request},
* {@link JSONRPC2Notification} or {@link JSONRPC2Response}.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Message parse(final String jsonString, final boolean preserveOrder, final boolean ignoreVersion)
throws JSONRPC2ParseException {
JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder, ignoreVersion);
return parser.parseJSONRPC2Message(jsonString);
}
/**
* Appends a non-standard attribute to this JSON-RPC 2.0 message. This is
* done by adding a new member (key / value pair) to the top level JSON
* object representing the message.
*
* <p>You may use this method to add meta and debugging attributes,
* such as the request processing time, to a JSON-RPC 2.0 message.
*
* @param name The attribute name. Must not conflict with the existing
* "method", "id", "params", "result", "error" and "jsonrpc"
* attributes reserved by the JSON-RPC 2.0 protocol, else
* an {@code IllegalArgumentException} will be thrown. Must
* not be {@code null} either.
* @param value The attribute value. Must be of type String, boolean,
* number, List, Map or null, else an
* {@code IllegalArgumentException} will be thrown.
*/
public void appendNonStdAttribute(final String name, final Object value) {
// Name check
if (name == null ||
name.equals("method") ||
name.equals("id") ||
name.equals("params") ||
name.equals("result") ||
name.equals("error") ||
name.equals("jsonrpc") )
throw new IllegalArgumentException("Non-standard attribute name violation");
// Value check
if ( value != null &&
! (value instanceof Boolean) &&
! (value instanceof Number) &&
! (value instanceof String) &&
! (value instanceof List) &&
! (value instanceof Map) )
throw new IllegalArgumentException("Illegal non-standard attribute value, must map to a valid JSON type");
if (nonStdAttributes == null)
nonStdAttributes = new HashMap<String,Object>();
nonStdAttributes.put(name, value);
}
/**
* Retrieves a non-standard JSON-RPC 2.0 message attribute.
*
* @param name The name of the non-standard attribute to retrieve. Must
* not be {@code null}.
*
* @return The value of the non-standard attribute (may also be
* {@code null}, {@code null} if not found.
*/
public Object getNonStdAttribute(final String name) {
if (nonStdAttributes == null)
return null;
return nonStdAttributes.get(name);
}
/**
* Retrieves the non-standard JSON-RPC 2.0 message attributes.
*
* @return The non-standard attributes as a map, {@code null} if none.
*/
public Map<String,Object> getNonStdAttributes() {
return nonStdAttributes;
}
/**
* Returns a JSON object representing this JSON-RPC 2.0 message.
*
* @return The JSON object.
*/
public abstract JSONObject toJSONObject();
/**
* Returns a JSON string representation of this JSON-RPC 2.0 message.
*
* @see #toString
*
* @return The JSON object string representing this JSON-RPC 2.0
* message.
*/
public String toJSONString() {
return toString();
}
/**
* Serialises this JSON-RPC 2.0 message to a JSON object string.
*
* @return The JSON object string representing this JSON-RPC 2.0
* message.
*/
@Override
public String toString() {
return toJSONObject().toString();
}
}

View File

@@ -0,0 +1,448 @@
package com.thetransactioncompany.jsonrpc2;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONObject;
/**
* Represents a JSON-RPC 2.0 notification.
*
* <p>Notifications provide a mean for calling a remote procedure without
* generating a response. Note that notifications are inherently unreliable
* as no confirmation is sent back to the caller.
*
* <p>Notifications have the same JSON structure as requests, except that they
* lack an identifier:
* <ul>
* <li>{@code method} The name of the remote method to call.
* <li>{@code params} The required method parameters (if any), which can
* be packed into a JSON array or object.
* <li>{@code jsonrpc} A string indicating the JSON-RPC protocol version
* set to "2.0".
* </ul>
*
* <p>Here is a sample JSON-RPC 2.0 notification string:
*
* <pre>
* {
* "method" : "progressNotify",
* "params" : ["75%"],
* "jsonrpc" : "2.0"
* }
* </pre>
*
* <p>This class provides two methods to obtain a request object:
* <ul>
* <li>Pass a JSON-RPC 2.0 notification string to the static
* {@link #parse} method, or
* <li>Invoke one of the constructors with the appropriate arguments.
* </ul>
*
* <p>Example 1: Parsing a notification string:
*
* <pre>
* String jsonString = "{\"method\":\"progressNotify\",\"params\":[\"75%\"],\"jsonrpc\":\"2.0\"}";
*
* JSONRPC2Notification notification = null;
*
* try {
* notification = JSONRPC2Notification.parse(jsonString);
*
* } catch (JSONRPC2ParseException e) {
* // handle exception
* }
* </pre>
*
* <p>Example 2: Recreating the above request:
*
* <pre>
* String method = "progressNotify";
* List&lt;Object&gt; params = new Vector&lt;Object&gt;();
* params.add("75%");
*
* JSONRPC2Notification notification = new JSONRPC2Notification(method, params);
*
* System.out.println(notification);
* </pre>
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Notification extends JSONRPC2Message {
/**
* The requested method name.
*/
private String method;
/**
* The positional parameters, {@code null} if none.
*/
private List<Object> positionalParams;
/**
* The named parameters, {@code null} if none.
*/
private Map<String,Object> namedParams;
/**
* Parses a JSON-RPC 2.0 notification string. This method is
* thread-safe.
*
* @param jsonString The JSON-RPC 2.0 notification string, UTF-8
* encoded. Must not be {@code null}.
*
* @return The corresponding JSON-RPC 2.0 notification object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Notification parse(final String jsonString)
throws JSONRPC2ParseException {
return parse(jsonString, false, false, false);
}
/**
* Parses a JSON-RPC 2.0 notification string. This method is
* thread-safe.
*
* @param jsonString The JSON-RPC 2.0 notification string, UTF-8
* encoded. Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in parameters.
*
* @return The corresponding JSON-RPC 2.0 notification object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Notification parse(final String jsonString,
final boolean preserveOrder)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, false, false);
}
/**
* Parses a JSON-RPC 2.0 notification string. This method is
* thread-safe.
*
* @param jsonString The JSON-RPC 2.0 notification string, UTF-8
* encoded. Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in parameters.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version attribute in the
* JSON-RPC 2.0 message.
*
* @return The corresponding JSON-RPC 2.0 notification object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Notification parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, ignoreVersion, false);
}
/**
* Parses a JSON-RPC 2.0 notification string. This method is
* thread-safe.
*
* @param jsonString The JSON-RPC 2.0 notification string,
* UTF-8 encoded. Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of
* JSON object members in parameters.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version
* attribute in the JSON-RPC 2.0 message.
* @param parseNonStdAttributes {@code true} to parse non-standard
* attributes found in the JSON-RPC 2.0
* message.
*
* @return The corresponding JSON-RPC 2.0 notification object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Notification parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion,
final boolean parseNonStdAttributes)
throws JSONRPC2ParseException {
JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder, ignoreVersion, parseNonStdAttributes);
return parser.parseJSONRPC2Notification(jsonString);
}
/**
* Constructs a new JSON-RPC 2.0 notification with no parameters.
*
* @param method The name of the requested method. Must not be
* {@code null}.
*/
public JSONRPC2Notification(final String method) {
setMethod(method);
setParams(null);
}
/**
* Constructs a new JSON-RPC 2.0 notification with positional (JSON
* array) parameters.
*
* @param method The name of the requested method. Must not
* be {@code null}.
* @param positionalParams The positional (JSON array) parameters,
* {@code null} if none.
*/
public JSONRPC2Notification(final String method,
final List<Object> positionalParams) {
setMethod(method);
setPositionalParams(positionalParams);
}
/**
* Constructs a new JSON-RPC 2.0 notification with named (JSON object)
* parameters.
*
* @param method The name of the requested method.
* @param namedParams The named (JSON object) parameters, {@code null}
* if none.
*/
public JSONRPC2Notification(final String method,
final Map <String,Object> namedParams) {
setMethod(method);
setNamedParams(namedParams);
}
/**
* Gets the name of the requested method.
*
* @return The method name.
*/
public String getMethod() {
return method;
}
/**
* Sets the name of the requested method.
*
* @param method The method name. Must not be {@code null}.
*/
public void setMethod(final String method) {
// The method name is mandatory
if (method == null)
throw new IllegalArgumentException("The method name must not be null");
this.method = method;
}
/**
* Gets the parameters type ({@link JSONRPC2ParamsType#ARRAY positional},
* {@link JSONRPC2ParamsType#OBJECT named} or
* {@link JSONRPC2ParamsType#NO_PARAMS none}).
*
* @return The parameters type.
*/
public JSONRPC2ParamsType getParamsType() {
if (positionalParams == null && namedParams == null)
return JSONRPC2ParamsType.NO_PARAMS;
if (positionalParams != null)
return JSONRPC2ParamsType.ARRAY;
if (namedParams != null)
return JSONRPC2ParamsType.OBJECT;
else
return JSONRPC2ParamsType.NO_PARAMS;
}
/**
* Gets the notification parameters.
*
* <p>This method was deprecated in version 1.30. Use
* {@link #getPositionalParams} or {@link #getNamedParams} instead.
*
* @return The parameters as {@code List&lt;Object&gt;} for positional
* (JSON array), {@code Map&lt;String,Object&gt;} for named
* (JSON object), or {@code null} if none.
*/
@Deprecated
public Object getParams() {
switch (getParamsType()) {
case ARRAY:
return positionalParams;
case OBJECT:
return namedParams;
default:
return null;
}
}
/**
* Gets the positional (JSON array) parameters.
*
* @since 1.30
*
* @return The positional (JSON array) parameters, {@code null} if none
* or named.
*/
public List<Object> getPositionalParams() {
return positionalParams;
}
/**
* Gets the named parameters.
*
* @since 1.30
*
* @return The named (JSON object) parameters, {@code null} if none or
* positional.
*/
public Map<String,Object> getNamedParams() {
return namedParams;
}
/**
* Sets the notification parameters.
*
* <p>This method was deprecated in version 1.30. Use
* {@link #setPositionalParams} or {@link #setNamedParams} instead.
*
* @param params The parameters. For positional (JSON array) pass a
* {@code List&lt;Object&gt;}. For named (JSON object)
* pass a {@code Map&lt;String,Object&gt;}. If there are
* no parameters pass {@code null}.
*/
@Deprecated
@SuppressWarnings("unchecked")
public void setParams(final Object params) {
if (params == null) {
positionalParams = null;
namedParams = null;
} else if (params instanceof List) {
positionalParams = (List<Object>) params;
} else if (params instanceof Map) {
namedParams = (Map<String, Object>) params;
} else {
throw new IllegalArgumentException("The notification parameters must be of type List, Map or null");
}
}
/**
* Sets the positional (JSON array) request parameters.
*
* @since 1.30
*
* @param positionalParams The positional (JSON array) request
* parameters, {@code null} if none.
*/
public void setPositionalParams(final List<Object> positionalParams) {
if (positionalParams == null)
return;
this.positionalParams = positionalParams;
}
/**
* Sets the named (JSON object) request parameters.
*
* @since 1.30
*
* @param namedParams The named (JSON object) request parameters,
* {@code null} if none.
*/
public void setNamedParams(final Map<String,Object> namedParams) {
if (namedParams == null)
return;
this.namedParams = namedParams;
}
@Override
public JSONObject toJSONObject() {
JSONObject notf = new JSONObject();
notf.put("method", method);
// The params can be omitted if none
switch (getParamsType()) {
case ARRAY:
notf.put("params", positionalParams);
break;
case OBJECT:
notf.put("params", namedParams);
break;
}
notf.put("jsonrpc", "2.0");
Map <String,Object> nonStdAttributes = getNonStdAttributes();
if (nonStdAttributes != null) {
for (final Map.Entry<String,Object> attr: nonStdAttributes.entrySet())
notf.put(attr.getKey(), attr.getValue());
}
return notf;
}
}

View File

@@ -0,0 +1,37 @@
package com.thetransactioncompany.jsonrpc2;
/**
* Enumeration of the three parameter types in JSON-RPC 2.0 requests and
* notifications.
*
* <ul>
* <li>{@link #NO_PARAMS} The method takes no parameters.
* <li>{@link #ARRAY} The method takes positional parameters, packed as a
* JSON array, e.g. {@code ["val1", "val2", ...]}.
* <li>{@link #OBJECT} The method takes named parameters, packed as a JSON
* object, e.g. {@code {"param1":"val1", "param2":"val2", ...}}.
* </ul>
*
* @author Vladimir Dzhuvinov
*/
public enum JSONRPC2ParamsType {
/**
* No parameters.
*/
NO_PARAMS,
/**
* Positional parameters, packed as a JSON array.
*/
ARRAY,
/**
* Named parameters, packed as a JSON object.
*/
OBJECT
}

View File

@@ -0,0 +1,113 @@
package com.thetransactioncompany.jsonrpc2;
/**
* Thrown to indicate an exception during the parsing of a JSON-RPC 2.0
* message string.
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2ParseException extends Exception {
/**
* Serial version UID.
*/
private static final long serialVersionUID = 3376608778436136410L;
/**
* Indicates a parse exception caused by a JSON message not conforming
* to the JSON-RPC 2.0 protocol.
*/
public static final int PROTOCOL = 0;
/**
* Indicates a parse exception caused by invalid JSON.
*/
public static final int JSON = 1;
/**
* The parse exception cause type. Default is {@link #PROTOCOL}.
*/
private int causeType = PROTOCOL;
/**
* The string that could't be parsed.
*/
private String unparsableString = null;
/**
* Creates a new parse exception with the specified message. The cause
* type is set to {@link #PROTOCOL}.
*
* @param message The exception message.
*/
public JSONRPC2ParseException(final String message) {
super(message);
}
/**
* Creates a new parse exception with the specified message and the
* original string that didn't parse. The cause type is set to
* {@link #PROTOCOL}.
*
* @param message The exception message.
* @param unparsableString The unparsable string.
*/
public JSONRPC2ParseException(final String message, final String unparsableString) {
super(message);
this.unparsableString = unparsableString;
}
/**
* Creates a new parse exception with the specified message, cause type
* and the original string that didn't parse.
*
* @param message The exception message.
* @param causeType The exception cause type, either
* {@link #PROTOCOL} or {@link #JSON}.
* @param unparsableString The unparsable string.
*/
public JSONRPC2ParseException(final String message, final int causeType, final String unparsableString) {
super(message);
if (causeType != PROTOCOL && causeType != JSON)
throw new IllegalArgumentException("Cause type must be either PROTOCOL or JSON");
this.causeType = causeType;
this.unparsableString = unparsableString;
}
/**
* Gets the parse exception cause type.
*
* @return The cause type, either {@link #PROTOCOL} or {@link #JSON}.
*/
public int getCauseType() {
return causeType;
}
/**
* Gets original string that caused the parse exception (if specified).
*
* @return The string that didn't parse, {@code null} if none.
*/
public String getUnparsableString() {
return unparsableString;
}
}

View File

@@ -0,0 +1,654 @@
package com.thetransactioncompany.jsonrpc2;
import java.util.List;
import java.util.Map;
import org.json.simple.parser.ContainerFactory;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
/**
* Parses JSON-RPC 2.0 request, notification and response messages.
*
* <p>Parsing of batched requests / notifications is not supported.
*
* <p>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.
*
* <p>Example:
*
* <pre>
* 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
* }
*
* </pre>
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Parser {
/**
* Reusable JSON parser. Not thread-safe!
*/
private final 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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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();
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<String,Object> 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<String,Object>)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 JSONRPC2ParseException 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.
*
* <p>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<String,Object> 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;
if (params == null)
request = new JSONRPC2Request((String)method, id);
else if (params instanceof List)
request = new JSONRPC2Request((String)method, (List<Object>)params, id);
else if (params instanceof Map)
request = new JSONRPC2Request((String)method, (Map<String,Object>)params, id);
else
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Method parameters have unexpected JSON type", jsonString);
// Extract remaining non-std params?
if (parseNonStdAttributes) {
for (Map.Entry<String,Object> entry: jsonObject.entrySet()) {
request.appendNonStdAttribute(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 corresponding 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<String,Object> 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 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).length() == 0)
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<Object>)params);
else if (params instanceof Map)
notification = new JSONRPC2Notification((String)method, (Map<String,Object>)params);
else
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 notification: Method parameters have unexpected JSON type", jsonString);
// Extract remaining non-std params?
if (parseNonStdAttributes) {
for (Map.Entry<String,Object> entry: jsonObject.entrySet()) {
notification.appendNonStdAttribute(entry.getKey(), entry.getValue());
}
}
return notification;
}
/**
* 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 corresponding 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<String,Object> jsonObject = parseJSONObject(jsonString);
// Check for JSON-RPC version "2.0"
Object version = jsonObject.remove("jsonrpc");
if (! ignoreVersion)
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") && ! jsonObject.containsKey("error")) {
// Success
Object res = jsonObject.remove("result");
response = new JSONRPC2Response(res, id);
}
else if (! jsonObject.containsKey("result") && jsonObject.containsKey("error")) {
// 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<String,Object> error = (Map<String,Object>)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") && jsonObject.containsKey("error")) {
// Invalid response
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: You cannot have result and error at the same time", jsonString);
}
else if (! jsonObject.containsKey("result") && ! jsonObject.containsKey("error")){
// Invalid response
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Neither result nor error specified", jsonString);
}
else {
throw new AssertionError();
}
// Extract remaining non-std params?
if (parseNonStdAttributes) {
for (Map.Entry<String,Object> entry: jsonObject.entrySet()) {
response.appendNonStdAttribute(entry.getKey(), entry.getValue());
}
}
return response;
}
/**
* 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}.
*/
public void preserveOrder(final boolean preserveOrder) {
this.preserveOrder = preserveOrder;
}
/**
* 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}.
*/
public boolean preservesOrder() {
return preserveOrder;
}
/**
* Specifies whether to ignore the {@code "jsonrpc":"2.0"} version
* attribute during parsing of JSON-RPC 2.0 messages.
*
* <p>You may with to disable strict 2.0 version checking if the parsed
* JSON-RPC 2.0 messages don't include a version attribute 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 attribute in parsed
* JSON-RPC 2.0 messages, else {@code false}.
*/
public void ignoreVersion(final boolean ignore) {
ignoreVersion = ignore;
}
/**
* Returns {@code true} if the {@code "jsonrpc":"2.0"} version
* attribute in parsed JSON-RPC 2.0 messages is ignored, else
* {@code false}.
*
* @return {@code true} if the {@code "jsonrpc":"2.0"} version
* attribute in parsed JSON-RPC 2.0 messages is ignored, else
* {@code false}.
*/
public boolean ignoresVersion() {
return ignoreVersion;
}
/**
* Specifies whether to parse non-standard attributes found in JSON-RPC
* 2.0 messages.
*
* @param enable {@code true} to parse non-standard attributes, else
* {@code false}.
*/
public void parseNonStdAttributes(final boolean enable) {
parseNonStdAttributes = enable;
}
/**
* Returns {@code true} if non-standard attributes in JSON-RPC 2.0
* messages are parsed.
*
* @return {@code true} if non-standard attributes are parsed, else
* {@code false}.
*/
public boolean parsesNonStdAttributes() {
return parseNonStdAttributes;
}
}

View File

@@ -0,0 +1,507 @@
package com.thetransactioncompany.jsonrpc2;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONObject;
/**
* Represents a JSON-RPC 2.0 request.
*
* <p>A request carries four pieces of data:
* <ul>
* <li>{@code method} The name of the remote method to call.
* <li>{@code params} The required method parameters (if any), which can
* be packed into a JSON array or object.
* <li>{@code id} An identifier which is echoed back to the client with
* the response.
* <li>{@code jsonrpc} A string indicating the JSON-RPC protocol version
* set to "2.0".
* </ul>
*
* <p>Here is a sample JSON-RPC 2.0 request string:
*
* <pre>
* {
* "method" : "makePayment",
* "params" : { "recipient" : "Penny Adams", "amount":175.05 },
* "id" : "0001",
* "jsonrpc" : "2.0"
* }
* </pre>
*
* <p>This class provides two methods to obtain a request object:
* <ul>
* <li>Pass a JSON-RPC 2.0 request string to the static
* {@link #parse} method, or
* <li>Invoke one of the constructors with the appropriate arguments.
* </ul>
*
* <p>Example 1: Parsing a request string:
*
* <pre>
* String jsonString = "{\"method\":\"makePayment\"," +
* "\"params\":{\"recipient\":\"Penny Adams\",\"amount\":175.05}," +
* "\"id\":\"0001\","+
* "\"jsonrpc\":\"2.0\"}";
*
* JSONRPC2Request req = null;
*
* try {
* req = JSONRPC2Request.parse(jsonString);
*
* } catch (JSONRPC2ParseException e) {
* // handle exception
* }
* </pre>
*
* <p>Example 2: Recreating the above request:
*
* <pre>
* String method = "makePayment";
* Map&lt;String,Object&gt; params = new HashMap&lt;String,Object&gt;();
* params.put("recipient", "Penny Adams");
* params.put("amount", 175.05);
* String id = "0001";
*
* JSONRPC2Request req = new JSONRPC2Request(method, params, id);
*
* System.out.println(req);
* </pre>
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Request extends JSONRPC2Message {
/**
* The method name.
*/
private String method;
/**
* The positional parameters, {@code null} if none.
*/
private List<Object> positionalParams;
/**
* The named parameters, {@code null} if none.
*/
private Map<String,Object> namedParams;
/**
* The request identifier.
*/
private Object id;
/**
* Parses a JSON-RPC 2.0 request string. This method is thread-safe.
*
* @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.
*/
public static JSONRPC2Request parse(final String jsonString)
throws JSONRPC2ParseException {
return parse(jsonString, false, false, false);
}
/**
* Parses a JSON-RPC 2.0 request string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 request string, UTF-8 encoded.
* Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in parameters.
*
* @return The corresponding JSON-RPC 2.0 request object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Request parse(final String jsonString,
final boolean preserveOrder)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, false, false);
}
/**
* Parses a JSON-RPC 2.0 request string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 request string, UTF-8 encoded.
* Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in parameters.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version attribute in the
* JSON-RPC 2.0 message.
*
* @return The corresponding JSON-RPC 2.0 request object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Request parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, ignoreVersion, false);
}
/**
* Parses a JSON-RPC 2.0 request string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 request string, UTF-8
* encoded. Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of
* JSON object members in parameters.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version
* attribute in the JSON-RPC 2.0 message.
* @param parseNonStdAttributes {@code true} to parse non-standard
* attributes found in the JSON-RPC 2.0
* message.
*
* @return The corresponding JSON-RPC 2.0 request object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Request parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion,
final boolean parseNonStdAttributes)
throws JSONRPC2ParseException {
JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder,
ignoreVersion,
parseNonStdAttributes);
return parser.parseJSONRPC2Request(jsonString);
}
/**
* Constructs a new JSON-RPC 2.0 request with no parameters.
*
* @param method The name of the requested method. Must not be
* {@code null}.
* @param id The request identifier echoed back to the caller.
* The value must <a href="#map">map</a> to a JSON
* scalar ({@code null} and fractions, however, should
* be avoided).
*/
public JSONRPC2Request(final String method, final Object id) {
setMethod(method);
setID(id);
}
/**
* Constructs a new JSON-RPC 2.0 request with positional (JSON array)
* parameters.
*
* @param method The name of the requested method. Must not
* be {@code null}.
* @param positionalParams The positional (JSON array) parameters,
* {@code null} if none.
* @param id The request identifier echoed back to the
* caller. The value must <a href="#map">map</a>
* to a JSON scalar ({@code null} and
* fractions, however, should be avoided).
*/
public JSONRPC2Request(final String method,
final List<Object> positionalParams,
final Object id) {
setMethod(method);
setPositionalParams(positionalParams);
setID(id);
}
/**
* Constructs a new JSON-RPC 2.0 request with named (JSON object)
* parameters.
*
* @param method The name of the requested method.
* @param namedParams The named (JSON object) parameters, {@code null}
* if none.
* @param id The request identifier echoed back to the caller.
* The value must <a href="#map">map</a> to a JSON
* scalar ({@code null} and fractions, however,
* should be avoided).
*/
public JSONRPC2Request(final String method,
final Map <String,Object> namedParams,
final Object id) {
setMethod(method);
setNamedParams(namedParams);
setID(id);
}
/**
* Gets the name of the requested method.
*
* @return The method name.
*/
public String getMethod() {
return method;
}
/**
* Sets the name of the requested method.
*
* @param method The method name. Must not be {@code null}.
*/
public void setMethod(final String method) {
// The method name is mandatory
if (method == null)
throw new IllegalArgumentException("The method name must not be null");
this.method = method;
}
/**
* Gets the parameters type ({@link JSONRPC2ParamsType#ARRAY positional},
* {@link JSONRPC2ParamsType#OBJECT named} or
* {@link JSONRPC2ParamsType#NO_PARAMS none}).
*
* @return The parameters type.
*/
public JSONRPC2ParamsType getParamsType() {
if (positionalParams == null && namedParams == null)
return JSONRPC2ParamsType.NO_PARAMS;
if (positionalParams != null)
return JSONRPC2ParamsType.ARRAY;
if (namedParams != null)
return JSONRPC2ParamsType.OBJECT;
else
return JSONRPC2ParamsType.NO_PARAMS;
}
/**
* Gets the request parameters.
*
* <p>This method was deprecated in version 1.30. Use
* {@link #getPositionalParams} or {@link #getNamedParams} instead.
*
* @return The parameters as {@code List&lt;Object&gt;} for positional
* (JSON array), {@code Map&lt;String,Object&gt;} for named
* (JSON object), or {@code null} if none.
*/
@Deprecated
public Object getParams() {
switch (getParamsType()) {
case ARRAY:
return positionalParams;
case OBJECT:
return namedParams;
default:
return null;
}
}
/**
* Gets the positional (JSON array) parameters.
*
* @since 1.30
*
* @return The positional (JSON array) parameters, {@code null} if none
* or named.
*/
public List<Object> getPositionalParams() {
return positionalParams;
}
/**
* Gets the named parameters.
*
* @since 1.30
*
* @return The named (JSON object) parameters, {@code null} if none or
* positional.
*/
public Map<String,Object> getNamedParams() {
return namedParams;
}
/**
* Sets the request parameters.
*
* <p>This method was deprecated in version 1.30. Use
* {@link #setPositionalParams} or {@link #setNamedParams} instead.
*
* @param params The parameters. For positional (JSON array) pass a
* {@code List&lt;Object&gt;}. For named (JSON object)
* pass a {@code Map&lt;String,Object&gt;}. If there are
* no parameters pass {@code null}.
*/
@Deprecated
@SuppressWarnings("unchecked")
public void setParams(final Object params) {
if (params == null) {
positionalParams = null;
namedParams = null;
} else if (params instanceof List) {
positionalParams = (List<Object>) params;
} else if (params instanceof Map) {
namedParams = (Map<String, Object>) params;
} else {
throw new IllegalArgumentException("The request parameters must be of type List, Map or null");
}
}
/**
* Sets the positional (JSON array) request parameters.
*
* @since 1.30
*
* @param positionalParams The positional (JSON array) request
* parameters, {@code null} if none.
*/
public void setPositionalParams(final List<Object> positionalParams) {
if (positionalParams == null)
return;
this.positionalParams = positionalParams;
}
/**
* Sets the named (JSON object) request parameters.
*
* @since 1.30
*
* @param namedParams The named (JSON object) request parameters,
* {@code null} if none.
*/
public void setNamedParams(final Map<String,Object> namedParams) {
if (namedParams == null)
return;
this.namedParams = namedParams;
}
/**
* Gets the request identifier.
*
* @return The request identifier ({@code Number}, {@code Boolean},
* {@code String}) or {@code null}.
*/
public Object getID() {
return id;
}
/**
* Sets the request identifier (ID).
*
* @param id The request identifier echoed back to the caller.
* The value must <a href="#map">map</a> to a JSON
* scalar ({@code null} and fractions, however, should
* be avoided).
*/
public void setID(final Object id) {
if (id == null ||
id instanceof Boolean ||
id instanceof Number ||
id instanceof String
) {
this.id = id;
} else {
this.id = id.toString();
}
}
@Override
public JSONObject toJSONObject() {
JSONObject req = new JSONObject();
req.put("method", method);
// The params can be omitted if none
switch (getParamsType()) {
case ARRAY:
req.put("params", positionalParams);
break;
case OBJECT:
req.put("params", namedParams);
break;
}
req.put("id", id);
req.put("jsonrpc", "2.0");
Map <String,Object> nonStdAttributes = getNonStdAttributes();
if (nonStdAttributes != null) {
for (final Map.Entry<String,Object> attr: nonStdAttributes.entrySet())
req.put(attr.getKey(), attr.getValue());
}
return req;
}
}

View File

@@ -0,0 +1,414 @@
package com.thetransactioncompany.jsonrpc2;
import java.util.Map;
import org.json.simple.JSONObject;
/**
* Represents a JSON-RPC 2.0 response.
*
* <p>A response is returned to the caller after a JSON-RPC 2.0 request has
* been processed (notifications, however, don't produce a response). The
* response can take two different forms depending on the outcome:
*
* <ul>
* <li>The request was successful. The corresponding response returns
* a JSON object with the following information:
* <ul>
* <li>{@code result} The result, which can be of any JSON type
* - a number, a boolean value, a string, an array, an object
* or null.
* <li>{@code id} The request identifier which is echoed back back
* to the caller.
* <li>{@code jsonrpc} A string indicating the JSON-RPC protocol
* version set to "2.0".
* </ul>
* <li>The request failed. The returned JSON object contains:
* <ul>
* <li>{@code error} An object with:
* <ul>
* <li>{@code code} An integer indicating the error type.
* <li>{@code message} A brief error messsage.
* <li>{@code data} Optional error data.
* </ul>
* <li>{@code id} The request identifier. If it couldn't be
* determined, e.g. due to a request parse error, the ID is
* set to {@code null}.
* <li>{@code jsonrpc} A string indicating the JSON-RPC protocol
* version set to "2.0".
* </ul>
* </ul>
*
* <p>Here is an example JSON-RPC 2.0 response string where the request
* has succeeded:
*
* <pre>
* {
* "result" : true,
* "id" : "req-002",
* "jsonrpc" : "2.0"
* }
* </pre>
*
*
* <p>And here is an example JSON-RPC 2.0 response string indicating a failure:
*
* <pre>
* {
* "error" : { "code" : -32601, "message" : "Method not found" },
* "id" : "req-003",
* "jsonrpc" : "2.0"
* }
* </pre>
*
* <p>A response object is obtained either by passing a valid JSON-RPC 2.0
* response string to the static {@link #parse} method or by invoking the
* appropriate constructor.
*
* <p>Here is how parsing is done:
*
* <pre>
* String jsonString = "{\"result\":true,\"id\":\"req-002\",\"jsonrpc\":\"2.0\"}";
*
* JSONRPC2Response response = null;
*
* try {
* response = JSONRPC2Response.parse(jsonString);
*
* } catch (JSONRPC2Exception e) {
* // handle exception
* }
* </pre>
*
* <p>And here is how you can replicate the above example response strings:
*
* <pre>
* // success example
* JSONRPC2Response resp = new JSONRPC2Response(true, "req-002");
* System.out.println(resp);
*
* // failure example
* JSONRPC2Error err = new JSONRPC2Error(-32601, "Method not found");
* resp = new JSONRPC2Response(err, "req-003");
* System.out.println(resp);
*
* </pre>
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Response extends JSONRPC2Message {
/**
* The result.
*/
private Object result = null;
/**
* The error object.
*/
private JSONRPC2Error error = null;
/**
* The echoed request identifier.
*/
private Object id = null;
/**
* Parses a JSON-RPC 2.0 response string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 response string, UTF-8 encoded.
* Must not be {@code null}.
*
* @return The corresponding JSON-RPC 2.0 response object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Response parse(final String jsonString)
throws JSONRPC2ParseException {
return parse(jsonString, false, false, false);
}
/**
* Parses a JSON-RPC 2.0 response string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 response string, UTF-8 encoded.
* Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in results.
*
* @return The corresponding JSON-RPC 2.0 response object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Response parse(final String jsonString,
final boolean preserveOrder)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, false, false);
}
/**
* Parses a JSON-RPC 2.0 response string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 response string, UTF-8 encoded.
* Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in results.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version attribute in the
* JSON-RPC 2.0 message.
*
* @return The corresponding JSON-RPC 2.0 response object.
*
* @throws JSONRPC2ParseException With detailed message if the parsing
* failed.
*/
public static JSONRPC2Response parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, ignoreVersion, false);
}
/**
* Parses a JSON-RPC 2.0 response string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 response string, UTF-8
* encoded. Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of
* JSON object members in results.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version
* attribute in the JSON-RPC 2.0 message.
* @param parseNonStdAttributes {@code true} to parse non-standard
* attributes found in the JSON-RPC 2.0
* message.
*
* @return The corresponding JSON-RPC 2.0 response object.
*
* @throws JSONRPC2ParseException With detailed message if the parsing
* failed.
*/
public static JSONRPC2Response parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion,
final boolean parseNonStdAttributes)
throws JSONRPC2ParseException {
JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder, ignoreVersion, parseNonStdAttributes);
return parser.parseJSONRPC2Response(jsonString);
}
/**
* Creates a new JSON-RPC 2.0 response to a successful request.
*
* @param result The result. The value can <a href="#map">map</a>
* to any JSON type. May be {@code null}.
* @param id The request identifier echoed back to the caller. May
* be {@code null} though not recommended.
*/
public JSONRPC2Response(final Object result, final Object id) {
setResult(result);
setID(id);
}
/**
* Creates a new JSON-RPC 2.0 response to a successful request which
* result is {@code null}.
*
* @param id The request identifier echoed back to the caller. May be
* {@code null} though not recommended.
*/
public JSONRPC2Response(final Object id) {
setResult(null);
setID(id);
}
/**
* Creates a new JSON-RPC 2.0 response to a failed request.
*
* @param error A JSON-RPC 2.0 error instance indicating the
* cause of the failure. Must not be {@code null}.
* @param id The request identifier echoed back to the caller.
* Pass a {@code null} if the request identifier couldn't
* be determined (e.g. due to a parse error).
*/
public JSONRPC2Response(final JSONRPC2Error error, final Object id) {
setError(error);
setID(id);
}
/**
* Indicates a successful JSON-RPC 2.0 request and sets the result.
* Note that if the response was previously indicating failure this
* will turn it into a response indicating success. Any previously set
* error data will be invalidated.
*
* @param result The result. The value can <a href="#map">map</a> to
* any JSON type. May be {@code null}.
*/
public void setResult(final Object result) {
// result and error are mutually exclusive
this.result = result;
this.error = null;
}
/**
* Gets the result of the request. The returned value has meaning
* only if the request was successful. Use the
* {@link #getError getError} method to check this.
*
* @return The result.
*/
public Object getResult() {
return result;
}
/**
* Indicates a failed JSON-RPC 2.0 request and sets the error details.
* Note that if the response was previously indicating success this
* will turn it into a response indicating failure. Any previously set
* result data will be invalidated.
*
* @param error A JSON-RPC 2.0 error instance indicating the cause of
* the failure. Must not be {@code null}.
*/
public void setError(final JSONRPC2Error error) {
if (error == null)
throw new IllegalArgumentException("The error object cannot be null");
// result and error are mutually exclusive
this.error = error;
this.result = null;
}
/**
* Gets the error object indicating the cause of the request failure.
* If a {@code null} is returned, the request succeeded and there was
* no error.
*
* @return A JSON-RPC 2.0 error object, {@code null} if the response
* indicates success.
*/
public JSONRPC2Error getError() {
return error;
}
/**
* A convinience method to check if the response indicates success or
* failure of the request. Alternatively, you can use the
* {@code #getError} method for this purpose.
*
* @return {@code true} if the request succeeded, {@code false} if
* there was an error.
*/
public boolean indicatesSuccess() {
return error == null;
}
/**
* Sets the request identifier echoed back to the caller.
*
* @param id The value must <a href="#map">map</a> to a JSON scalar.
* Pass a {@code null} if the request identifier couldn't
* be determined (e.g. due to a parse error).
*/
public void setID(final Object id) {
if (id == null ||
id instanceof Boolean ||
id instanceof Number ||
id instanceof String
) {
this.id = id;
} else {
this.id = id.toString();
}
}
/**
* Gets the request identifier that is echoed back to the caller.
*
* @return The request identifier. If there was an error during the
* the request retrieval (e.g. parse error) and the identifier
* couldn't be determined, the value will be {@code null}.
*/
public Object getID() {
return id;
}
@Override
public JSONObject toJSONObject() {
JSONObject out = new JSONObject();
// Result and error are mutually exclusive
if (error != null) {
out.put("error", error.toJSONObject());
}
else {
out.put("result", result);
}
out.put("id", id);
out.put("jsonrpc", "2.0");
Map <String,Object> nonStdAttributes = getNonStdAttributes();
if (nonStdAttributes != null) {
for (final Map.Entry<String,Object> attr: nonStdAttributes.entrySet())
out.put(attr.getKey(), attr.getValue());
}
return out;
}
}

View File

@@ -0,0 +1,32 @@
/**
* Classes to represent, parse and serialise JSON-RPC 2.0 requests,
* notifications and responses.
*
* <p>JSON-RPC is a protocol for
* <a href="http://en.wikipedia.org/wiki/Remote_procedure_call">remote
* procedure calls</a> (RPC) using <a href="http://www.json.org" >JSON</a>
* - encoded requests and responses. It can be easily relayed over HTTP
* and is of JavaScript origin, making it ideal for use in dynamic web
* applications in the spirit of Ajax and Web 2.0.
*
* <p>This package implements <b>version 2.0</b> of the protocol, with the
* exception of <i>batching / multicall</i>. This feature is deliberately left
* out as it tends to confuse users (judging by posts in the JSON-RPC forum).
*
* <p>See the <a href="http://www.jsonrpc.org/specification"></a>JSON-RPC 2.0
* specification</a> for more information or write to the
* <a href="https://groups.google.com/forum/#!forum/json-rpc">user group</a> if
* you have questions.
*
* <p><b>Package dependencies:</b> The classes in this package rely on the
* {@code org.json.simple} and {@code org.json.simple.parser} packages
* (version 1.1.1 and compabile) for JSON encoding and decoding. You can obtain
* them from the <a href="http://code.google.com/p/json-smart/">JSON-Smart</a>
* website.
*
* @author Vladimir Dzhuvinov
*/
package com.thetransactioncompany.jsonrpc2;

View File

@@ -0,0 +1,263 @@
package com.thetransactioncompany.jsonrpc2.server;
import java.util.Hashtable;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Notification;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
/**
* Dispatcher for JSON-RPC 2.0 requests and notifications. This class is
* tread-safe.
*
* <p>Use the {@code register()} methods to add a request or notification
* handler for an RPC method.
*
* <p>Use the {@code process()} methods to have an incoming request or
* notification processed by the matching handler.
*
* <p>The {@code reportProcTime()} method enables reporting of request
* processing time (in microseconds) by appending a non-standard "xProcTime"
* attribute to the resulting JSON-RPC 2.0 response message.
*
* <p>Example:
*
* <pre>
* {
* "result" : "xyz",
* "id" : 1,
* "jsonrpc" : "2.0",
* "xProcTime" : "189 us"
* }
* </pre>
*
* <p>Note: The dispatch(...) methods were deprecated in version 1.7. Use
* process(...) instead.
*
* @author Vladimir Dzhuvinov
*/
public class Dispatcher implements RequestHandler, NotificationHandler {
/**
* Hashtable of request name / handler pairs.
*/
private final Hashtable<String,RequestHandler> requestHandlers;
/**
* Hashtable of notification name / handler pairs.
*/
private final Hashtable<String,NotificationHandler> notificationHandlers;
/**
* Controls reporting of request processing time by appending a
* non-standard "xProcTime" attribute to the JSON-RPC 2.0 response.
*/
private boolean reportProcTime = false;
/**
* Creates a new dispatcher with no registered handlers.
*/
public Dispatcher() {
requestHandlers = new Hashtable<String,RequestHandler>();
notificationHandlers = new Hashtable<String,NotificationHandler>();
}
/**
* Registers a new JSON-RPC 2.0 request handler.
*
* @param handler The request handler to register. Must not be
* {@code null}.
*
* @throws IllegalArgumentException On attempting to register a handler
* that duplicates an existing request
* name.
*/
public void register(final RequestHandler handler) {
for (String name: handler.handledRequests()) {
if (requestHandlers.containsKey(name))
throw new IllegalArgumentException("Cannot register a duplicate JSON-RPC 2.0 handler for request " + name);
requestHandlers.put(name, handler);
}
}
/**
* Registers a new JSON-RPC 2.0 notification handler.
*
* @param handler The notification handler to register. Must not be
* {@code null}.
*
* @throws IllegalArgumentException On attempting to register a handler
* that duplicates an existing
* notification name.
*/
public void register(final NotificationHandler handler) {
for (String name: handler.handledNotifications()) {
if (notificationHandlers.containsKey(name))
throw new IllegalArgumentException("Cannot register a duplicate JSON-RPC 2.0 handler for notification " + name);
notificationHandlers.put(name, handler);
}
}
@Override
public String[] handledRequests() {
java.util.Set<String> var = requestHandlers.keySet();
return var.toArray(new String[var.size()]);
}
@Override
public String[] handledNotifications() {
java.util.Set<String> var = notificationHandlers.keySet();
return var.toArray(new String[var.size()]);
}
/**
* Gets the handler for the specified JSON-RPC 2.0 request name.
*
* @param requestName The request name to lookup.
*
* @return The corresponding request handler or {@code null} if none
* was found.
*/
public RequestHandler getRequestHandler(final String requestName) {
return requestHandlers.get(requestName);
}
/**
* Gets the handler for the specified JSON-RPC 2.0 notification name.
*
* @param notificationName The notification name to lookup.
*
* @return The corresponding notification handler or {@code null} if
* none was found.
*/
public NotificationHandler getNotificationHandler(final String notificationName) {
return notificationHandlers.get(notificationName);
}
/**
* @deprecated
*/
public JSONRPC2Response dispatch(final JSONRPC2Request request, final MessageContext requestCtx) {
return process(request, requestCtx);
}
@Override
public JSONRPC2Response process(final JSONRPC2Request request, final MessageContext requestCtx) {
long startNanosec = 0;
// Measure request processing time?
if (reportProcTime)
startNanosec = System.nanoTime();
final String method = request.getMethod();
RequestHandler handler = getRequestHandler(method);
if (handler == null) {
// We didn't find a handler for the requested RPC
Object id = request.getID();
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, id);
}
// Process the request
JSONRPC2Response response = handler.process(request, requestCtx);
if (reportProcTime) {
final long procTimeNanosec = System.nanoTime() - startNanosec;
response.appendNonStdAttribute("xProcTime", procTimeNanosec / 1000 + " us");
}
return response;
}
/**
* @deprecated
*/
public void dispatch(final JSONRPC2Notification notification, final MessageContext notificationCtx) {
process(notification, notificationCtx);
}
@Override
public void process(final JSONRPC2Notification notification, final MessageContext notificationCtx) {
final String method = notification.getMethod();
NotificationHandler handler = getNotificationHandler(method);
if (handler == null) {
// We didn't find a handler for the requested RPC
return;
}
// Process the notification
handler.process(notification, notificationCtx);
}
/**
* Controls reporting of request processing time by appending a
* non-standard "xProcTime" attribute to the JSON-RPC 2.0 response.
* Reporting is disabled by default.
*
* @param enable {@code true} to enable proccessing time reporting,
* {@code false} to disable it.
*/
public void reportProcTime(final boolean enable) {
reportProcTime = enable;
}
/**
* Returns {@code true} if reporting of request processing time is
* enabled. See the {@link #reportProcTime} description for more
* information.
*
* @return {@code true} if reporting of request processing time is
* enabled, else {@code false}.
*/
public boolean reportsProcTime() {
return reportProcTime;
}
}

View File

@@ -0,0 +1,428 @@
package com.thetransactioncompany.jsonrpc2.server;
import java.net.InetAddress;
import java.net.URLConnection;
import java.security.Principal;
import java.security.cert.X509Certificate;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
/**
* Context information about JSON-RPC 2.0 request and notification messages.
* This class is immutable.
*
* <ul>
* <li>The client's host name.
* <li>The client's IP address.
* <li>Whether the request / notification was transmitted securely (e.g.
* via HTTPS).
* <li>The client principal(s) (user), if authenticated.
* </ul>
*
* @author Vladimir Dzhuvinov
*/
public class MessageContext {
/**
* The client hostname, {@code null} if none was specified.
*/
private String clientHostName = null;
/**
* The client IP address, {@code null} if none was specified.
*/
private String clientInetAddress = null;
/**
* Indicates whether the request was received over a secure channel
* (typically HTTPS).
*/
private boolean secure = false;
/**
* The authenticated client principals, {@code null} if none were
* specified.
*/
private Principal[] principals = null;
/**
* Minimal implementation of the {@link java.security.Principal}
* interface.
*/
public class BasicPrincipal implements Principal {
/**
* The principal name.
*/
private String name;
/**
* Creates a new principal.
*
* @param name The principal name, must not be {@code null} or
* empty string.
*
* @throws IllegalArgumentException On a {@code null} or empty
* principal name.
*/
public BasicPrincipal(final String name) {
if (name == null || name.trim().isEmpty())
throw new IllegalArgumentException("The principal name must be defined");
this.name = name;
}
/**
* Checks for equality.
*
* @param another The object to compare to.
*/
public boolean equals(final Object another) {
return another != null &&
another instanceof Principal &&
((Principal)another).getName().equals(this.getName());
}
/**
* Returns a hash code for this principal.
*
* @return The hash code.
*/
public int hashCode() {
return getName().hashCode();
}
/**
* Returns the principal name.
*
* @return The principal name.
*/
public String getName() {
return name;
}
}
/**
* Creates a new JSON-RPC 2.0 request / notification context.
*
* @param clientHostName The client host name, {@code null} if
* unknown.
* @param clientInetAddress The client IP address, {@code null} if
* unknown.
* @param secure Specifies a request received over HTTPS.
* @param principalName Specifies the authenticated client principle
* name, {@code null} if unknown. The name must
* not be an empty or blank string.
*/
public MessageContext(final String clientHostName,
final String clientInetAddress,
final boolean secure,
final String principalName) {
this.clientHostName = clientHostName;
this.clientInetAddress = clientInetAddress;
this.secure = secure;
if (principalName != null) {
principals = new Principal[1];
principals[0] = new BasicPrincipal(principalName);
}
}
/**
* Creates a new JSON-RPC 2.0 request / notification context.
*
* @param clientHostName The client host name, {@code null} if
* unknown.
* @param clientInetAddress The client IP address, {@code null} if
* unknown.
* @param secure Specifies a request received over HTTPS.
* @param principalNames Specifies the authenticated client principle
* names, {@code null} if unknown. The names
* must not be an empty or blank string.
*/
public MessageContext(final String clientHostName,
final String clientInetAddress,
final boolean secure,
final String[] principalNames) {
this.clientHostName = clientHostName;
this.clientInetAddress = clientInetAddress;
this.secure = secure;
if (principalNames != null) {
principals = new Principal[principalNames.length];
for (int i=0; i < principals.length; i++)
principals[0] = new BasicPrincipal(principalNames[i]);
}
}
/**
* Creates a new JSON-RPC 2.0 request / notification context. No
* authenticated client principal is specified.
*
* @param clientHostName The client host name, {@code null} if
* unknown.
* @param clientInetAddress The client IP address, {@code null} if
* unknown.
* @param secure Specifies a request received over HTTPS.
*/
public MessageContext(final String clientHostName,
final String clientInetAddress,
final boolean secure) {
this.clientHostName = clientHostName;
this.clientInetAddress = clientInetAddress;
this.secure = secure;
}
/**
* Creates a new JSON-RPC 2.0 request / notification context. Indicates
* an insecure transport (plain HTTP) and no authenticated client
* principal.
*
* @param clientHostName The client host name, {@code null} if
* unknown.
* @param clientInetAddress The client IP address, {@code null} if
* unknown.
*/
public MessageContext(final String clientHostName,
final String clientInetAddress) {
this.clientHostName = clientHostName;
this.clientInetAddress = clientInetAddress;
this.secure = false;
}
/**
* Creates a new JSON-RPC 2.0 request / notification context. Indicates
* an insecure transport (plain HTTP) and no authenticated client
* principal. Not client host name / IP is specified.
*/
public MessageContext() {
this.secure = false;
}
/**
* Creates a new JSON-RPC 2.0 request / notification context from the
* specified HTTP request.
*
* @param httpRequest The HTTP request.
*/
public MessageContext(final HttpServletRequest httpRequest) {
clientInetAddress = httpRequest.getRemoteAddr();
clientHostName = httpRequest.getRemoteHost();
if (clientHostName != null && clientHostName.equals(clientInetAddress))
clientHostName = null; // not resolved actually
secure = httpRequest.isSecure();
X509Certificate[] certs = (X509Certificate[])httpRequest.getAttribute("javax.servlet.request.X509Certificate");
if (certs != null && certs.length > 0) {
principals = new Principal[certs.length];
for (int i=0; i < principals.length; i++)
principals[i] = certs[i].getSubjectX500Principal();
}
}
/**
* Creates a new JSON-RPC 2.0 request / notification context from the
* specified URL connection. Use this constructor in cases when the
* HTTP server is the origin of the JSON-RPC 2.0 requests /
* notifications. If the IP address of the HTTP server cannot be
* resolved {@link #getClientInetAddress} will return {@code null}.
*
* @param connection The URL connection, must be established and not
* {@code null}.
*/
public MessageContext(final URLConnection connection) {
clientHostName = connection.getURL().getHost();
InetAddress ip = null;
if (clientHostName != null) {
try {
ip = InetAddress.getByName(clientHostName);
} catch (Exception e) {
// UnknownHostException, SecurityException
// ignore
}
}
if (ip != null)
clientInetAddress = ip.getHostAddress();
if (connection instanceof HttpsURLConnection) {
secure = true;
HttpsURLConnection httpsConnection = (HttpsURLConnection)connection;
Principal prn = null;
try {
prn = httpsConnection.getPeerPrincipal();
} catch (Exception e) {
// SSLPeerUnverifiedException, IllegalStateException
// ignore
}
if (prn != null) {
principals = new Principal[1];
principals[0] = prn;
}
}
}
/**
* Gets the host name of the client that sent the request /
* notification.
*
* @return The client host name, {@code null} if unknown.
*/
public String getClientHostName() {
return clientHostName;
}
/**
* Gets the IP address of the client that sent the request /
* notification.
*
* @return The client IP address, {@code null} if unknown.
*/
public String getClientInetAddress() {
return clientInetAddress;
}
/**
* Indicates whether the request / notification was received over a
* secure HTTPS connection.
*
* @return {@code true} If the request was received over HTTPS,
* {@code false} if it was received over plain HTTP.
*/
public boolean isSecure() {
return secure;
}
/**
* Returns the first authenticated client principal, {@code null} if
* none.
*
* @return The first client principal, {@code null} if none.
*/
public Principal getPrincipal() {
if (principals != null)
return principals[0];
else
return null;
}
/**
* Returns the authenticated client principals, {@code null} if
* none.
*
* @return The client principals, {@code null} if none.
*/
public Principal[] getPrincipals() {
return principals;
}
/**
* Returns the first authenticated client principal name, {@code null}
* if none.
*
* @return The first client principal name, {@code null} if none.
*/
public String getPrincipalName() {
if (principals != null)
return principals[0].getName();
else
return null;
}
/**
* Returns the authenticated client principal names, {@code null}
* if none.
*
* @return The client principal names, {@code null} if none.
*/
public String[] getPrincipalNames() {
String[] names = new String[principals.length];
for (int i=0; i < names.length; i++)
names[i] = principals[i].getName();
return names;
}
@Override
public String toString() {
String s = "[host=" + clientHostName + " hostIP=" + clientInetAddress + " secure=" + secure;
if (principals != null) {
int i = 0;
for (Principal p: principals)
s += " principal[" + (i++) + "]=" + p;
}
return s + "]";
}
}

View File

@@ -0,0 +1,35 @@
package com.thetransactioncompany.jsonrpc2.server;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Notification;
/**
* Interface for handling JSON-RPC 2.0 notifications.
*
* @author Vladimir Dzhuvinov
*/
public interface NotificationHandler {
/**
* Gets the names of the handled JSON-RPC 2.0 notification methods.
*
* @return The names of the handled JSON-RPC 2.0 notification methods.
*/
public String[] handledNotifications();
/**
* Processes a JSON-RPC 2.0 notification.
*
* <p>Note that JSON-RPC 2.0 notifications don't produce a response!
*
* @param notification A valid JSON-RPC 2.0 notification instance.
* Must not be {@code null}.
* @param notificationCtx Context information about the notification
* message, may be {@code null} if undefined.
*/
public void process(final JSONRPC2Notification notification, final MessageContext notificationCtx);
}

View File

@@ -0,0 +1,36 @@
package com.thetransactioncompany.jsonrpc2.server;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
/**
* Interface for handling JSON-RPC 2.0 requests.
*
* @author Vladimir Dzhuvinov
*/
public interface RequestHandler {
/**
* Gets the names of the handled JSON-RPC 2.0 request methods.
*
* @return The names of the handled JSON-RPC 2.0 request methods.
*/
public String[] handledRequests();
/**
* Processes a JSON-RPC 2.0 request.
*
* @param request A valid JSON-RPC 2.0 request instance. Must not be
* {@code null}.
* @param requestCtx Context information about the request message, may
* be {@code null} if undefined.
*
* @return The resulting JSON-RPC 2.0 response. It indicates success
* or an error, such as METHOD_NOT_FOUND.
*/
public JSONRPC2Response process(final JSONRPC2Request request, final MessageContext requestCtx);
}

View File

@@ -0,0 +1,34 @@
/**
* Simple server framework for processing JSON-RPC 2.0 requests and
* notifications.
*
* <p>Usage:
*
* <ol>
* <li>Implement {@link com.thetransactioncompany.jsonrpc2.server.RequestHandler request}
* and / or {@link com.thetransactioncompany.jsonrpc2.server.NotificationHandler notification}
* handlers for the various expected JSON-RPC 2.0 messages. A handler
* may process one or more request/notification methods (identified by
* method name).
* <li>Create a new {@link com.thetransactioncompany.jsonrpc2.server.Dispatcher}
* and register the handlers with it.
* <li>Pass the received JSON-RPC 2.0 requests and notifications to the
* appropriate {@code Dispatcher.dispatch(...)} method, then, if the
* message is a request, pass the resulting JSON-RPC 2.0 response back
* to the client.
* </ol>
*
* <p>Direct package dependencies:
*
* <ul>
* <li><b><a href="http://software.dzhuvinov.com/json-rpc-2.0-base.html">JSON-RPC 2.0 Base</a></b>
* [<i>com.thetransactioncompany.jsonrpc2</i>] to construct and represent
* JSON-RPC 2.0 messages.
* <li><b>Java Servlet API</b> [<i>javax.servlet.http</i>] for constructing
* {@link com.thetransactioncompany.jsonrpc2.server.MessageContext}
* objects from HTTP servlet requests.
* </ul>
*
* @author Vladimir Dzhuvinov
*/
package com.thetransactioncompany.jsonrpc2.server;

View File

@@ -0,0 +1,80 @@
package com.thetransactioncompany.jsonrpc2.util;
/**
* The base abstract class for the JSON-RPC 2.0 parameter retrievers.
*
* @author Vladimir Dzhuvinov
*/
public abstract class ParamsRetriever {
/**
* Returns the parameter count.
*
* @return The parameters count.
*/
public abstract int size();
/**
* Matches a string against an array of acceptable values.
*
* @param input The string to match.
* @param enumStrings The acceptable string values. Must not be
* {@code null}.
* @param ignoreCase {@code true} for a case insensitive match.
*
* @return The matching string value, {@code null} if no match was
* found.
*/
protected static String getEnumStringMatch(final String input,
final String[] enumStrings,
final boolean ignoreCase) {
for (final String en: enumStrings) {
if (ignoreCase) {
if (en.equalsIgnoreCase(input))
return en;
}
else {
if (en.equals(input))
return en;
}
}
return null;
}
/**
* Matches a string against an enumeration of acceptable values.
*
* @param input The string to match.
* @param enumClass The enumeration class specifying the acceptable
* string values. Must not be {@code null}.
* @param ignoreCase {@code true} for a case insensitive match.
*
* @return The matching enumeration constant, {@code null} if no match
* was found.
*/
protected static <T extends Enum<T>> T getEnumStringMatch(final String input,
final Class<T> enumClass,
final boolean ignoreCase) {
for (T en: enumClass.getEnumConstants()) {
if (ignoreCase) {
if (en.toString().equalsIgnoreCase(input))
return en;
}
else {
if (en.toString().equals(input))
return en;
}
}
return null;
}
}

View File

@@ -0,0 +1,44 @@
/**
* Utility classes for typed retrieval of JSON-RPC 2.0 request parameters on the
* server side.
*
* <p>The following parameter type conversion choices are available:
*
* <ul>
* <li>JSON true/false to Java {@code boolean}
* <li>JSON number to Java {@code int}, {@code long}, {@code float} or
* {@code double}
* <li>JSON string to {@code java.lang.String}
* <li>Predefined (enumerated) JSON string to a Java {@code enum} constant
* or {@code java.lang.String}
* <li>JSON array to Java {@code boolean[]}, {@code int[]}, {@code long[]},
* {@code float[]}, {@code double[]} or {@code string[]} array, or
* to mixed type {@code java.util.List}
* <li>JSON object to {@code java.util.Map}
* </ul>
*
* <p>If a parameter cannot be retrieved, either because it's missing or
* is of the wrong type, a standard
* {@link com.thetransactioncompany.jsonrpc2.JSONRPC2Error#INVALID_PARAMS}
* exception is thrown.
*
* <p>There are two concrete classes:
*
* <ul>
* <li>The {@link com.thetransactioncompany.jsonrpc2.util.PositionalParamsRetriever}
* class is for extracting <em>positional parameters</em> (packed in a
* JSON array).
* <li>The {@link com.thetransactioncompany.jsonrpc2.util.NamedParamsRetriever}
* class is for extracting <em>named parameters</em> (packed in a JSON
* object).
* </ul>
*
*
* <p><b>Package dependencies:</b> The classes in this package depend on the
* sister {@link com.thetransactioncompany.jsonrpc2} package.
*
* @author Vladimir Dzhuvinov
*/
package com.thetransactioncompany.jsonrpc2.util;