Skip to content

Commit

Permalink
Add JSON abstraction for JSON RPC support
Browse files Browse the repository at this point in the history
[#159302201]

Fixes #378
  • Loading branch information
acogoluegnes committed Aug 14, 2018
1 parent e28b34a commit 01f10d6
Show file tree
Hide file tree
Showing 15 changed files with 246 additions and 227 deletions.
5 changes: 5 additions & 0 deletions src/main/java/com/rabbitmq/tools/json/JSONReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
import java.util.List;
import java.util.Map;

/**
* Will be removed in 6.0
*
* @deprecated Use a third-party JSON library, e.g. Jackson or GJSON
*/
public class JSONReader {

private static final Object OBJECT_END = new Object();
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/rabbitmq/tools/json/JSONSerializable.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

/**
* Interface for classes that wish to control their own serialization.
*
* Will be removed in 6.0
*
* @deprecated Use a third-party JSON library, e.g. Jackson or GJSON
*/
public interface JSONSerializable {
/**
Expand Down
79 changes: 38 additions & 41 deletions src/main/java/com/rabbitmq/tools/json/JSONUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
// If you have any questions regarding licensing, please contact us at
// [email protected].


package com.rabbitmq.tools.json;

import org.slf4j.Logger;
Expand All @@ -34,54 +33,52 @@
*/
public class JSONUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(JSONUtil.class);
private static final Logger LOGGER = LoggerFactory.getLogger(JSONUtil.class);

/**
* Uses reflection to fill public fields and Bean properties of
* the target object from the source Map.
*/
public static Object fill(Object target, Map<String, Object> source)
throws IntrospectionException, IllegalAccessException, InvocationTargetException
{
return fill(target, source, true);
throws IntrospectionException, IllegalAccessException, InvocationTargetException {
return fill(target, source, true);
}

/**
* Uses reflection to fill public fields and optionally Bean
* properties of the target object from the source Map.
*/
public static Object fill(Object target, Map<String, Object> source, boolean useProperties)
throws IntrospectionException, IllegalAccessException, InvocationTargetException
{
if (useProperties) {
BeanInfo info = Introspector.getBeanInfo(target.getClass());
throws IntrospectionException, IllegalAccessException, InvocationTargetException {
if (useProperties) {
BeanInfo info = Introspector.getBeanInfo(target.getClass());

PropertyDescriptor[] props = info.getPropertyDescriptors();
for (int i = 0; i < props.length; ++i) {
PropertyDescriptor prop = props[i];
String name = prop.getName();
Method setter = prop.getWriteMethod();
if (setter != null && !Modifier.isStatic(setter.getModifiers())) {
setter.invoke(target, source.get(name));
}
}
}
PropertyDescriptor[] props = info.getPropertyDescriptors();
for (int i = 0; i < props.length; ++i) {
PropertyDescriptor prop = props[i];
String name = prop.getName();
Method setter = prop.getWriteMethod();
if (setter != null && !Modifier.isStatic(setter.getModifiers())) {
setter.invoke(target, source.get(name));
}
}
}

Field[] ff = target.getClass().getDeclaredFields();
for (int i = 0; i < ff.length; ++i) {
Field field = ff[i];
Field[] ff = target.getClass().getDeclaredFields();
for (int i = 0; i < ff.length; ++i) {
Field field = ff[i];
int fieldMod = field.getModifiers();
if (Modifier.isPublic(fieldMod) && !(Modifier.isFinal(fieldMod) ||
Modifier.isStatic(fieldMod)))
{
try {
field.set(target, source.get(field.getName()));
} catch (IllegalArgumentException iae) {
// no special error processing required
if (Modifier.isPublic(fieldMod) && !(Modifier.isFinal(fieldMod) ||
Modifier.isStatic(fieldMod))) {
try {
field.set(target, source.get(field.getName()));
} catch (IllegalArgumentException iae) {
// no special error processing required
}
}
}
}
}

return target;
return target;
}

/**
Expand All @@ -90,14 +87,14 @@ public static Object fill(Object target, Map<String, Object> source, boolean use
* source Map.
*/
public static void tryFill(Object target, Map<String, Object> source) {
try {
fill(target, source);
} catch (IntrospectionException ie) {
LOGGER.error("Error in tryFill", ie);
} catch (IllegalAccessException iae) {
LOGGER.error("Error in tryFill", iae);
} catch (InvocationTargetException ite) {
LOGGER.error("Error in tryFill", ite);
}
try {
fill(target, source);
} catch (IntrospectionException ie) {
LOGGER.error("Error in tryFill", ie);
} catch (IllegalAccessException iae) {
LOGGER.error("Error in tryFill", iae);
} catch (InvocationTargetException ite) {
LOGGER.error("Error in tryFill", ite);
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/rabbitmq/tools/json/JSONWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
import java.util.Map;
import java.util.Set;

/**
* Will be removed in 6.0
* @deprecated Use a third-party JSON library, e.g. Jackson or GJSON
*/
public class JSONWriter {
private boolean indentMode = false;
private int indentLevel = 0;
Expand Down
45 changes: 12 additions & 33 deletions src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,27 @@
import com.rabbitmq.tools.json.JSONReader;
import com.rabbitmq.tools.json.JSONWriter;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
* Simple {@link JsonRpcMapper} based on homegrown JSON utilities.
* Handles integers, doubles, strings, booleans, and arrays of those types.
* <p>
* For a more comprehensive set of features, use {@link JacksonJsonRpcMapper}.
* <p>
* Will be removed in 6.0
*
* @see JsonRpcMapper
* @see JacksonJsonRpcMapper
* @since 5.4.0
* @deprecated use {@link JacksonJsonRpcMapper} instead
*/
public class DefaultJsonRpcMapper implements JsonRpcMapper {

@Override
public JsonRpcRequest parse(String requestBody, ServiceDescription description) {
Map<String, Object> request = (Map<String,Object>) new JSONReader().read(requestBody);

Map<String, Object> request = (Map<String, Object>) new JSONReader().read(requestBody);
return new JsonRpcRequest(
request.get("id"), request.get("version").toString(), request.get("method").toString(),
((List<?>) request.get("params")).toArray()
Expand All @@ -52,40 +60,11 @@ public JsonRpcResponse parse(String responseBody, Class<?> expectedType) {
error
);
}
return new JsonRpcResponse(map, map.get("result"), map.get("error"), exception);
return new JsonRpcResponse(map.get("result"), map.get("error"), exception);
}

@Override
public String write(Object input) {
return new JSONWriter().write(input);
}

/*
@Override
public Object[] parameters(JsonRpcRequest request, Method method) {
Object[] parameters = request.getParameters();
Object[] convertedParameters = new Object[parameters.length];
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameters.length; i++) {
convertedParameters[i] = convert(parameters[i], parameterTypes[i]);
}
return convertedParameters;
}
protected Object convert(Object input, Class<?> expectedClass) {
return input;
// if (input == null || input.getClass().equals(expectedClass)) {
// return input;
// }
// System.err.println(input.getClass() + " " + expectedClass);
// if (Long.class.equals(expectedClass) && input instanceof Integer) {
// return Long.valueOf(((Integer) input).longValue());
// } else if (long.class.equals(expectedClass) && input instanceof Integer) {
// return
// }
// return input;
}
*/
}
112 changes: 67 additions & 45 deletions src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ValueNode;
import com.rabbitmq.tools.json.JSONReader;
import com.rabbitmq.tools.json.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Method;
Expand All @@ -33,10 +33,16 @@
import java.util.Map;

/**
* {@link JsonRpcMapper} based on Jackson.
* Uses the streaming and databind modules.
*
* @see JsonRpcMapper
* @since 5.4.0
*/
public class JacksonJsonRpcMapper implements JsonRpcMapper {

private static final Logger LOGGER = LoggerFactory.getLogger(JacksonJsonRpcMapper.class);

private final ObjectMapper mapper;

public JacksonJsonRpcMapper(ObjectMapper mapper) {
Expand All @@ -62,7 +68,21 @@ public JsonRpcRequest parse(String requestBody, ServiceDescription description)
if ("method".equals(name)) {
method = parser.getValueAsString();
} else if ("id".equals(name)) {
// FIXME parse id, can be any type (handle only primitive and wrapper)
TreeNode node = parser.readValueAsTree();
if (node instanceof ValueNode) {
ValueNode idNode = (ValueNode) node;
if (idNode.isNull()) {
id = null;
} else if (idNode.isTextual()) {
id = idNode.asText();
} else if (idNode.isNumber()) {
id = Long.valueOf(idNode.asLong());
} else {
LOGGER.warn("ID type not null, text, or number {}, ignoring", idNode);
}
} else {
LOGGER.warn("ID not a scalar value {}, ignoring", node);
}
} else if ("version".equals(name)) {
version = parser.getValueAsString();
} else if ("params".equals(name)) {
Expand All @@ -80,6 +100,10 @@ public JsonRpcRequest parse(String requestBody, ServiceDescription description)
throw new JsonRpcMappingException("Error during JSON parsing", e);
}

if (method == null) {
throw new IllegalArgumentException("Could not find method to invoke in request");
}

List<Object> convertedParameters = new ArrayList<>(parameters.size());
if (!parameters.isEmpty()) {
ProcedureDescription proc = description.getProcedure(method, parameters.size());
Expand All @@ -102,69 +126,40 @@ public JsonRpcRequest parse(String requestBody, ServiceDescription description)
);
}

protected Object convert(TreeNode node, Class<?> expectedType) throws IOException {
Object value;
if (expectedType.isPrimitive()) {
ValueNode valueNode = (ValueNode) node;
if (expectedType == Boolean.TYPE) {
value = valueNode.booleanValue();
} else if (expectedType == Character.TYPE) {
value = valueNode.textValue().charAt(0);
} else if (expectedType == Short.TYPE) {
value = valueNode.shortValue();
} else if (expectedType == Integer.TYPE) {
value = valueNode.intValue();
} else if (expectedType == Long.TYPE) {
value = valueNode.longValue();
} else if (expectedType == Float.TYPE) {
value = valueNode.floatValue();
} else if (expectedType == Double.TYPE) {
value = valueNode.doubleValue();
} else {
throw new IllegalArgumentException("Primitive type not supported: " + expectedType);
}
} else {
value = mapper.readValue(node.traverse(), expectedType);
}
return value;
}

@Override
public JsonRpcResponse parse(String responseBody, Class<?> expectedReturnType) {
JsonFactory jsonFactory = new MappingJsonFactory();
Object result = null;
JsonRpcException exception = null;
Map<String, Object> errorMap = null;
try (JsonParser parser = jsonFactory.createParser(responseBody)) {
while (parser.nextToken() != null) {
JsonToken token = parser.currentToken();
if (token == JsonToken.FIELD_NAME) {
String name = parser.currentName();
parser.nextToken();
if ("result".equals(name)) {
parser.nextToken();
if (expectedReturnType == Void.TYPE) {
result = null;
} else {
result = convert(parser.readValueAsTree(), expectedReturnType);
}
} else if ("error".equals(name)) {
errorMap = (Map<String, Object>) convert(parser.readValueAsTree(), Map.class);
exception = new JsonRpcException(
errorMap.toString(),
(String) errorMap.get("name"),
errorMap.get("code") == null ? 0 : (Integer) errorMap.get("code"),
(String) errorMap.get("message"),
errorMap
);
}
}
}
} catch (IOException e) {
throw new JsonRpcMappingException("Error during JSON parsing", e);
}
Map<String, Object> map = (Map<String, Object>) (new JSONReader().read(responseBody));
Map<String, Object> error;
JsonRpcException exception = null;
if (map.containsKey("error")) {
error = (Map<String, Object>) map.get("error");
exception = new JsonRpcException(
new JSONWriter().write(error),
(String) error.get("name"),
error.get("code") == null ? 0 : (Integer) error.get("code"),
(String) error.get("message"),
error
);
}
return new JsonRpcResponse(map, result, map.get("error"), exception);
return new JsonRpcResponse(result, errorMap, exception);
}

@Override
Expand All @@ -175,4 +170,31 @@ public String write(Object input) {
throw new JsonRpcMappingException("Error during JSON serialization", e);
}
}

protected Object convert(TreeNode node, Class<?> expectedType) throws IOException {
Object value;
if (expectedType.isPrimitive()) {
ValueNode valueNode = (ValueNode) node;
if (expectedType == Boolean.TYPE) {
value = valueNode.booleanValue();
} else if (expectedType == Character.TYPE) {
value = valueNode.textValue().charAt(0);
} else if (expectedType == Short.TYPE) {
value = valueNode.shortValue();
} else if (expectedType == Integer.TYPE) {
value = valueNode.intValue();
} else if (expectedType == Long.TYPE) {
value = valueNode.longValue();
} else if (expectedType == Float.TYPE) {
value = valueNode.floatValue();
} else if (expectedType == Double.TYPE) {
value = valueNode.doubleValue();
} else {
throw new IllegalArgumentException("Primitive type not supported: " + expectedType);
}
} else {
value = mapper.readValue(node.traverse(), expectedType);
}
return value;
}
}
Loading

0 comments on commit 01f10d6

Please sign in to comment.