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

Java tutorial

Introduction

Here is the source code for org.fortiss.smg.remoteframework.lib.jsonrpc.JsonRpcServer.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.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
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.SerializationFeature;
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 {
    private static ObjectMapper staticMapper;
    /** 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;
    private ObjectMapper mapper;
    private final static Logger logger = (Logger) LoggerFactory.getLogger(JsonRpcServer.class.getCanonicalName());

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

        // init gson factories
        mapper = new ObjectMapper();
        mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
        //mapper.enableDefaultTyping();
    }

    public static ObjectMapper getMapper() {
        if (staticMapper == null) {
            staticMapper = new ObjectMapper();
            staticMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
            staticMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
            staticMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            staticMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
        }
        return staticMapper;
    }

    /**
     * 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.
     */
    public String handleStringCall(String requestBody, AMQP.BasicProperties replyProperties) {
        String replyBody = null;
        try {
            replyBody = doCall(requestBody);
        } catch (JsonProcessingException e) {
            // TODO Auto-generated catch block
            logger.error("JSONProcessing", e.fillInStackTrace());
        } catch (IOException e) {
            logger.error("IOException", e.fillInStackTrace());
        }
        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)
     * @throws IOException 
     * @throws JsonProcessingException 
     */
    public String doCall(String requestBody) throws JsonProcessingException, IOException {
        logger.debug("Request-Server: " + requestBody);
        Object id = null;
        String method;
        Object[] params;
        try {

            if (requestBody.length() < 1) {
                return errorResponse(null, 400, "Bad Request", null);
            }

            //JsonObject request = jsonParser.parse(requestBody)

            JsonNode rootNode = mapper.readTree(requestBody);

            if (!ServiceDescription.JSON_RPC_VERSION.equals(rootNode.path("version").textValue())) {
                return errorResponse(null, 505, "JSONRPC version not supported", null);
            }

            method = rootNode.path("method").textValue();
            logger.debug("Method: " + method + "()");

            // id = request.get("id").getAsString();
            if (method.startsWith("system.")) {
                @SuppressWarnings("unchecked")
                Map<String, Object> request2 = (Map<String, Object>) new JSONReader().read(requestBody);
                id = request2.get("id");
                List<?> parmList = (List<?>) request2.get("params");
                params = parmList.toArray();

            } else {
                JsonNode jsonParams = rootNode.path("params");
                Iterator<JsonNode> jsonArray = jsonParams.elements();

                int arrSize = 0;

                for (JsonNode node : jsonParams) {
                    arrSize++;
                }

                Method met = matchingMethod(method, arrSize);

                params = new Object[arrSize];

                logger.debug("Method: " + met.getName() + "()");
                Type[] gen_types = met.getGenericParameterTypes();
                Class<?>[] stat_types = met.getParameterTypes();

                int j = 0;
                for (JsonNode element : jsonParams) {
                    logger.debug("\tParameter: " + gen_types[j]);
                    JavaType type = mapper.getTypeFactory().constructType(gen_types[j], stat_types[j]);
                    params[j] = mapper.readValue(element.traverse(), type);
                    j++;
                }

            }

        } 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 {
                Method meth = matchingMethod(method, params);
                if (meth != null) {
                    result = meth.invoke(interfaceInstance, params);
                }
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
                return errorResponse(id, 500, "IllegalArgumentException", e);
            } catch (Throwable t) {
                t.printStackTrace();
                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, int size) {
        ProcedureDescription proc = serviceDescription.getProcedure(methodName, size);
        return proc.internal_getMethod();
    }

    /**
     * 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) {
        return matchingMethod(methodName, params.length);
    }

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

        String respStr = "";
        try {
            respStr = getMapper().writeValueAsString(resp);
        } catch (JsonProcessingException e) {
            logger.error("JSON deserialze error" + e);
        }
        logger.debug("Result-Server: " + respStr);
        return respStr;
    }

    /**
     * Public API - gets the service description record that this service built
     * from interfaceClass at construction time.
     */
    public ServiceDescription getServiceDescription() {
        return serviceDescription;
    }

    @Override
    public void close() throws IOException {
        if (super.getChannel() != null && super.getChannel().isOpen()) {
            super.getChannel().close();
            super.close();
        }
    }
}