From 0a3bed1ae57c308f806c476f1c89f951a41888ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= Date: Mon, 13 Aug 2018 11:28:08 +0200 Subject: [PATCH 1/4] Introduce JsonRpcMapper abstract WIP References #378 --- .../com/rabbitmq/tools/json/JSONUtil.java | 2 - .../tools/jsonrpc/DefaultJsonRpcMapper.java | 72 +++++++++++++++ .../rabbitmq/tools/jsonrpc/JsonRpcClient.java | 25 +++--- .../tools/jsonrpc/JsonRpcException.java | 35 ++++---- .../rabbitmq/tools/jsonrpc/JsonRpcMapper.java | 89 +++++++++++++++++++ .../rabbitmq/tools/jsonrpc/JsonRpcServer.java | 30 ++++--- .../tools/jsonrpc/ServiceDescription.java | 4 +- .../java/com/rabbitmq/client/JsonRpcTest.java | 63 ++++++++++--- 8 files changed, 263 insertions(+), 57 deletions(-) create mode 100644 src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java create mode 100644 src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java 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; + } } } From e28b34ad8094b47a7fa46112490d1308bf8418b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= Date: Mon, 13 Aug 2018 18:10:02 +0200 Subject: [PATCH 2/4] Adding Jackson support in JSON RPC WIP References #378 --- pom.xml | 7 + .../tools/jsonrpc/DefaultJsonRpcMapper.java | 29 ++- .../tools/jsonrpc/JacksonJsonRpcMapper.java | 178 ++++++++++++++ .../rabbitmq/tools/jsonrpc/JsonRpcClient.java | 221 +++++++++--------- .../rabbitmq/tools/jsonrpc/JsonRpcMapper.java | 23 +- .../jsonrpc/JsonRpcMappingException.java | 26 +++ .../rabbitmq/tools/jsonrpc/JsonRpcServer.java | 30 ++- .../tools/jsonrpc/ProcedureDescription.java | 44 ++++ .../rabbitmq/client/AbstractJsonRpcTest.java | 202 ++++++++++++++++ .../com/rabbitmq/client/JacksonRpcTest.java | 64 +++++ .../java/com/rabbitmq/client/JsonRpcTest.java | 167 ++----------- .../com/rabbitmq/client/test/ClientTests.java | 2 + 12 files changed, 724 insertions(+), 269 deletions(-) create mode 100644 src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java create mode 100644 src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java create mode 100644 src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java create mode 100644 src/test/java/com/rabbitmq/client/JacksonRpcTest.java diff --git a/pom.xml b/pom.xml index d437c401bd..a87e3edddc 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,7 @@ 1.7.25 3.2.6 1.0.2 + 2.9.6 1.2.3 1.1 4.12 @@ -640,6 +641,12 @@ ${micrometer.version} true + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + true + commons-cli commons-cli diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java index 358284d650..c522d24767 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java @@ -1,3 +1,18 @@ +// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.tools.jsonrpc; import com.rabbitmq.tools.json.JSONReader; @@ -23,7 +38,7 @@ public JsonRpcRequest parse(String requestBody, ServiceDescription description) } @Override - public JsonRpcResponse parse(String responseBody) { + public JsonRpcResponse parse(String responseBody, Class expectedType) { Map map = (Map) (new JSONReader().read(responseBody)); Map error; JsonRpcException exception = null; @@ -40,6 +55,12 @@ public JsonRpcResponse parse(String responseBody) { return new JsonRpcResponse(map, 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(); @@ -51,10 +72,7 @@ public Object[] parameters(JsonRpcRequest request, Method method) { return convertedParameters; } - @Override - public String write(Object input) { - return new JSONWriter().write(input); - } + protected Object convert(Object input, Class expectedClass) { return input; @@ -69,4 +87,5 @@ protected Object convert(Object input, Class expectedClass) { // } // return input; } + */ } diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java new file mode 100644 index 0000000000..0a19f53921 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java @@ -0,0 +1,178 @@ +// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.tools.jsonrpc; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.TreeNode; +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 java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * + */ +public class JacksonJsonRpcMapper implements JsonRpcMapper { + + private final ObjectMapper mapper; + + public JacksonJsonRpcMapper(ObjectMapper mapper) { + this.mapper = mapper; + } + + public JacksonJsonRpcMapper() { + this(new ObjectMapper()); + } + + @Override + public JsonRpcRequest parse(String requestBody, ServiceDescription description) { + JsonFactory jsonFactory = new MappingJsonFactory(); + String method = null, version = null; + final List parameters = new ArrayList<>(); + Object id = null; + try (JsonParser parser = jsonFactory.createParser(requestBody)) { + while (parser.nextToken() != null) { + JsonToken token = parser.currentToken(); + if (token == JsonToken.FIELD_NAME) { + String name = parser.currentName(); + token = parser.nextToken(); + if ("method".equals(name)) { + method = parser.getValueAsString(); + } else if ("id".equals(name)) { + // FIXME parse id, can be any type (handle only primitive and wrapper) + } else if ("version".equals(name)) { + version = parser.getValueAsString(); + } else if ("params".equals(name)) { + if (token == JsonToken.START_ARRAY) { + while (parser.nextToken() != JsonToken.END_ARRAY) { + parameters.add(parser.readValueAsTree()); + } + } else { + throw new IllegalStateException("Field params must be an array"); + } + } + } + } + } catch (IOException e) { + throw new JsonRpcMappingException("Error during JSON parsing", e); + } + + List convertedParameters = new ArrayList<>(parameters.size()); + if (!parameters.isEmpty()) { + ProcedureDescription proc = description.getProcedure(method, parameters.size()); + Method internalMethod = proc.internal_getMethod(); + for (int i = 0; i < internalMethod.getParameterCount(); i++) { + TreeNode parameterNode = parameters.get(i); + try { + Class parameterType = internalMethod.getParameterTypes()[i]; + Object value = convert(parameterNode, parameterType); + convertedParameters.add(value); + } catch (IOException e) { + throw new JsonRpcMappingException("Error during parameter conversion", e); + } + } + } + + return new JsonRpcRequest( + id, version, method, + convertedParameters.toArray() + ); + } + + 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; + 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)) { + if (expectedReturnType == Void.TYPE) { + result = null; + } else { + result = convert(parser.readValueAsTree(), expectedReturnType); + } + } + } + } + } catch (IOException e) { + throw new JsonRpcMappingException("Error during JSON parsing", e); + } + 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, result, map.get("error"), exception); + } + + @Override + public String write(Object input) { + try { + return mapper.writeValueAsString(input); + } catch (JsonProcessingException e) { + throw new JsonRpcMappingException("Error during JSON serialization", e); + } + } +} diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java index 581e304f83..12032941f3 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java @@ -15,6 +15,13 @@ package com.rabbitmq.tools.jsonrpc; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.RpcClient; +import com.rabbitmq.client.ShutdownSignalException; +import com.rabbitmq.tools.json.JSONReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @@ -23,78 +30,106 @@ import java.util.Map; import java.util.concurrent.TimeoutException; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.RpcClient; -import com.rabbitmq.client.ShutdownSignalException; -import com.rabbitmq.tools.json.JSONReader; -import com.rabbitmq.tools.json.JSONWriter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** - JSON-RPC is a lightweight - RPC mechanism using JSON - as a data language for request and reply messages. It is - rapidly becoming a standard in web development, where it is - used to make RPC requests over HTTP. RabbitMQ provides an - AMQP transport binding for JSON-RPC in the form of the - JsonRpcClient class. - - JSON-RPC services are self-describing - each service is able - to list its supported procedures, and each procedure - describes its parameters and types. An instance of - JsonRpcClient retrieves its service description using the - standard system.describe procedure when it is - constructed, and uses the information to coerce parameter - types appropriately. A JSON service description is parsed - into instances of ServiceDescription. Client - code can access the service description by reading the - serviceDescription field of - JsonRpcClient instances. - - @see #call(String, Object[]) - @see #call(String[]) + * JSON-RPC is a lightweight + * RPC mechanism using JSON + * as a data language for request and reply messages. It is + * rapidly becoming a standard in web development, where it is + * used to make RPC requests over HTTP. RabbitMQ provides an + * AMQP transport binding for JSON-RPC in the form of the + * JsonRpcClient class. + *

+ * JSON-RPC services are self-describing - each service is able + * to list its supported procedures, and each procedure + * describes its parameters and types. An instance of + * JsonRpcClient retrieves its service description using the + * standard system.describe procedure when it is + * constructed, and uses the information to coerce parameter + * types appropriately. A JSON service description is parsed + * into instances of ServiceDescription. Client + * code can access the service description by reading the + * serviceDescription field of + * JsonRpcClient instances. + * + * @see #call(String, Object[]) + * @see #call(String[]) */ public class JsonRpcClient extends RpcClient implements InvocationHandler { private static final Logger LOGGER = LoggerFactory.getLogger(JsonRpcClient.class); - - /** Holds the JSON-RPC service description for this client. */ + private final JsonRpcMapper mapper; + /** + * 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 + * retrieved from the server during construction. + * + * @throws TimeoutException if a response is not received within the timeout specified, if any + */ + public JsonRpcClient(Channel channel, String exchange, String routingKey, int timeout, JsonRpcMapper mapper) + throws IOException, JsonRpcException, TimeoutException { + super(channel, exchange, routingKey, timeout); + this.mapper = mapper; + retrieveServiceDescription(); + } /** * Construct a new JsonRpcClient, passing the parameters through * to RpcClient's constructor. The service description record is * retrieved from the server during construction. + * * @throws TimeoutException if a response is not received within the timeout specified, if any */ public JsonRpcClient(Channel channel, String exchange, String routingKey, int timeout) - throws IOException, JsonRpcException, TimeoutException - { - super(channel, exchange, routingKey, timeout); - this.mapper = new DefaultJsonRpcMapper(); - retrieveServiceDescription(); + throws IOException, JsonRpcException, TimeoutException { + this(channel, exchange, routingKey, timeout, new DefaultJsonRpcMapper()); } public JsonRpcClient(Channel channel, String exchange, String routingKey) - throws IOException, JsonRpcException, TimeoutException - { + throws IOException, JsonRpcException, TimeoutException { this(channel, exchange, routingKey, RpcClient.NO_TIMEOUT); } + /** + * Private API - used by {@link #call(String[])} to ad-hoc convert + * strings into the required data types for a call. + */ + public static Object coerce(String val, String type) + throws NumberFormatException { + if ("bit".equals(type)) { + return Boolean.getBoolean(val) ? Boolean.TRUE : Boolean.FALSE; + } else if ("num".equals(type)) { + try { + return Integer.valueOf(val); + } catch (NumberFormatException nfe) { + return Double.valueOf(val); + } + } else if ("str".equals(type)) { + return val; + } else if ("arr".equals(type) || "obj".equals(type) || "any".equals(type)) { + return new JSONReader().read(val); + } else if ("nil".equals(type)) { + return null; + } else { + throw new IllegalArgumentException("Bad type: " + type); + } + } + /** * Private API - parses a JSON-RPC reply object, checking it for exceptions. + * * @return the result contained within the reply, if no exception is found * Throws JsonRpcException if the reply object contained an exception */ private Object checkReply(JsonRpcMapper.JsonRpcResponse reply) - throws JsonRpcException - { - if (reply.getError() != null) { - throw reply.getException(); - } + throws JsonRpcException { + if (reply.getError() != null) { + throw reply.getException(); + } return reply.getResult(); } @@ -102,31 +137,37 @@ private Object checkReply(JsonRpcMapper.JsonRpcResponse reply) /** * Public API - builds, encodes and sends a JSON-RPC request, and * waits for the response. + * * @return the result contained within the reply, if no exception is found * @throws JsonRpcException if the reply object contained an exception * @throws TimeoutException if a response is not received within the timeout specified, if any */ - public Object call(String method, Object[] params) throws IOException, JsonRpcException, TimeoutException - { - HashMap request = new HashMap(); + public Object call(String method, Object[] params) throws IOException, JsonRpcException, TimeoutException { + Map request = new HashMap(); request.put("id", null); request.put("method", method); request.put("version", ServiceDescription.JSON_RPC_VERSION); - request.put("params", (params == null) ? new Object[0] : params); + params = (params == null) ? new Object[0] : params; + request.put("params", params); String requestStr = mapper.write(request); try { String replyStr = this.stringCall(requestStr); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Reply string: {}", replyStr); } - - JsonRpcMapper.JsonRpcResponse reply = mapper.parse(replyStr); + Class expectedType; + if ("system.describe".equals(method) && params.length == 0) { + expectedType = Map.class; + } else { + ProcedureDescription proc = serviceDescription.getProcedure(method, params.length); + expectedType = proc.getReturnType(); + } + JsonRpcMapper.JsonRpcResponse reply = mapper.parse(replyStr, expectedType); return checkReply(reply); - } catch(ShutdownSignalException ex) { + } catch (ShutdownSignalException ex) { throw new IOException(ex.getMessage()); // wrap, re-throw } - } /** @@ -136,8 +177,7 @@ public Object call(String method, Object[] params) throws IOException, JsonRpcEx */ @Override public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable - { + throws Throwable { return call(method.getName(), args); } @@ -146,69 +186,42 @@ public Object invoke(Object proxy, Method method, Object[] args) */ @SuppressWarnings("unchecked") public T createProxy(Class klass) - throws IllegalArgumentException - { + throws IllegalArgumentException { return (T) Proxy.newProxyInstance(klass.getClassLoader(), - new Class[] { klass }, - this); - } - - /** - * Private API - used by {@link #call(String[])} to ad-hoc convert - * strings into the required data types for a call. - */ - public static Object coerce(String val, String type) - throws NumberFormatException - { - if ("bit".equals(type)) { - return Boolean.getBoolean(val) ? Boolean.TRUE : Boolean.FALSE; - } else if ("num".equals(type)) { - try { - return Integer.valueOf(val); - } catch (NumberFormatException nfe) { - return Double.valueOf(val); - } - } else if ("str".equals(type)) { - return val; - } else if ("arr".equals(type) || "obj".equals(type) || "any".equals(type)) { - return new JSONReader().read(val); - } else if ("nil".equals(type)) { - return null; - } else { - throw new IllegalArgumentException("Bad type: " + type); - } + new Class[] { klass }, + this); } /** - * Public API - as {@link #call(String,Object[])}, but takes the + * Public API - as {@link #call(String, Object[])}, but takes the * method name from the first entry in args, and the * parameters from subsequent entries. All parameter values are * passed through coerce() to attempt to make them the types the * server is expecting. + * * @return the result contained within the reply, if no exception is found - * @throws JsonRpcException if the reply object contained an exception + * @throws JsonRpcException if the reply object contained an exception * @throws NumberFormatException if a coercion failed - * @throws TimeoutException if a response is not received within the timeout specified, if any + * @throws TimeoutException if a response is not received within the timeout specified, if any * @see #coerce */ public Object call(String[] args) - throws NumberFormatException, IOException, JsonRpcException, TimeoutException - { - if (args.length == 0) { - throw new IllegalArgumentException("First string argument must be method name"); - } + throws NumberFormatException, IOException, JsonRpcException, TimeoutException { + if (args.length == 0) { + throw new IllegalArgumentException("First string argument must be method name"); + } - String method = args[0]; + String method = args[0]; int arity = args.length - 1; - ProcedureDescription proc = serviceDescription.getProcedure(method, arity); - ParameterDescription[] params = proc.getParams(); + ProcedureDescription proc = serviceDescription.getProcedure(method, arity); + ParameterDescription[] params = proc.getParams(); - Object[] actuals = new Object[arity]; - for (int count = 0; count < params.length; count++) { - actuals[count] = coerce(args[count + 1], params[count].type); - } + Object[] actuals = new Object[arity]; + for (int count = 0; count < params.length; count++) { + actuals[count] = coerce(args[count + 1], params[count].type); + } - return call(method, actuals); + return call(method, actuals); } /** @@ -216,7 +229,7 @@ public Object call(String[] args) * service loaded from the server itself at construction time. */ public ServiceDescription getServiceDescription() { - return serviceDescription; + return serviceDescription; } /** @@ -224,10 +237,10 @@ public ServiceDescription getServiceDescription() { * server, and parses and stores the resulting service description * in this object. * TODO: Avoid calling this from the constructor. + * * @throws TimeoutException if a response is not received within the timeout specified, if any */ - private void retrieveServiceDescription() throws IOException, JsonRpcException, TimeoutException - { + private void retrieveServiceDescription() throws IOException, JsonRpcException, TimeoutException { @SuppressWarnings("unchecked") Map rawServiceDescription = (Map) call("system.describe", null); serviceDescription = new ServiceDescription(rawServiceDescription); diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java index d1e32b0492..36f1ad72b2 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java @@ -1,6 +1,19 @@ -package com.rabbitmq.tools.jsonrpc; +// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. -import java.lang.reflect.Method; +package com.rabbitmq.tools.jsonrpc; /** * @@ -9,9 +22,7 @@ public interface JsonRpcMapper { JsonRpcRequest parse(String requestBody, ServiceDescription description); - JsonRpcResponse parse(String responseBody); - - Object[] parameters(JsonRpcRequest request, Method method); + JsonRpcResponse parse(String responseBody, Class expectedType); String write(Object input); @@ -52,7 +63,6 @@ public boolean isSystem() { public boolean isSystemDescribe() { return "system.describe".equals(method); } - } class JsonRpcResponse { @@ -85,5 +95,4 @@ public JsonRpcException getException() { return exception; } } - } diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java new file mode 100644 index 0000000000..633a1d79d7 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java @@ -0,0 +1,26 @@ +// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.tools.jsonrpc; + +/** + * + */ +public class JsonRpcMappingException extends RuntimeException { + + public JsonRpcMappingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java index 068fae1546..922fe35b1c 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java @@ -57,6 +57,16 @@ public class JsonRpcServer extends StringRpcServer { private final JsonRpcMapper mapper; + public JsonRpcServer(Channel channel, + Class interfaceClass, + Object interfaceInstance, JsonRpcMapper mapper) + throws IOException + { + super(channel); + this.mapper = mapper; + init(interfaceClass, interfaceInstance); + } + /** * Construct a server that talks to the outside world using the * given channel, and constructs a fresh temporary @@ -71,9 +81,7 @@ public JsonRpcServer(Channel channel, Object interfaceInstance) throws IOException { - super(channel); - this.mapper = new DefaultJsonRpcMapper(); - init(interfaceClass, interfaceInstance); + this(channel, interfaceClass, interfaceInstance, new DefaultJsonRpcMapper()); } private void init(Class interfaceClass, Object interfaceInstance) @@ -83,6 +91,17 @@ private void init(Class interfaceClass, Object interfaceInstance) this.serviceDescription = new ServiceDescription(interfaceClass); } + public JsonRpcServer(Channel channel, + String queueName, + Class interfaceClass, + Object interfaceInstance, JsonRpcMapper mapper) + throws IOException + { + super(channel, queueName); + this.mapper = mapper; + init(interfaceClass, interfaceInstance); + } + /** * Construct a server that talks to the outside world using the * given channel and queue name. Our superclass, @@ -100,9 +119,7 @@ public JsonRpcServer(Channel channel, Object interfaceInstance) throws IOException { - super(channel, queueName); - this.mapper = new DefaultJsonRpcMapper(); - init(interfaceClass, interfaceInstance); + this(channel, queueName, interfaceClass, interfaceInstance, new DefaultJsonRpcMapper()); } /** @@ -147,7 +164,6 @@ public String doCall(String requestBody) Object result; try { Method matchingMethod = matchingMethod(method, params); - params = mapper.parameters(request, matchingMethod); if (LOGGER.isDebugEnabled()) { Collection parametersValuesAndTypes = new ArrayList(); if (params != null) { diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/ProcedureDescription.java b/src/main/java/com/rabbitmq/tools/jsonrpc/ProcedureDescription.java index 57dcc39b05..714db1da96 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/ProcedureDescription.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/ProcedureDescription.java @@ -39,6 +39,8 @@ public class ProcedureDescription { private ParameterDescription[] params; /** Return type for this procedure */ private String returnType; + private String javaReturnType; + private Class _javaReturnTypeAsClass; /** Reflected method object, used for service invocation */ private Method method; @@ -68,6 +70,7 @@ public ProcedureDescription(Method m) { params[i] = new ParameterDescription(i, parameterTypes[i]); } this.returnType = ParameterDescription.lookup(m.getReturnType()); + this.javaReturnType = m.getReturnType().getName(); } public ProcedureDescription() { @@ -82,6 +85,47 @@ public ProcedureDescription() { /** Private API - used to get the reflected method object, for servers */ public Method internal_getMethod() { return method; } + public String getJavaReturnType() { + return javaReturnType; + } + + public void setJavaReturnType(String javaReturnType) { + this.javaReturnType = javaReturnType; + this._javaReturnTypeAsClass = computeReturnTypeAsJavaClass(); + } + + public Class getReturnType() { + return _javaReturnTypeAsClass; + } + + private Class computeReturnTypeAsJavaClass() { + try { + if ("int".equals(javaReturnType)) { + return Integer.TYPE; + } else if ("double".equals(javaReturnType)) { + return Double.TYPE; + } else if ("long".equals(javaReturnType)) { + return Long.TYPE; + } else if ("boolean".equals(javaReturnType)) { + return Boolean.TYPE; + } else if ("char".equals(javaReturnType)) { + return Character.TYPE; + } else if ("byte".equals(javaReturnType)) { + return Byte.TYPE; + } else if ("short".equals(javaReturnType)) { + return Short.TYPE; + } else if ("float".equals(javaReturnType)) { + return Float.TYPE; + } else if ("void".equals(javaReturnType)) { + return Void.TYPE; + } else { + return Class.forName(javaReturnType); + } + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Unable to load class: " + javaReturnType, e); + } + } + /** Gets an array of parameter descriptions for all this procedure's parameters */ public ParameterDescription[] internal_getParams() { return params; diff --git a/src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java b/src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java new file mode 100644 index 0000000000..555d4c0b24 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java @@ -0,0 +1,202 @@ +// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.tools.jsonrpc.JsonRpcClient; +import com.rabbitmq.tools.jsonrpc.JsonRpcMapper; +import com.rabbitmq.tools.jsonrpc.JsonRpcServer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.UndeclaredThrowableException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public abstract class AbstractJsonRpcTest { + + Connection clientConnection, serverConnection; + Channel clientChannel, serverChannel; + String queue = "json.rpc.queue"; + JsonRpcServer server; + JsonRpcClient client; + RpcService service; + + abstract JsonRpcMapper createMapper(); + + @Before + public void init() throws Exception { + clientConnection = TestUtils.connectionFactory().newConnection(); + clientChannel = clientConnection.createChannel(); + serverConnection = TestUtils.connectionFactory().newConnection(); + serverChannel = serverConnection.createChannel(); + serverChannel.queueDeclare(queue, false, false, false, null); + server = new JsonRpcServer(serverChannel, queue, RpcService.class, new DefaultRpcservice(), createMapper()); + new Thread(() -> { + try { + server.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled + } + }).start(); + client = new JsonRpcClient(clientChannel, "", queue, 1000, createMapper()); + service = client.createProxy(RpcService.class); + } + + @After + public void tearDown() throws Exception { + if (server != null) { + server.terminateMainloop(); + } + if (client != null) { + client.close(); + } + if (serverChannel != null) { + serverChannel.queueDelete(queue); + } + clientConnection.close(); + serverConnection.close(); + } + + public interface RpcService { + + boolean procedurePrimitiveBoolean(boolean input); + + Boolean procedureBoolean(Boolean input); + + String procedureString(String input); + + String procedureStringString(String input1, String input2); + + int procedurePrimitiveInteger(int input); + + Integer procedureInteger(Integer input); + + Double procedureDouble(Double input); + + double procedurePrimitiveDouble(double input); + + Integer procedureLongToInteger(Long input); + + int procedurePrimitiveLongToInteger(long input); + + Long procedureLong(Long input); + + long procedurePrimitiveLong(long input); + + Pojo procedureIntegerToPojo(Integer id); + + String procedurePojoToString(Pojo pojo); + + void procedureException(); + + } + + public static class DefaultRpcservice implements RpcService { + + @Override + public boolean procedurePrimitiveBoolean(boolean input) { + return !input; + } + + @Override + public Boolean procedureBoolean(Boolean input) { + return Boolean.valueOf(!input.booleanValue()); + } + + @Override + public String procedureString(String input) { + return input + 1; + } + + @Override + public String procedureStringString(String input1, String input2) { + return input1 + input2; + } + + @Override + public int procedurePrimitiveInteger(int input) { + return input + 1; + } + + @Override + public Integer procedureInteger(Integer input) { + return input + 1; + } + + @Override + public Long procedureLong(Long input) { + return input + 1; + } + + @Override + public long procedurePrimitiveLong(long input) { + return input + 1L; + } + + @Override + public Double procedureDouble(Double input) { + return input + 1; + } + + @Override + public double procedurePrimitiveDouble(double input) { + return input + 1; + } + + @Override + public Integer procedureLongToInteger(Long input) { + return (int) (input + 1); + } + + @Override + 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(); + } + + @Override + public void procedureException() { + throw new RuntimeException(); + } + } + + public static class Pojo { + + private String stringProperty; + + public String getStringProperty() { + return stringProperty; + } + + public void setStringProperty(String stringProperty) { + this.stringProperty = stringProperty; + } + } +} diff --git a/src/test/java/com/rabbitmq/client/JacksonRpcTest.java b/src/test/java/com/rabbitmq/client/JacksonRpcTest.java new file mode 100644 index 0000000000..ba95efef98 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/JacksonRpcTest.java @@ -0,0 +1,64 @@ +// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import com.rabbitmq.tools.jsonrpc.JacksonJsonRpcMapper; +import com.rabbitmq.tools.jsonrpc.JsonRpcException; +import com.rabbitmq.tools.jsonrpc.JsonRpcMapper; +import org.junit.Test; + +import java.lang.reflect.UndeclaredThrowableException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class JacksonRpcTest extends AbstractJsonRpcTest { + + @Override + JsonRpcMapper createMapper() { + return new JacksonJsonRpcMapper(); + } + + @Test + public void rpc() { + assertFalse(service.procedurePrimitiveBoolean(true)); + assertFalse(service.procedureBoolean(Boolean.TRUE).booleanValue()); + assertEquals("hello1", service.procedureString("hello")); + assertEquals("hello1hello2", service.procedureStringString("hello1", "hello2")); + assertEquals(2, service.procedureInteger(1).intValue()); + assertEquals(2, service.procedurePrimitiveInteger(1)); + assertEquals(2, service.procedureDouble(1.0).intValue()); + assertEquals(2, (int) service.procedurePrimitiveDouble(1.0)); + assertEquals(2, (int) service.procedureLongToInteger(1L)); + assertEquals(2, service.procedurePrimitiveLongToInteger(1L)); + assertEquals(2, service.procedurePrimitiveLong(1L)); + assertEquals(2, service.procedureLong(1L).longValue()); + assertEquals("123", service.procedureIntegerToPojo(123).getStringProperty()); + + Pojo pojo = new Pojo(); + pojo.setStringProperty("hello"); + assertEquals("hello", service.procedurePojoToString(pojo)); + + try { + service.procedureException(); + fail("Remote procedure throwing exception, an exception should have been thrown"); + } catch (UndeclaredThrowableException e) { + assertTrue(e.getCause() instanceof JsonRpcException); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/JsonRpcTest.java b/src/test/java/com/rabbitmq/client/JsonRpcTest.java index f13b5b4036..a4a18f9693 100644 --- a/src/test/java/com/rabbitmq/client/JsonRpcTest.java +++ b/src/test/java/com/rabbitmq/client/JsonRpcTest.java @@ -15,69 +15,44 @@ package com.rabbitmq.client; -import com.rabbitmq.client.test.TestUtils; -import com.rabbitmq.tools.jsonrpc.JsonRpcClient; -import com.rabbitmq.tools.jsonrpc.JsonRpcServer; -import org.junit.After; -import org.junit.Before; +import com.rabbitmq.tools.jsonrpc.DefaultJsonRpcMapper; +import com.rabbitmq.tools.jsonrpc.JacksonJsonRpcMapper; +import com.rabbitmq.tools.jsonrpc.JsonRpcException; +import com.rabbitmq.tools.jsonrpc.JsonRpcMapper; import org.junit.Test; import java.lang.reflect.UndeclaredThrowableException; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -public class JsonRpcTest { +public class JsonRpcTest extends AbstractJsonRpcTest { - Connection clientConnection, serverConnection; - Channel clientChannel, serverChannel; - String queue = "json.rpc.queue"; - JsonRpcServer server; - JsonRpcClient client; - RpcService service; - - @Before - public void init() throws Exception { - clientConnection = TestUtils.connectionFactory().newConnection(); - clientChannel = clientConnection.createChannel(); - serverConnection = TestUtils.connectionFactory().newConnection(); - serverChannel = serverConnection.createChannel(); - serverChannel.queueDeclare(queue, false, false, false, null); - server = new JsonRpcServer(serverChannel, queue, RpcService.class, new DefaultRpcservice()); - new Thread(() -> { - try { - server.mainloop(); - } catch (Exception e) { - // safe to ignore when loops ends/server is canceled - } - }).start(); - client = new JsonRpcClient(clientChannel, "", queue, 1000); - service = client.createProxy(RpcService.class); - } - - @After - public void tearDown() throws Exception { - if (server != null) { - server.terminateMainloop(); - } - if (client != null) { - client.close(); - } - if (serverChannel != null) { - serverChannel.queueDelete(queue); - } - clientConnection.close(); - serverConnection.close(); + @Override + JsonRpcMapper createMapper() { + return new DefaultJsonRpcMapper(); } @Test public void rpc() { + assertFalse(service.procedurePrimitiveBoolean(true)); + assertFalse(service.procedureBoolean(Boolean.TRUE).booleanValue()); assertEquals("hello1", service.procedureString("hello")); assertEquals(2, service.procedureInteger(1).intValue()); assertEquals(2, service.procedurePrimitiveInteger(1)); assertEquals(2, service.procedureDouble(1.0).intValue()); assertEquals(2, (int) service.procedurePrimitiveDouble(1.0)); + try { + service.procedureException(); + fail("Remote procedure throwing exception, an exception should have been thrown"); + } catch (UndeclaredThrowableException e) { + assertTrue(e.getCause() instanceof JsonRpcException); + } + + try { assertEquals(2, (int) service.procedureLongToInteger(1L)); fail("Long argument isn't supported"); @@ -115,105 +90,5 @@ public void rpc() { } catch (UndeclaredThrowableException e) { // OK } - - } - - public interface RpcService { - - String procedureString(String input); - - int procedurePrimitiveInteger(int input); - - Integer procedureInteger(Integer input); - - Double procedureDouble(Double input); - - double procedurePrimitiveDouble(double input); - - Integer procedureLongToInteger(Long input); - - int procedurePrimitiveLongToInteger(long input); - - Long procedureLong(Long input); - - long procedurePrimitiveLong(long input); - - Pojo procedureIntegerToPojo(Integer id); - - String procedurePojoToString(Pojo pojo); - - } - - public static class DefaultRpcservice implements RpcService { - - @Override - public String procedureString(String input) { - return input + 1; - } - - @Override - public int procedurePrimitiveInteger(int input) { - return input + 1; - } - - @Override - public Integer procedureInteger(Integer input) { - return input + 1; - } - - @Override - public Long procedureLong(Long input) { - return input + 1; - } - - @Override - public long procedurePrimitiveLong(long input) { - return input + 1L; - } - - @Override - public Double procedureDouble(Double input) { - return input + 1; - } - - @Override - public double procedurePrimitiveDouble(double input) { - return input + 1; - } - - @Override - public Integer procedureLongToInteger(Long input) { - return (int) (input + 1); - } - - @Override - 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; - } } -} +} \ No newline at end of file diff --git a/src/test/java/com/rabbitmq/client/test/ClientTests.java b/src/test/java/com/rabbitmq/client/test/ClientTests.java index 9c2994b671..78e8616d4b 100644 --- a/src/test/java/com/rabbitmq/client/test/ClientTests.java +++ b/src/test/java/com/rabbitmq/client/test/ClientTests.java @@ -16,6 +16,7 @@ package com.rabbitmq.client.test; +import com.rabbitmq.client.JacksonRpcTest; import com.rabbitmq.client.JsonRpcTest; import com.rabbitmq.utility.IntAllocatorTests; import org.junit.runner.RunWith; @@ -62,6 +63,7 @@ StrictExceptionHandlerTest.class, NoAutoRecoveryWhenTcpWindowIsFullTest.class, JsonRpcTest.class, + JacksonRpcTest.class, AddressTest.class, DefaultRetryHandlerTest.class }) From 01f10d6cf7c55167f40c7f82fe8459341c4e93f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= Date: Tue, 14 Aug 2018 10:13:12 +0200 Subject: [PATCH 3/4] Add JSON abstraction for JSON RPC support [#159302201] Fixes #378 --- .../com/rabbitmq/tools/json/JSONReader.java | 5 + .../rabbitmq/tools/json/JSONSerializable.java | 4 + .../com/rabbitmq/tools/json/JSONUtil.java | 79 ++++++------ .../com/rabbitmq/tools/json/JSONWriter.java | 4 + .../tools/jsonrpc/DefaultJsonRpcMapper.java | 45 ++----- .../tools/jsonrpc/JacksonJsonRpcMapper.java | 112 +++++++++++------- .../rabbitmq/tools/jsonrpc/JsonRpcClient.java | 5 + .../rabbitmq/tools/jsonrpc/JsonRpcMapper.java | 31 +++-- .../jsonrpc/JsonRpcMappingException.java | 1 + .../rabbitmq/tools/jsonrpc/JsonRpcServer.java | 100 ++++++++-------- .../rabbitmq/client/AbstractJsonRpcTest.java | 19 ++- .../client/BlockingCellBenchmark.java | 38 ------ ...onRpcTest.java => DefaultJsonRpcTest.java} | 5 +- ...onRpcTest.java => JacksonJsonRpcTest.java} | 17 ++- .../com/rabbitmq/client/test/ClientTests.java | 8 +- 15 files changed, 246 insertions(+), 227 deletions(-) delete mode 100644 src/test/java/com/rabbitmq/client/BlockingCellBenchmark.java rename src/test/java/com/rabbitmq/client/{JsonRpcTest.java => DefaultJsonRpcTest.java} (96%) rename src/test/java/com/rabbitmq/client/{JacksonRpcTest.java => JacksonJsonRpcTest.java} (80%) diff --git a/src/main/java/com/rabbitmq/tools/json/JSONReader.java b/src/main/java/com/rabbitmq/tools/json/JSONReader.java index 6c9420767e..fa4c43643d 100644 --- a/src/main/java/com/rabbitmq/tools/json/JSONReader.java +++ b/src/main/java/com/rabbitmq/tools/json/JSONReader.java @@ -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(); diff --git a/src/main/java/com/rabbitmq/tools/json/JSONSerializable.java b/src/main/java/com/rabbitmq/tools/json/JSONSerializable.java index 1cb1b0a77e..f453de5038 100644 --- a/src/main/java/com/rabbitmq/tools/json/JSONSerializable.java +++ b/src/main/java/com/rabbitmq/tools/json/JSONSerializable.java @@ -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 { /** diff --git a/src/main/java/com/rabbitmq/tools/json/JSONUtil.java b/src/main/java/com/rabbitmq/tools/json/JSONUtil.java index c40727d6e8..c024e142c8 100644 --- a/src/main/java/com/rabbitmq/tools/json/JSONUtil.java +++ b/src/main/java/com/rabbitmq/tools/json/JSONUtil.java @@ -13,7 +13,6 @@ // If you have any questions regarding licensing, please contact us at // info@rabbitmq.com. - package com.rabbitmq.tools.json; import org.slf4j.Logger; @@ -34,15 +33,15 @@ */ 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 source) - throws IntrospectionException, IllegalAccessException, InvocationTargetException - { - return fill(target, source, true); + throws IntrospectionException, IllegalAccessException, InvocationTargetException { + return fill(target, source, true); } /** @@ -50,38 +49,36 @@ public static Object fill(Object target, Map source) * properties of the target object from the source Map. */ public static Object fill(Object target, Map 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; } /** @@ -90,14 +87,14 @@ public static Object fill(Object target, Map source, boolean use * source Map. */ public static void tryFill(Object target, Map 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); + } } } diff --git a/src/main/java/com/rabbitmq/tools/json/JSONWriter.java b/src/main/java/com/rabbitmq/tools/json/JSONWriter.java index 4a7f78c7f0..eb82cc7751 100644 --- a/src/main/java/com/rabbitmq/tools/json/JSONWriter.java +++ b/src/main/java/com/rabbitmq/tools/json/JSONWriter.java @@ -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; diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java index c522d24767..db6cda0b6d 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java @@ -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. + *

+ * For a more comprehensive set of features, use {@link JacksonJsonRpcMapper}. + *

+ * 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 request = (Map) new JSONReader().read(requestBody); - + 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() @@ -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; - } - */ } diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java index 0a19f53921..9cb5411b25 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java @@ -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; @@ -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) { @@ -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)) { @@ -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 convertedParameters = new ArrayList<>(parameters.size()); if (!parameters.isEmpty()) { ProcedureDescription proc = description.getProcedure(method, parameters.size()); @@ -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 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) 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 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, result, map.get("error"), exception); + return new JsonRpcResponse(result, errorMap, exception); } @Override @@ -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; + } } diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java index 12032941f3..f64b9f47f0 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java @@ -50,9 +50,14 @@ * code can access the service description by reading the * serviceDescription field of * JsonRpcClient instances. + *

+ * {@link JsonRpcClient} delegates JSON parsing and generating to + * a {@link JsonRpcMapper}. * * @see #call(String, Object[]) * @see #call(String[]) + * @see JsonRpcMapper + * @see JacksonJsonRpcMapper */ public class JsonRpcClient extends RpcClient implements InvocationHandler { diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java index 36f1ad72b2..fdad5e1960 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java @@ -16,14 +16,37 @@ package com.rabbitmq.tools.jsonrpc; /** + * Abstraction to handle JSON parsing and generation. + * Used by {@link JsonRpcServer} and {@link JsonRpcClient}. * + * @since 5.4.0 */ public interface JsonRpcMapper { + /** + * Parses a JSON RPC request. + * The {@link ServiceDescription} can be used + * to look up the invoked procedure and learn about + * its signature. + * @param requestBody + * @param description + * @return + */ JsonRpcRequest parse(String requestBody, ServiceDescription description); + /** + * Parses a JSON RPC response. + * @param responseBody + * @param expectedType + * @return + */ JsonRpcResponse parse(String responseBody, Class expectedType); + /** + * Serialize an object into JSON. + * @param input + * @return + */ String write(Object input); class JsonRpcRequest { @@ -67,22 +90,16 @@ public boolean isSystemDescribe() { 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; + public JsonRpcResponse(Object result, Object error, JsonRpcException exception) { this.result = result; this.error = error; this.exception = exception; } - public Object getReply() { - return reply; - } - public Object getError() { return error; } diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java index 633a1d79d7..03a7d12b91 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java @@ -17,6 +17,7 @@ /** * + * @since 5.4.0 */ public class JsonRpcMappingException extends RuntimeException { diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java index 922fe35b1c..723664c3b5 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java @@ -13,55 +13,59 @@ // If you have any questions regarding licensing, please contact us at // info@rabbitmq.com. - package com.rabbitmq.tools.jsonrpc; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.StringRpcServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.StringRpcServer; -import com.rabbitmq.tools.json.JSONReader; -import com.rabbitmq.tools.json.JSONWriter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * JSON-RPC Server class. - * + *

* Given a Java {@link Class}, representing an interface, and an * implementation of that interface, JsonRpcServer will reflect on the * class to construct the {@link ServiceDescription}, and will route * incoming requests for methods on the interface to the * implementation object while the mainloop() is running. + *

+ * {@link JsonRpcServer} delegates JSON parsing and generating to + * a {@link JsonRpcMapper}. * * @see com.rabbitmq.client.RpcServer * @see JsonRpcClient + * @see JsonRpcMapper + * @see JacksonJsonRpcMapper */ public class JsonRpcServer extends StringRpcServer { private static final Logger LOGGER = LoggerFactory.getLogger(JsonRpcServer.class); - - /** Holds the JSON-RPC service description for this client. */ + private final JsonRpcMapper mapper; + /** + * Holds the JSON-RPC service description for this client. + */ public ServiceDescription serviceDescription; - /** The interface this server implements. */ + /** + * The interface this server implements. + */ public Class interfaceClass; - /** The instance backing this server. */ + /** + * The instance backing this server. + */ public Object interfaceInstance; - private final JsonRpcMapper mapper; - public JsonRpcServer(Channel channel, Class interfaceClass, Object interfaceInstance, JsonRpcMapper mapper) - throws IOException - { + throws IOException { super(channel); this.mapper = mapper; init(interfaceClass, interfaceInstance); @@ -71,32 +75,24 @@ public JsonRpcServer(Channel channel, * Construct a server that talks to the outside world using the * given channel, and constructs a fresh temporary * queue. Use getQueueName() to discover the created queue name. - * @param channel AMQP channel to use - * @param interfaceClass Java interface that this server is exposing to the world + * + * @param channel AMQP channel to use + * @param interfaceClass Java interface that this server is exposing to the world * @param interfaceInstance Java instance (of interfaceClass) that is being exposed * @throws IOException if something goes wrong during an AMQP operation */ public JsonRpcServer(Channel channel, - Class interfaceClass, - Object interfaceInstance) - throws IOException - { + Class interfaceClass, + Object interfaceInstance) + throws IOException { this(channel, interfaceClass, interfaceInstance, new DefaultJsonRpcMapper()); } - private void init(Class interfaceClass, Object interfaceInstance) - { - this.interfaceClass = interfaceClass; - this.interfaceInstance = interfaceInstance; - this.serviceDescription = new ServiceDescription(interfaceClass); - } - public JsonRpcServer(Channel channel, String queueName, Class interfaceClass, Object interfaceInstance, JsonRpcMapper mapper) - throws IOException - { + throws IOException { super(channel, queueName); this.mapper = mapper; init(interfaceClass, interfaceInstance); @@ -107,38 +103,43 @@ public JsonRpcServer(Channel channel, * given channel and queue name. Our superclass, * RpcServer, expects the queue to exist at the time of * construction. - * @param channel AMQP channel to use - * @param queueName AMQP queue name to listen for requests on - * @param interfaceClass Java interface that this server is exposing to the world + * + * @param channel AMQP channel to use + * @param queueName AMQP queue name to listen for requests on + * @param interfaceClass Java interface that this server is exposing to the world * @param interfaceInstance Java instance (of interfaceClass) that is being exposed * @throws IOException if something goes wrong during an AMQP operation */ public JsonRpcServer(Channel channel, - String queueName, - Class interfaceClass, - Object interfaceInstance) - throws IOException - { + String queueName, + Class interfaceClass, + Object interfaceInstance) + throws IOException { this(channel, queueName, interfaceClass, interfaceInstance, new DefaultJsonRpcMapper()); } + private void init(Class interfaceClass, Object interfaceInstance) { + this.interfaceClass = interfaceClass; + this.interfaceInstance = interfaceInstance; + this.serviceDescription = new ServiceDescription(interfaceClass); + } + /** * Override our superclass' method, dispatching to doCall. */ @Override - public String handleStringCall(String requestBody, AMQP.BasicProperties replyProperties) - { + public String handleStringCall(String requestBody, AMQP.BasicProperties replyProperties) { String replyBody = doCall(requestBody); return replyBody; } /** * Runs a single JSON-RPC request. + * * @param requestBody the JSON-RPC request string (a JSON encoded value) * @return a JSON-RPC response string (a JSON encoded value) */ - public String doCall(String requestBody) - { + public String doCall(String requestBody) { Object id; String method; Object[] params; @@ -188,7 +189,7 @@ public String doCall(String requestBody) } } catch (ClassCastException cce) { // Bogus request! - response = errorResponse(null, 400, "Bad Request", null); + response = errorResponse(null, 400, "Bad Request", null); } if (LOGGER.isDebugEnabled()) { @@ -200,13 +201,12 @@ public String doCall(String requestBody) /** * Retrieves the best matching method for the given method name and parameters. - * + *

* Subclasses may override this if they have specialised * dispatching requirements, so long as they continue to honour * their ServiceDescription. */ - public Method matchingMethod(String methodName, Object[] params) - { + public Method matchingMethod(String methodName, Object[] params) { ProcedureDescription proc = serviceDescription.getProcedure(methodName, params.length); return proc.internal_getMethod(); } diff --git a/src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java b/src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java index 555d4c0b24..079dcf00a7 100644 --- a/src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java +++ b/src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java @@ -21,12 +21,8 @@ import com.rabbitmq.tools.jsonrpc.JsonRpcServer; import org.junit.After; import org.junit.Before; -import org.junit.Test; -import java.lang.reflect.UndeclaredThrowableException; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import java.util.Date; public abstract class AbstractJsonRpcTest { @@ -105,6 +101,9 @@ public interface RpcService { void procedureException(); + void procedureNoArgumentVoid(); + + Date procedureDateDate(Date date); } public static class DefaultRpcservice implements RpcService { @@ -185,6 +184,16 @@ public String procedurePojoToString(Pojo pojo) { public void procedureException() { throw new RuntimeException(); } + + @Override + public void procedureNoArgumentVoid() { + + } + + @Override + public Date procedureDateDate(Date date) { + return date; + } } public static class Pojo { diff --git a/src/test/java/com/rabbitmq/client/BlockingCellBenchmark.java b/src/test/java/com/rabbitmq/client/BlockingCellBenchmark.java deleted file mode 100644 index d65d01f956..0000000000 --- a/src/test/java/com/rabbitmq/client/BlockingCellBenchmark.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.rabbitmq.client; - -import com.rabbitmq.utility.BlockingCell; - -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * - */ -public class BlockingCellBenchmark { - - public static void main(String[] args) { - - } - - public void legacyBlockingCell() { - ExecutorService executorService = Executors.newFixedThreadPool(10); - - for (int i = 0; i < 1000; i++) { - final BlockingCell cell = new BlockingCell(); - executorService.submit(new Callable() { - - @Override - public Void call() throws Exception { - cell.set("whatever"); - return null; - } - }); - - cell.uninterruptibleGet(); - - } - - } - -} diff --git a/src/test/java/com/rabbitmq/client/JsonRpcTest.java b/src/test/java/com/rabbitmq/client/DefaultJsonRpcTest.java similarity index 96% rename from src/test/java/com/rabbitmq/client/JsonRpcTest.java rename to src/test/java/com/rabbitmq/client/DefaultJsonRpcTest.java index a4a18f9693..049554d1d2 100644 --- a/src/test/java/com/rabbitmq/client/JsonRpcTest.java +++ b/src/test/java/com/rabbitmq/client/DefaultJsonRpcTest.java @@ -16,7 +16,6 @@ package com.rabbitmq.client; import com.rabbitmq.tools.jsonrpc.DefaultJsonRpcMapper; -import com.rabbitmq.tools.jsonrpc.JacksonJsonRpcMapper; import com.rabbitmq.tools.jsonrpc.JsonRpcException; import com.rabbitmq.tools.jsonrpc.JsonRpcMapper; import org.junit.Test; @@ -28,7 +27,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -public class JsonRpcTest extends AbstractJsonRpcTest { +public class DefaultJsonRpcTest extends AbstractJsonRpcTest { @Override JsonRpcMapper createMapper() { @@ -44,6 +43,7 @@ public void rpc() { assertEquals(2, service.procedurePrimitiveInteger(1)); assertEquals(2, service.procedureDouble(1.0).intValue()); assertEquals(2, (int) service.procedurePrimitiveDouble(1.0)); + service.procedureNoArgumentVoid(); try { service.procedureException(); @@ -52,7 +52,6 @@ public void rpc() { assertTrue(e.getCause() instanceof JsonRpcException); } - try { assertEquals(2, (int) service.procedureLongToInteger(1L)); fail("Long argument isn't supported"); diff --git a/src/test/java/com/rabbitmq/client/JacksonRpcTest.java b/src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java similarity index 80% rename from src/test/java/com/rabbitmq/client/JacksonRpcTest.java rename to src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java index ba95efef98..6ccb2c4751 100644 --- a/src/test/java/com/rabbitmq/client/JacksonRpcTest.java +++ b/src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java @@ -21,13 +21,15 @@ import org.junit.Test; import java.lang.reflect.UndeclaredThrowableException; +import java.util.Calendar; +import java.util.Date; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -public class JacksonRpcTest extends AbstractJsonRpcTest { +public class JacksonJsonRpcTest extends AbstractJsonRpcTest { @Override JsonRpcMapper createMapper() { @@ -49,6 +51,19 @@ public void rpc() { assertEquals(2, service.procedurePrimitiveLong(1L)); assertEquals(2, service.procedureLong(1L).longValue()); assertEquals("123", service.procedureIntegerToPojo(123).getStringProperty()); + service.procedureNoArgumentVoid(); + + Calendar calendar = Calendar.getInstance(); + Date date = calendar.getTime(); + Date returnedDate = service.procedureDateDate(date); + assertEquals(date.getTime(), returnedDate.getTime()); + + try { + service.procedureException(); + fail("Remote procedure throwing exception, an exception should have been thrown"); + } catch (UndeclaredThrowableException e) { + assertTrue(e.getCause() instanceof JsonRpcException); + } Pojo pojo = new Pojo(); pojo.setStringProperty("hello"); diff --git a/src/test/java/com/rabbitmq/client/test/ClientTests.java b/src/test/java/com/rabbitmq/client/test/ClientTests.java index 78e8616d4b..e79c40df73 100644 --- a/src/test/java/com/rabbitmq/client/test/ClientTests.java +++ b/src/test/java/com/rabbitmq/client/test/ClientTests.java @@ -16,8 +16,8 @@ package com.rabbitmq.client.test; -import com.rabbitmq.client.JacksonRpcTest; -import com.rabbitmq.client.JsonRpcTest; +import com.rabbitmq.client.JacksonJsonRpcTest; +import com.rabbitmq.client.DefaultJsonRpcTest; import com.rabbitmq.utility.IntAllocatorTests; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -62,8 +62,8 @@ TestUtilsTest.class, StrictExceptionHandlerTest.class, NoAutoRecoveryWhenTcpWindowIsFullTest.class, - JsonRpcTest.class, - JacksonRpcTest.class, + DefaultJsonRpcTest.class, + JacksonJsonRpcTest.class, AddressTest.class, DefaultRetryHandlerTest.class }) From d79a8b06d209e62d21c08e041d89c14261fc56f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= Date: Tue, 14 Aug 2018 10:35:26 +0200 Subject: [PATCH 4/4] Polish JSON RPC support References #378 --- src/main/java/com/rabbitmq/tools/json/JSONReader.java | 2 +- .../java/com/rabbitmq/tools/json/JSONSerializable.java | 2 +- src/main/java/com/rabbitmq/tools/json/JSONWriter.java | 2 +- .../com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java | 2 ++ src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java | 7 ------- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/rabbitmq/tools/json/JSONReader.java b/src/main/java/com/rabbitmq/tools/json/JSONReader.java index fa4c43643d..14c690e6e2 100644 --- a/src/main/java/com/rabbitmq/tools/json/JSONReader.java +++ b/src/main/java/com/rabbitmq/tools/json/JSONReader.java @@ -47,7 +47,7 @@ /** * Will be removed in 6.0 * - * @deprecated Use a third-party JSON library, e.g. Jackson or GJSON + * @deprecated Use a third-party JSON library, e.g. Jackson or Gson */ public class JSONReader { diff --git a/src/main/java/com/rabbitmq/tools/json/JSONSerializable.java b/src/main/java/com/rabbitmq/tools/json/JSONSerializable.java index f453de5038..39f72d4ac2 100644 --- a/src/main/java/com/rabbitmq/tools/json/JSONSerializable.java +++ b/src/main/java/com/rabbitmq/tools/json/JSONSerializable.java @@ -21,7 +21,7 @@ * * Will be removed in 6.0 * - * @deprecated Use a third-party JSON library, e.g. Jackson or GJSON + * @deprecated Use a third-party JSON library, e.g. Jackson or Gson */ public interface JSONSerializable { /** diff --git a/src/main/java/com/rabbitmq/tools/json/JSONWriter.java b/src/main/java/com/rabbitmq/tools/json/JSONWriter.java index eb82cc7751..7101598040 100644 --- a/src/main/java/com/rabbitmq/tools/json/JSONWriter.java +++ b/src/main/java/com/rabbitmq/tools/json/JSONWriter.java @@ -55,7 +55,7 @@ /** * Will be removed in 6.0 - * @deprecated Use a third-party JSON library, e.g. Jackson or GJSON + * @deprecated Use a third-party JSON library, e.g. Jackson or Gson */ public class JSONWriter { private boolean indentMode = false; diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java index db6cda0b6d..b40789e380 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/DefaultJsonRpcMapper.java @@ -38,6 +38,7 @@ public class DefaultJsonRpcMapper implements JsonRpcMapper { @Override public JsonRpcRequest parse(String requestBody, ServiceDescription description) { + @SuppressWarnings("unchecked") Map request = (Map) new JSONReader().read(requestBody); return new JsonRpcRequest( request.get("id"), request.get("version").toString(), request.get("method").toString(), @@ -47,6 +48,7 @@ public JsonRpcRequest parse(String requestBody, ServiceDescription description) @Override public JsonRpcResponse parse(String responseBody, Class expectedType) { + @SuppressWarnings("unchecked") Map map = (Map) (new JSONReader().read(responseBody)); Map error; JsonRpcException exception = null; diff --git a/src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java b/src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java index 6ccb2c4751..091ce44680 100644 --- a/src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java +++ b/src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java @@ -58,13 +58,6 @@ public void rpc() { Date returnedDate = service.procedureDateDate(date); assertEquals(date.getTime(), returnedDate.getTime()); - try { - service.procedureException(); - fail("Remote procedure throwing exception, an exception should have been thrown"); - } catch (UndeclaredThrowableException e) { - assertTrue(e.getCause() instanceof JsonRpcException); - } - Pojo pojo = new Pojo(); pojo.setStringProperty("hello"); assertEquals("hello", service.procedurePojoToString(pojo));