com.rabbitmq.tools.jsonrpc.JsonRpcClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of amqp-client Show documentation
Show all versions of amqp-client Show documentation
The RabbitMQ Java client library allows Java applications to interface with RabbitMQ.
// 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
// [email protected].
package com.rabbitmq.tools.jsonrpc;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
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;
/**
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 {
/** Holds the JSON-RPC service description for this client. */
private ServiceDescription serviceDescription;
/**
* 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);
retrieveServiceDescription();
}
public JsonRpcClient(Channel channel, String exchange, String routingKey)
throws IOException, JsonRpcException, TimeoutException
{
this(channel, exchange, routingKey, RpcClient.NO_TIMEOUT);
}
/**
* 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
*/
public static Object checkReply(Map reply)
throws JsonRpcException
{
if (reply.containsKey("error")) {
@SuppressWarnings("unchecked")
Map map = (Map) reply.get("error");
// actually a Map
throw new JsonRpcException(map);
}
Object result = reply.get("result");
//System.out.println(new JSONWriter().write(result));
return result;
}
/**
* 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();
request.put("id", null);
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);
try {
String replyStr = this.stringCall(requestStr);
@SuppressWarnings("unchecked")
Map map = (Map) (new JSONReader().read(replyStr));
return checkReply(map);
} catch(ShutdownSignalException ex) {
throw new IOException(ex.getMessage()); // wrap, re-throw
}
}
/**
* Public API - implements InvocationHandler.invoke. This is
* useful for constructing dynamic proxies for JSON-RPC
* interfaces.
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
return call(method.getName(), args);
}
/**
* Public API - gets a dynamic proxy for a particular interface class.
*/
public Object createProxy(Class klass)
throws IllegalArgumentException
{
return 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);
}
}
/**
* 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 NumberFormatException if a coercion failed
* @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");
}
String method = args[0];
int arity = args.length - 1;
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);
}
return call(method, actuals);
}
/**
* Public API - gets the service description record that this
* service loaded from the server itself at construction time.
*/
public ServiceDescription getServiceDescription() {
return serviceDescription;
}
/**
* Private API - invokes the "system.describe" method on the
* 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
{
@SuppressWarnings("unchecked")
Map rawServiceDescription = (Map) call("system.describe", null);
serviceDescription = new ServiceDescription(rawServiceDescription);
}
}