// Copyright (c) 2007-Present 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 java.io.IOException; import java.lang.reflect.Method; 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; /** * 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. * * @see com.rabbitmq.client.RpcServer * @see JsonRpcClient */ public class JsonRpcServer extends StringRpcServer { /** Holds the JSON-RPC service description for this client. */ public ServiceDescription serviceDescription; /** The interface this server implements. */ public Class<?> interfaceClass; /** The instance backing this server. */ public Object interfaceInstance; /** * 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 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 { super(channel); init(interfaceClass, interfaceInstance); } private void init(Class<?> interfaceClass, Object interfaceInstance) { this.interfaceClass = interfaceClass; this.interfaceInstance = interfaceInstance; this.serviceDescription = new ServiceDescription(interfaceClass); } /** * Construct a server that talks to the outside world using the * 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 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 { super(channel, queueName); init(interfaceClass, interfaceInstance); } /** * Override our superclass' method, dispatching to doCall. */ @Override 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) { Object id; String method; Object[] params; try { @SuppressWarnings("unchecked") Map<String, Object> request = (Map<String,Object>) new JSONReader().read(requestBody); if (request == null) { return errorResponse(null, 400, "Bad Request", null); } if (!ServiceDescription.JSON_RPC_VERSION.equals(request.get("version"))) { return errorResponse(null, 505, "JSONRPC version not supported", null); } id = request.get("id"); method = (String) request.get("method"); List<?> parmList = (List<?>) request.get("params"); params = parmList.toArray(); } catch (ClassCastException cce) { // Bogus request! return errorResponse(null, 400, "Bad Request", null); } if (method.equals("system.describe")) { return resultResponse(id, serviceDescription); } else if (method.startsWith("system.")) { return errorResponse(id, 403, "System methods forbidden", null); } else { Object result; try { result = matchingMethod(method, params).invoke(interfaceInstance, params); } catch (Throwable t) { return errorResponse(id, 500, "Internal Server Error", t); } return resultResponse(id, result); } } /** * 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) { ProcedureDescription proc = serviceDescription.getProcedure(methodName, params.length); return proc.internal_getMethod(); } /** * Construct and encode a JSON-RPC error response for the request * 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) { Map<String, Object> err = new HashMap<String, Object>(); err.put("name", "JSONRPCError"); err.put("code", code); err.put("message", message); err.put("error", errorArg); return response(id, "error", err); } /** * 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) { return response(id, "result", result); } /** * Private API - used by errorResponse and resultResponse. */ public static String response(Object id, String label, Object value) { Map<String, Object> resp = new HashMap<String, Object>(); 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); return respStr; } /** * Public API - gets the service description record that this * service built from interfaceClass at construction time. */ public ServiceDescription getServiceDescription() { return serviceDescription; } }