/*
* 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.lang.reflect.Method;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.almende.eve.protocol.jsonrpc.annotation.Name;
import com.almende.eve.protocol.jsonrpc.annotation.Optional;
import com.almende.util.AnnotationUtil;
import com.almende.util.AnnotationUtil.AnnotatedMethod;
import com.almende.util.AnnotationUtil.AnnotatedParam;
import com.almende.util.AnnotationUtil.CachedAnnotation;
import com.almende.util.callback.AsyncCallback;
import com.almende.util.jackson.JOM;
import com.almende.util.uuid.UUID;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* The Class JSONRequest.
*/
public final class JSONRequest extends JSONMessage {
private static final Logger LOG = Logger.getLogger(JSONRequest.class
.getCanonicalName());
private static final long serialVersionUID = 1970046457233622444L;
private static final ObjectReader READER = JOM.getInstance()
.reader(JSONRequest.class);
private static final ObjectNode OBJECT = JOM.getInstance()
.createObjectNode();
transient private AsyncCallback<?> callback = null;
private String method = null;
private ObjectNode params = null;
/**
* Instantiates a new jSON request.
*/
public JSONRequest() {
init(null, null, null, null);
}
/**
* Instantiates a new JSON request.
*
* @param json
* the json
* @throws IOException
* Signals that an I/O exception has occurred.
*/
public JSONRequest(final String json) throws IOException {
init(JOM.getInstance().readTree(json));
}
/**
* Instantiates a new JSON request.
*
* @param request
* the request
*/
public JSONRequest(final JsonNode request) {
init(request);
}
/**
* Instantiates a new JSON request.
*
* @param method
* the method
* @param params
* the params
* @param callback
* the callback
*/
public <T> JSONRequest(final String method, final ObjectNode params,
final AsyncCallback<T> callback) {
init(null, method, params, callback);
}
/**
* Instantiates a new JSON request.
*
* @param method
* the method
* @param params
* the params
*/
public JSONRequest(final String method, final ObjectNode params) {
init(null, method, params, null);
}
/**
* Instantiates a new jSON request.
*
* @param id
* the id
* @param method
* the method
* @param params
* the params
* @param callback
* the callback
*/
public <T> JSONRequest(final JsonNode id, final String method,
final ObjectNode params, final AsyncCallback<T> callback) {
init(id, method, params, callback);
}
/**
* Create a JSONRequest from a java method and arguments.
*
* @param method
* the method
* @param args
* the args
* @param callback
* the callback
*/
public <T> JSONRequest(final Method method, final Object[] args,
final AsyncCallback<T> callback) {
AnnotatedMethod annotatedMethod = null;
try {
annotatedMethod = new AnnotationUtil.AnnotatedMethod(method);
} catch (final Exception e) {
LOG.log(Level.WARNING, "Method can't be used as annotated method",
e);
throw new IllegalArgumentException("Method '" + method.getName()
+ "' can't be used as annotated method.", e);
}
final List<AnnotatedParam> annotatedParams = annotatedMethod
.getParams();
final ObjectNode params = JOM.createObjectNode();
for (int i = 0; i < annotatedParams.size(); i++) {
final AnnotatedParam annotatedParam = annotatedParams.get(i);
if (i < args.length && args[i] != null) {
final CachedAnnotation nameAnnotation = annotatedParam
.getAnnotation(Name.class);
if (nameAnnotation != null && nameAnnotation.value() != null) {
final String name = (String) nameAnnotation.value();
final JsonNode paramValue = JOM.getInstance().valueToTree(
args[i]);
params.set(name, paramValue);
} else {
throw new IllegalArgumentException("Parameter " + i
+ " in method '" + method.getName()
+ "' is missing the @Name annotation.");
}
} else if (isRequired(annotatedParam)) {
throw new IllegalArgumentException("Required parameter " + i
+ " in method '" + method.getName() + "' is null.");
}
}
if (callback != null) {
final JsonNode id = OBJECT.textNode(new UUID().toString());
init(id, method.getName(), params, callback);
} else {
init(null, method.getName(), params, null);
}
}
/**
* Test if a parameter is required Reads the parameter annotation @Required.
* Returns True if the annotation is not provided.
*
* @param param
* the param
* @return required
*/
@SuppressWarnings("deprecation")
static boolean isRequired(final AnnotatedParam param) {
boolean required = true;
final CachedAnnotation requiredAnnotation = param
.getAnnotation(com.almende.eve.protocol.jsonrpc.annotation.Required.class);
if (requiredAnnotation != null) {
required = (boolean) requiredAnnotation.value();
}
if (param.getAnnotation(Optional.class) != null) {
required = false;
}
return required;
}
/**
* Inits the.
*
* @param request
* the request
*/
public void init(final JsonNode request) {
super.init(request);
try {
READER.withValueToUpdate(this).readValue(request);
} catch (IOException e) {
LOG.log(Level.WARNING, "Couldn't parse incoming request", e);
throw new JSONRPCException(JSONRPCException.CODE.INVALID_REQUEST,
e.getLocalizedMessage(), e);
}
if (getMethod() == null) {
throw new JSONRPCException(JSONRPCException.CODE.INVALID_REQUEST,
"Member 'method' missing in request");
}
}
/**
* Inits the.
*
* @param id
* the id
* @param method
* the method
* @param params
* the params
*/
private <T> void init(final JsonNode id, final String method,
final ObjectNode params, final AsyncCallback<T> callback) {
if (callback != null && (id == null || id.isNull())) {
setId(OBJECT.textNode(new UUID().toString()));
} else {
setId(id);
}
setMethod(method);
setParams(params);
setCallback(callback);
}
/**
* Sets the method.
*
* @param method
* the new method
*/
public void setMethod(final String method) {
this.method = method;
}
/**
* Gets the method.
*
* @return the method
*/
public String getMethod() {
return this.method;
}
/**
* Sets the params.
*
* @param params
* the new params
*/
public void setParams(final ObjectNode params) {
this.params = params;
}
/**
* Gets the params.
*
* @return the params
*/
public ObjectNode getParams() {
if (this.params == null) {
return JOM.createObjectNode();
}
return this.params;
}
/**
* Put param.
*
* @param name
* the name
* @param value
* the value
*/
public void putParam(final String name, final Object value) {
this.params.set(name, OBJECT.pojoNode(value));
}
/**
* Gets the param.
*
* @param name
* the name
* @return the param
*/
public Object getParam(final String name) {
if (params.has(name)) {
return JOM.getInstance().convertValue(params.get(name),
Object.class);
}
return null;
}
/**
* Checks for param.
*
* @param name
* the name
* @return the object
*/
public Object hasParam(final String name) {
return this.params.has(name);
}
/**
* Gets the callback.
*
* @return the callback
*/
@JsonIgnore
public AsyncCallback<?> getCallback() {
return callback;
}
/**
* Sets the callback.
*
* @param callback
* the new callback
*/
@JsonIgnore
public <T> void setCallback(AsyncCallback<T> callback) {
this.callback = callback;
}
@Override
@JsonIgnore
public boolean isRequest() {
return true;
}
/*
* (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;
}
}