com.github.stephanarts.cas.ticket.registry.support.JSONRPCServer.java Source code

Java tutorial

Introduction

Here is the source code for com.github.stephanarts.cas.ticket.registry.support.JSONRPCServer.java

Source

/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.github.stephanarts.cas.ticket.registry.support;

import java.util.HashMap;

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

import org.json.JSONObject;
import org.json.JSONException;

import org.zeromq.ZMQ;
import org.zeromq.ZMsg;
import org.zeromq.ZFrame;
import org.zeromq.ZMQ.Context;
import org.zeromq.ZMQ.Socket;
import org.zeromq.ZMQ.Poller;

/**
 * JSONRPCServer Class.
 */
public class JSONRPCServer extends Thread {

    /**
     * Logging Class.
     */
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * ZMQ Context.
     */
    private final Context context;

    /**
     * ZMQ Socket.
     */
    private final Socket socket;

    /**
     * ZMQ Control Socket.
     */
    private final Socket controlSocket;

    /**
     * BindURI.
     */
    private final String bindUri;

    /**
     * Hashmap of Methods.
     */
    private final HashMap<String, IMethod> methodMap;

    /**
     * Hashmap of call-counters.
     */
    private final HashMap<String, Integer> methodCounterMap;

    private static int NR = 0;

    private static Object NRLOCK = new Object();

    private final int nr;

    /**
     * Create a JSONRPCServer object.
     *
     * @param bindUri   The URI to listen on
     */
    public JSONRPCServer(final String bindUri) {

        this.context = ZMQ.context(1);

        this.socket = this.context.socket(ZMQ.ROUTER);
        this.controlSocket = this.context.socket(ZMQ.PULL);

        this.bindUri = bindUri;

        this.setName("JSONRPCServer");

        this.methodMap = new HashMap<String, IMethod>();
        this.methodCounterMap = new HashMap<String, Integer>();

        synchronized (this.NRLOCK) {
            this.NR++;
            this.nr = this.NR;
        }
    }

    /**
     * Register a JSONRPCFunction.
     *
     * @param name                    Method Name.
     * @param method                  Method Class
     * @throws JSONRPCException       Exception
     */
    public final void registerMethod(final String name, final IMethod method) throws JSONRPCException {

        /**
         * Check if a method with this name is already
         * registered.
         */
        if (this.methodMap.containsKey(name)) {
            throw new JSONRPCException(-1, "Method already registered");
        }

        this.methodMap.put(name, method);
        this.methodCounterMap.put(name, new Integer(0));
    }

    /**
     * Run the server.
     */
    public final void run() {

        ZMsg message;
        ZFrame body;
        String msg;

        String resp = null;

        Poller items = new Poller(2);

        /** Enter the main event-loop */
        items.register(this.socket, Poller.POLLIN);
        int i = items.register(this.controlSocket, Poller.POLLIN);

        logger.debug("Registered controlSocket as item: " + i);

        logger.debug("Entering main event-loop [" + this.nr + "]");
        /** Enter the main event-loop */
        while (!Thread.currentThread().isInterrupted()) {
            items.poll();
            logger.debug("[" + this.nr + "] - got input");

            /**
             * TODO
             *
             * Don't assume indexes, properly use the ones returned
             * by the items.register function.
             */
            if (items.pollin(1)) {
                message = ZMsg.recvMsg(controlSocket);
                logger.debug("Received STOP message [" + this.nr + "]");
                break;
            }
            if (items.pollin(0)) {
                message = ZMsg.recvMsg(socket);
                body = message.getLast();
                byte[] d = body.getData();
                if (d.length == 1 && d[0] == 0x0) {
                    /* Send pong */
                    message.removeLast();
                    message.addLast(new byte[] { 0x0 });
                    message.send(this.socket);
                } else {
                    msg = new String(body.getData());

                    logger.debug("Got a message");

                    try {
                        resp = handleJSONRPC(msg);

                        logger.debug("Sent a reply");
                        message.removeLast();
                        message.addString(resp);
                        message.send(this.socket);
                    } catch (final Exception e) {
                        logger.warn(e.toString());
                    }
                }
            }

        }

        logger.debug("Closing context [" + this.nr + "]");
        this.socket.setLinger(0);
        this.controlSocket.setLinger(0);

        this.controlSocket.close();
        this.socket.close();
        this.context.close();
    }

    /**
     * Handle JSONRPC call.
     *
     * @param msg     String message of the request.
     *
     * @return        String message of the response.
     */
    protected final String handleJSONRPC(final String msg) {
        JSONObject response = new JSONObject();
        JSONObject result;
        JSONObject error = null;

        JSONObject request;

        String methodName;
        String methodId = null;

        IMethod method;
        Integer methodCounter = 0;

        JSONObject params;

        response.put("jsonrpc", "2.0");

        try {
            request = new JSONObject(msg);

            validateJSONRPC(request);

            /**
             * Get the methodId, required for sending a response.
             */
            methodId = request.optString("id");

            methodName = request.getString("method");
            if (!this.methodMap.containsKey(methodName)) {
                /**
                 * code = -32601
                 * msg = Method not Found
                 */
                throw new JSONRPCException(-32601, "Method not Found");
            }

            method = this.methodMap.get(methodName);
            if (method == null) {
                /**
                 * code = -32601
                 * msg = Method not Found
                 */
                throw new JSONRPCException(-32601, "Method not Found");
            }

            params = request.getJSONObject("params");

            methodCounter = this.methodCounterMap.get(methodName);
            this.methodCounterMap.put(methodName, methodCounter + 1);

            result = method.execute(params);

            if (methodId != null) {
                response.put("id", methodId);
                response.put("result", result);
            }
        } catch (final JSONException e) {
            response.put("id", methodId);

            error = new JSONObject();
            response.put("error", error);
            error.put("code", -32700);
            error.put("message", "Parse error");
            logger.warn("Parse error");
            logger.warn(e.getMessage());
        } catch (final JSONRPCException e) {
            response.put("id", methodId);

            error = new JSONObject();
            response.put("error", error);
            error.put("code", e.getCode());
            error.put("message", e.getMessage());
            logger.warn(e.getMessage());
        } catch (final Exception e) {
            response.put("id", methodId);

            error = new JSONObject();
            response.put("error", error);
            error.put("code", -32603);
            error.put("message", "Internal error");
            logger.warn("Internal error");
        }

        if (methodId == null && error == null) {
            return null;
        }

        return response.toString();
    }

    /**
     * Validate JSONRPC call.
     *
     * @param  request    JSONRPC request object
     *
     * @throws JSONRPCException Throws exception if request object contains
     * malformed or unsupported json-rpc
     *
     * Batch calls or array-style parameters are not supported.
     *
     */
    protected final void validateJSONRPC(final JSONObject request) throws JSONRPCException {

        JSONObject params;

        JSONObject method;

        if (!request.has("jsonrpc")) {
            /**
             * code = -32600
             * msg = Invalid Request
             */
            throw new JSONRPCException(-32600, "Invalid Request");
        } else {
            if (!request.getString("jsonrpc").equals("2.0")) {
                /**
                 * code = -32600
                 * msg = Invalid Request
                 */
                throw new JSONRPCException(-32600, "Invalid Request");
            }
        }

        if (!request.has("params")) {
            /**
             * code = -32600
             * msg = Invalid Request
             */
            throw new JSONRPCException(-32600, "Invalid Request");
        }

        /**
         * We only support named params at the moment.
         */
        params = request.getJSONObject("params");
        if (params == null) {
            /**
             * code = -32600
             * msg = Invalid Request
             */
            throw new JSONRPCException(-32600, "Invalid Request");
        }

        if (!request.has("method")) {
            /**
             * code = -32600
             * msg = Invalid Request
             */
            throw new JSONRPCException(-32600, "Invalid Request");
        }

    }

    /**
     * Send a 'stop' message to the control socket.
     *
     */
    public final void interrupt() {
        byte[] msg = new byte[1];
        Socket s = this.context.socket(ZMQ.PUSH);
        s.connect("inproc://jsonrpc-" + this.nr);
        s.send(msg, ZMQ.NOBLOCK);
        s.close();
        logger.debug("Sent a STOP Message to inproc://jsonrpc-" + this.nr);
        //super.interrupt();
    }

    /**
     * Start the JSONRPCServer thread.
     *
     * Binds the sockets before calling Thread.start(), this way
     * they are already bound before the first run() is executed.
     */
    public final void start() {
        /** Bind Socket */
        logger.debug("Binding Sockets [" + this.nr + "]");
        this.socket.bind(this.bindUri);
        this.controlSocket.bind("inproc://jsonrpc-" + this.nr);

        logger.debug("Starting JSONRPCServer [" + this.nr + "]");
        super.start();
    }

    /**
     * Return counter for method.
     *
     * @param methodName method for which the statistics are kept.
     *
     * @return counter (or -1 if method does not exist)
     */
    protected final int getMethodStats(final String methodName) {

        Integer methodCounter;

        if (!this.methodCounterMap.containsKey(methodName)) {
            return -1;
        }

        methodCounter = this.methodCounterMap.get(methodName);

        return methodCounter.intValue();
    }

    /**
     * Close.
     */
    public final void cleanup() {
        this.interrupt();
        try {
            this.join();
        } catch (final InterruptedException e) {
            // It's fine if join throws an InterruptedException
            logger.debug(e.toString());
        }
    }
}