diff --git a/src/main/java/com/rabbitmq/tools/json/JSONUtil.java b/src/main/java/com/rabbitmq/tools/json/JSONUtil.java index 6050482f5e..c40727d6e8 100644 --- a/src/main/java/com/rabbitmq/tools/json/JSONUtil.java +++ b/src/main/java/com/rabbitmq/tools/json/JSONUtil.java @@ -61,7 +61,6 @@ public static Object fill(Object target, Map source, boolean use String name = prop.getName(); Method setter = prop.getWriteMethod(); if (setter != null && !Modifier.isStatic(setter.getModifiers())) { - //System.out.println(target + " " + name + " <- " + source.get(name)); setter.invoke(target, source.get(name)); } } @@ -74,7 +73,6 @@ public static Object fill(Object target, Map source, boolean use if (Modifier.isPublic(fieldMod) && !(Modifier.isFinal(fieldMod) || Modifier.isStatic(fieldMod))) { - //System.out.println(target + " " + field.getName() + " := " + source.get(field.getName())); try { field.set(target, source.get(field.getName())); } catch (IllegalArgumentException iae) { diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java new file mode 100644 index 0000000000..358284d650 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java @@ -0,0 +1,72 @@ +package com.rabbitmq.tools.jsonrpc; + +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; + +/** + * + */ +public class DefaultJsonRpcMapper implements JsonRpcMapper { + + @Override + public JsonRpcRequest parse(String requestBody, ServiceDescription description) { + Map request = (Map) new JSONReader().read(requestBody); + + return new JsonRpcRequest( + request.get("id"), request.get("version").toString(), request.get("method").toString(), + ((List) request.get("params")).toArray() + ); + } + + @Override + public JsonRpcResponse parse(String responseBody) { + Map map = (Map) (new JSONReader().read(responseBody)); + Map error; + JsonRpcException exception = null; + if (map.containsKey("error")) { + error = (Map) 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, map.get("result"), map.get("error"), exception); + } + + @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; + } + + @Override + public String write(Object input) { + return new JSONWriter().write(input); + } + + 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; + } +} diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java index 31822da6d0..581e304f83 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java @@ -62,6 +62,8 @@ public class JsonRpcClient extends RpcClient implements InvocationHandler { /** Holds the JSON-RPC service description for this client. */ private ServiceDescription serviceDescription; + private final JsonRpcMapper mapper; + /** * Construct a new JsonRpcClient, passing the parameters through * to RpcClient's constructor. The service description record is @@ -72,6 +74,7 @@ public JsonRpcClient(Channel channel, String exchange, String routingKey, int ti throws IOException, JsonRpcException, TimeoutException { super(channel, exchange, routingKey, timeout); + this.mapper = new DefaultJsonRpcMapper(); retrieveServiceDescription(); } @@ -86,18 +89,14 @@ public JsonRpcClient(Channel channel, String exchange, String routingKey) * @return the result contained within the reply, if no exception is found * Throws JsonRpcException if the reply object contained an exception */ - public static Object checkReply(Map reply) + private Object checkReply(JsonRpcMapper.JsonRpcResponse reply) throws JsonRpcException { - if (reply.containsKey("error")) { - @SuppressWarnings("unchecked") - Map map = (Map) reply.get("error"); - // actually a Map - throw new JsonRpcException(map); - } + if (reply.getError() != null) { + throw reply.getException(); + } - Object result = reply.get("result"); - return result; + return reply.getResult(); } /** @@ -114,16 +113,16 @@ public Object call(String method, Object[] params) throws IOException, JsonRpcEx request.put("method", method); request.put("version", ServiceDescription.JSON_RPC_VERSION); request.put("params", (params == null) ? new Object[0] : params); - String requestStr = new JSONWriter().write(request); + String requestStr = mapper.write(request); try { String replyStr = this.stringCall(requestStr); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Reply string: {}", replyStr); } - @SuppressWarnings("unchecked") - Map map = (Map) (new JSONReader().read(replyStr)); - return checkReply(map); + JsonRpcMapper.JsonRpcResponse reply = mapper.parse(replyStr); + + return checkReply(reply); } catch(ShutdownSignalException ex) { throw new IOException(ex.getMessage()); // wrap, re-throw } diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcException.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcException.java index cbefc4fb21..fafb9457d0 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcException.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcException.java @@ -13,40 +13,43 @@ // If you have any questions regarding licensing, please contact us at // info@rabbitmq.com. - package com.rabbitmq.tools.jsonrpc; -import java.util.Map; - -import com.rabbitmq.tools.json.JSONWriter; - /** * Thrown when a JSON-RPC service indicates an error occurred during a call. */ public class JsonRpcException extends Exception { + /** * Default serialized version ID */ private static final long serialVersionUID = 1L; - /** Usually the constant string, "JSONRPCError" */ + /** + * Usually the constant string, "JSONRPCError" + */ public String name; - /** Error code */ + /** + * Error code + */ public int code; - /** Error message */ + /** + * Error message + */ public String message; - /** Error detail object - may not always be present or meaningful */ + /** + * Error detail object - may not always be present or meaningful + */ public Object error; public JsonRpcException() { // no work needed in default no-arg constructor } - public JsonRpcException(Map errorMap) { - super(new JSONWriter().write(errorMap)); - name = (String) errorMap.get("name"); - code = 0; - if (errorMap.get("code") != null) { code = ((Integer) errorMap.get("code")); } - message = (String) errorMap.get("message"); - error = errorMap.get("error"); + public JsonRpcException(String detailMessage, String name, int code, String message, Object error) { + super(detailMessage); + this.name = name; + this.code = code; + this.message = message; + this.error = error; } } diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java new file mode 100644 index 0000000000..d1e32b0492 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java @@ -0,0 +1,89 @@ +package com.rabbitmq.tools.jsonrpc; + +import java.lang.reflect.Method; + +/** + * + */ +public interface JsonRpcMapper { + + JsonRpcRequest parse(String requestBody, ServiceDescription description); + + JsonRpcResponse parse(String responseBody); + + Object[] parameters(JsonRpcRequest request, Method method); + + String write(Object input); + + class JsonRpcRequest { + + private final Object id; + private final String version; + private final String method; + private final Object[] parameters; + + public JsonRpcRequest(Object id, String version, String method, Object[] parameters) { + this.id = id; + this.version = version; + this.method = method; + this.parameters = parameters; + } + + public Object getId() { + return id; + } + + public String getVersion() { + return version; + } + + public String getMethod() { + return method; + } + + public Object[] getParameters() { + return parameters; + } + + public boolean isSystem() { + return method.startsWith("system."); + } + + public boolean isSystemDescribe() { + return "system.describe".equals(method); + } + + } + + class JsonRpcResponse { + + private final Object reply; + private final Object result; + private final Object error; + private final JsonRpcException exception; + + public JsonRpcResponse(Object reply, Object result, Object error, JsonRpcException exception) { + this.reply = reply; + this.result = result; + this.error = error; + this.exception = exception; + } + + public Object getReply() { + return reply; + } + + public Object getError() { + return error; + } + + public Object getResult() { + return result; + } + + public JsonRpcException getException() { + return exception; + } + } + +} diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java index 138f654b12..068fae1546 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java @@ -55,6 +55,8 @@ public class JsonRpcServer extends StringRpcServer { /** The instance backing this server. */ public Object interfaceInstance; + private final JsonRpcMapper mapper; + /** * Construct a server that talks to the outside world using the * given channel, and constructs a fresh temporary @@ -70,6 +72,7 @@ public JsonRpcServer(Channel channel, throws IOException { super(channel); + this.mapper = new DefaultJsonRpcMapper(); init(interfaceClass, interfaceInstance); } @@ -98,6 +101,7 @@ public JsonRpcServer(Channel channel, throws IOException { super(channel, queueName); + this.mapper = new DefaultJsonRpcMapper(); init(interfaceClass, interfaceInstance); } @@ -126,25 +130,24 @@ public String doCall(String requestBody) LOGGER.debug("Request: {}", requestBody); } try { - @SuppressWarnings("unchecked") - Map request = (Map) new JSONReader().read(requestBody); + JsonRpcMapper.JsonRpcRequest request = mapper.parse(requestBody, serviceDescription); if (request == null) { response = errorResponse(null, 400, "Bad Request", null); - } else if (!ServiceDescription.JSON_RPC_VERSION.equals(request.get("version"))) { + } else if (!ServiceDescription.JSON_RPC_VERSION.equals(request.getVersion())) { response = errorResponse(null, 505, "JSONRPC version not supported", null); } else { - id = request.get("id"); - method = (String) request.get("method"); - List parmList = (List) request.get("params"); - params = parmList.toArray(); - if (method.equals("system.describe")) { + id = request.getId(); + method = request.getMethod(); + params = request.getParameters(); + if (request.isSystemDescribe()) { response = resultResponse(id, serviceDescription); - } else if (method.startsWith("system.")) { + } else if (request.isSystem()) { response = errorResponse(id, 403, "System methods forbidden", null); } else { Object result; try { Method matchingMethod = matchingMethod(method, params); + params = mapper.parameters(request, matchingMethod); if (LOGGER.isDebugEnabled()) { Collection parametersValuesAndTypes = new ArrayList(); if (params != null) { @@ -197,7 +200,7 @@ public Method matchingMethod(String methodName, Object[] params) * ID given, using the code, message, and possible * (JSON-encodable) argument passed in. */ - public static String errorResponse(Object id, int code, String message, Object errorArg) { + private String errorResponse(Object id, int code, String message, Object errorArg) { Map err = new HashMap(); err.put("name", "JSONRPCError"); err.put("code", code); @@ -210,22 +213,21 @@ public static String errorResponse(Object id, int code, String message, Object e * Construct and encode a JSON-RPC success response for the * request ID given, using the result value passed in. */ - public static String resultResponse(Object id, Object result) { + private String resultResponse(Object id, Object result) { return response(id, "result", result); } /** * Private API - used by errorResponse and resultResponse. */ - public static String response(Object id, String label, Object value) { + private String response(Object id, String label, Object value) { Map resp = new HashMap(); resp.put("version", ServiceDescription.JSON_RPC_VERSION); if (id != null) { resp.put("id", id); } resp.put(label, value); - String respStr = new JSONWriter().write(resp); - //System.err.println(respStr); + String respStr = mapper.write(resp); return respStr; } diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/ServiceDescription.java b/src/main/java/com/rabbitmq/tools/jsonrpc/ServiceDescription.java index 79d750278b..4cb6d3bf66 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/ServiceDescription.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/ServiceDescription.java @@ -48,7 +48,7 @@ public ServiceDescription(Map rawServiceDescription) { } public ServiceDescription(Class klass) { - this.procedures = new HashMap(); + this.procedures = new HashMap<>(); for (Method m: klass.getMethods()) { ProcedureDescription proc = new ProcedureDescription(m); addProcedure(proc); @@ -66,7 +66,7 @@ public Collection getProcs() { /** Private API - used via reflection during parsing/loading */ public void setProcs(Collection> p) { - procedures = new HashMap(); + procedures = new HashMap<>(); for (Map pm: p) { ProcedureDescription proc = new ProcedureDescription(pm); addProcedure(proc); diff --git a/src/test/java/com/rabbitmq/client/JsonRpcTest.java b/src/test/java/com/rabbitmq/client/JsonRpcTest.java index b6f73c824e..f13b5b4036 100644 --- a/src/test/java/com/rabbitmq/client/JsonRpcTest.java +++ b/src/test/java/com/rabbitmq/client/JsonRpcTest.java @@ -44,15 +44,11 @@ public void init() throws Exception { serverChannel = serverConnection.createChannel(); serverChannel.queueDeclare(queue, false, false, false, null); server = new JsonRpcServer(serverChannel, queue, RpcService.class, new DefaultRpcservice()); - new Thread(new Runnable() { - - @Override - public void run() { - try { - server.mainloop(); - } catch (Exception e) { - // safe to ignore when loops ends/server is canceled - } + new Thread(() -> { + try { + server.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled } }).start(); client = new JsonRpcClient(clientChannel, "", queue, 1000); @@ -103,6 +99,23 @@ public void rpc() { } catch (UndeclaredThrowableException e) { // OK } + + try { + assertEquals("123", service.procedureIntegerToPojo(123).getStringProperty()); + fail("Complex return type not supported"); + } catch (ClassCastException e) { + // OK + } + + try { + Pojo pojo = new Pojo(); + pojo.setStringProperty("hello"); + assertEquals("hello", service.procedurePojoToString(pojo)); + fail("Complex type argument not supported"); + } catch (UndeclaredThrowableException e) { + // OK + } + } public interface RpcService { @@ -124,9 +137,14 @@ public interface RpcService { Long procedureLong(Long input); long procedurePrimitiveLong(long input); + + Pojo procedureIntegerToPojo(Integer id); + + String procedurePojoToString(Pojo pojo); + } - public class DefaultRpcservice implements RpcService { + public static class DefaultRpcservice implements RpcService { @Override public String procedureString(String input) { @@ -172,5 +190,30 @@ public Integer procedureLongToInteger(Long input) { public int procedurePrimitiveLongToInteger(long input) { return (int) input + 1; } + + @Override + public Pojo procedureIntegerToPojo(Integer id) { + Pojo pojo = new Pojo(); + pojo.setStringProperty(id.toString()); + return pojo; + } + + @Override + public String procedurePojoToString(Pojo pojo) { + return pojo.getStringProperty(); + } + } + + public static class Pojo { + + private String stringProperty; + + public String getStringProperty() { + return stringProperty; + } + + public void setStringProperty(String stringProperty) { + this.stringProperty = stringProperty; + } } }