/*
* Copyright: Almende B.V. (2014), Rotterdam, The Netherlands
* License: The Apache Software License, Version 2.0
*/
package com.almende.eve.protocol.jsonrpc.formats;
import java.io.IOException;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.almende.util.jackson.JOM;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* The Class JSONMessage.
*/
public class JSONMessage implements Serializable {
private static final Logger LOG = Logger.getLogger(JSONMessage.class
.getName());
private static final long serialVersionUID = -3324436908445901707L;
protected static final String JSONRPC = "jsonrpc";
protected static final String ID = "id";
protected static final String METHOD = "method";
protected static final String PARAMS = "params";
protected static final String ERROR = "error";
protected static final String RESULT = "result";
protected static final String URL = "url";
protected static final String CALLBACK = "callback";
protected static final String EXTRA = "extra";
protected static final String VERSION = "2.0";
private JsonNode id = null;
private ObjectNode extra = null;
/**
* Instantiates a new JSON message.
*/
public JSONMessage() {}
/**
* Instantiates a new JSON message.
*
* @param jsonNode
* the json node
*/
public JSONMessage(final JsonNode jsonNode) {
init(jsonNode);
try {
JOM.getInstance().readerForUpdating(this).readValue(jsonNode);
} catch (IOException e) {
LOG.log(Level.WARNING, "Couldn't parse incoming message", e);
throw new JSONRPCException(JSONRPCException.CODE.INVALID_REQUEST,
e.getLocalizedMessage(), e);
}
}
protected void init(final JsonNode jsonNode) {
if (jsonNode == null || jsonNode.isNull()) {
throw new JSONRPCException(JSONRPCException.CODE.INVALID_REQUEST,
"JSON-RPC message is null");
}
if (jsonNode.has(JSONRPC) && jsonNode.get(JSONRPC).isTextual()
&& !jsonNode.get(JSONRPC).asText().equals(VERSION)) {
throw new JSONRPCException(JSONRPCException.CODE.INVALID_REQUEST,
"Value of member 'jsonrpc' is not equal to '2.0'");
}
if (jsonNode.has(EXTRA) && !(jsonNode.get(EXTRA).isObject()||jsonNode.get(EXTRA).isNull())) {
throw new JSONRPCException(JSONRPCException.CODE.INVALID_REQUEST,
"Value of member 'extra' should be an object");
}
}
/**
* Sets the id.
*
* @param id
* the new id
*/
public void setId(final JsonNode id) {
this.id = id;
}
/**
* Gets the id.
*
* @return the id
*/
public JsonNode getId() {
return this.id;
}
/**
* Gets the jsonrpc version.
*
* @return the jsonrpc version
*/
public String getJsonrpc() {
return VERSION;
}
/**
* Checks if is request.
*
* @return true, if is request
*/
@JsonIgnore
public boolean isRequest() {
return false;
}
/**
* Checks if is response.
*
* @return true, if is response
*/
@JsonIgnore
public boolean isResponse() {
return false;
}
/**
* Set extra (non JSON-RPC) data;.
*
* @param extra
* the new extra
*/
public void setExtra(final ObjectNode extra) {
this.extra = extra;
}
/**
* Gets extra (non JSON-RPC) data.
*
* @return the extra
*/
public ObjectNode getExtra() {
return this.extra;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof JSONMessage)) {
return false;
}
return JOM.getInstance().valueToTree(this)
.equals(JOM.getInstance().valueToTree(o));
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return JOM.getInstance().valueToTree(this).hashCode();
}
/**
* Check if given json object contains all fields required for a
* json-rpc request (id, method, params).
*
* @param json
* the json
* @return true, if is request
*/
public static boolean isRequest(final ObjectNode json) {
return json.has(METHOD);
}
/**
* Check if given json object contains all fields required for a
* json-rpc response (id, result or error).
*
* @param json
* the json
* @return true, if is response
*/
public static boolean isResponse(final ObjectNode json) {
return (json.has(RESULT) || json.has(ERROR));
}
/**
* Convert incoming message object to JSONMessage if possible. Returns null
* if the message can't be interpreted as a JSONMessage.
*
* @param msg
* the msg
* @return the JSON message
*/
public static JSONMessage jsonConvert(final Object msg) {
JSONMessage jsonMsg = null;
if (msg == null) {
LOG.warning("Message null!");
return null;
}
try {
if (msg instanceof JSONMessage) {
jsonMsg = (JSONMessage) msg;
} else {
ObjectNode json = null;
if (msg instanceof String) {
final String message = (String) msg;
if (message.startsWith("{")
|| message.trim().startsWith("{")) {
json = (ObjectNode) JOM.getInstance().readTree(message);
}
} else if (msg instanceof ObjectNode
|| (msg instanceof JsonNode && ((JsonNode) msg)
.isObject())) {
json = (ObjectNode) msg;
} else {
LOG.info("Message unknown type:" + msg.getClass());
}
if (json != null) {
if (isResponse(json)) {
final JSONResponse response = new JSONResponse(json);
jsonMsg = response;
} else if (isRequest(json)) {
final JSONRequest request = new JSONRequest(json);
jsonMsg = request;
} else {
final JSONMessage generic = new JSONMessage(json);
jsonMsg = generic;
}
}
}
} catch (final Exception e) {
LOG.log(Level.WARNING,
"Message triggered exception in trying to convert it to a JSONMessage.",
e);
}
return jsonMsg;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
final ObjectMapper mapper = JOM.getInstance();
try {
ObjectNode tree = mapper.valueToTree(this);
if (tree.get(ID) == null || tree.get(ID).isNull()) {
tree.remove(ID);
}
if (tree.get(EXTRA) == null || tree.get(EXTRA).isNull()) {
tree.remove(EXTRA);
}
return tree.toString();
} catch (final Exception e) {
LOG.log(Level.WARNING, "", e);
}
return null;
}
}