org.fortiss.smg.remoteframework.lib.jsonrpc.JsonRpcClient.java Source code

Java tutorial

Introduction

Here is the source code for org.fortiss.smg.remoteframework.lib.jsonrpc.JsonRpcClient.java

Source

//  The contents of this file are subject to the Mozilla Public License
//  Version 1.1 (the "License"); you may not use this file except in
//  compliance with the License. You may obtain a copy of the License
//  at http://www.mozilla.org/MPL/
//
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
//  the License for the specific language governing rights and
//  limitations under the License.
//
//  The Original Code is RabbitMQ.
//
//  The Initial Developer of the Original Code is GoPivotal, Inc.
//  Copyright (c) 2007-2013 GoPivotal, Inc.  All rights reserved.
//

package org.fortiss.smg.remoteframework.lib.jsonrpc;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

import org.fortiss.smg.remoteframework.lib.except.JsonRpcException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
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;

/**
 * <a href="http://json-rpc.org">JSON-RPC</a> is a lightweight RPC mechanism
 * using <a href="http://www.json.org/">JSON</a> 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 <code>JsonRpcClient</code>
 * 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 <code>system.describe</code> procedure when it is constructed, and
 * uses the information to coerce parameter types appropriately. A JSON service
 * description is parsed into instances of <code>ServiceDescription</code>.
 * Client code can access the service description by reading the
 * <code>serviceDescription</code> field of <code>JsonRpcClient</code>
 * 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;
    private ObjectMapper mapper;
    private final static Logger logger = (Logger) LoggerFactory.getLogger(JsonRpcClient.class.getCanonicalName());

    /**
     * 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();
        init();
    }

    public JsonRpcClient(Channel channel, String exchange, String routingKey)
            throws IOException, JsonRpcException, TimeoutException {
        this(channel, exchange, routingKey, RpcClient.NO_TIMEOUT);
        init();
    }

    private void init() {
        mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        //mapper.enableDefaultTyping();
    }

    /**
     * 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<String, Object> reply) throws JsonRpcException {
        if (reply.containsKey("error")) {
            @SuppressWarnings("unchecked")
            Map<String, Object> map = (Map<String, Object>) reply.get("error");
            // actually a Map<String, Object>
            throw new JsonRpcException(map);
        }

        Object result = reply.get("result");
        logger.debug("Client-Reply:" + 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.
     * 
     * @param proxy
     * @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
     * @throws SQLException 
     */
    public Object call_gson(Method method, Object[] params)
            throws IOException, JsonRpcException, TimeoutException, SQLException {
        HashMap<String, Object> request = new HashMap<String, Object>();
        request.put("id", null);
        request.put("method", method.getName());
        request.put("version", ServiceDescription.JSON_RPC_VERSION);
        //request.put("params", (params == null) ? new Object[0] : params);
        //request.put("params", params);

        String requestStr = new JSONWriter().write(request);
        // normally we would use that,but we can't because of polymorphic typing
        // names of the class are erased
        // String requestStr = mapper.writeValueAsString(request);

        if (params != null) {
            StringBuilder strBuilder = new StringBuilder(",\"params\":[");
            for (int i = 0; i < params.length; i++) {
                strBuilder.append(mapper.writeValueAsString(params[i]));
                // no comma at last element
                if (i != params.length - 1) {
                    strBuilder.append(",");
                }
            }
            strBuilder.append("]}");
            requestStr = requestStr.substring(0, requestStr.length() - 1) + strBuilder;
        }

        //      ObjectNode node = mapper.createObjectNode();
        //      node.put("id", "");
        //      node.put("method", method.getName());
        //      node.put("version", ServiceDescription.JSON_RPC_VERSION);
        //ArrayNode par = node.putArray("params");
        //par.
        //      for(Object p : params){
        //         System.out.println(p.getClass());
        //      }
        //      
        //   
        //      node.put( "params" , mapper.getNodeFactory().POJONode(params));

        logger.debug("Request-Client: " + requestStr);

        try {
            String replyStr = this.stringCall(requestStr);

            // TODO : maybe we should make void calls asynchronous
            if (!method.getReturnType().equals(Void.TYPE)) {

                JsonNode rootNode = mapper.readTree(replyStr);

                JavaType type = mapper.getTypeFactory().constructType(method.getGenericReturnType(),
                        method.getReturnType());
                logger.debug("Type: " + method.getGenericReturnType());
                Object response = mapper.readValue(rootNode.get("result").traverse(), type);

                return response;
            } else {
                return null;
            }
        } catch (ShutdownSignalException ex) {
            throw new IOException(ex.getMessage()); // wrap, re-throw
        } catch (TimeoutException e) {
            throw new TimeoutException(e.getMessage());
        }

    }

    /**
     * Public API - builds, encodes and sends a JSON-RPC request, and waits for
     * the response.
     * 
     * @param proxy
     * @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<String, Object> request = new HashMap<String, Object>();
        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);
        logger.debug("Request-Client: " + requestStr);
        try {
            String replyStr = this.stringCall(requestStr);
            @SuppressWarnings("unchecked")
            Map<String, Object> map = (Map<String, Object>) (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.
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, TimeoutException {
        return call_gson(method, 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 {
        System.out.println("val=" + val + " type=" + type);
        if ("bit".equals(type)) {
            return Boolean.getBoolean(val) ? Boolean.TRUE : Boolean.FALSE;
        } else if ("num".equals(type)) {
            try {
                return new Integer(val);
            } catch (NumberFormatException nfe) {
                return new Double(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 <code>args</code>, 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<String, Object> rawServiceDescription = (Map<String, Object>) call("system.describe", null);
        serviceDescription = new ServiceDescription(rawServiceDescription);
    }
}