Package com.googlecode.jsonrpc4j

Source Code of com.googlecode.jsonrpc4j.JsonRpcServer

package com.googlecode.jsonrpc4j;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;

/**
* A JSON-RPC request server reads JSON-RPC requests from an
* input stream and writes responses to an output stream.
*/
public class JsonRpcServer {

  private static final Logger LOGGER = Logger.getLogger(JsonRpcServer.class.getName());

    public static final String JSONRPC_RESPONSE_CONTENT_TYPE = "application/json-rpc";

    private boolean rethrowExceptions = false;
  private ObjectMapper mapper;
  private Object handler;
  private Class<?> remoteInterface;

  /**
   * Creates the server with the given {@link ObjectMapper} delegating
   * all calls to the given {@code handler} {@link Object} but only
   * methods available on the {@code remoteInterface}.
   * @param mapper the {@link ObjectMapper}
   * @param handler the {@code handler}
   * @param remoteInterface the interface
   */
  public JsonRpcServer(ObjectMapper mapper, Object handler, Class<?> remoteInterface) {
    this.mapper        = mapper;
    this.handler       = handler;
    this.remoteInterface  = remoteInterface;
  }

  /**
   * Creates the server with the given {@link ObjectMapper} delegating
   * all calls to the given {@code handler}.
   * @param mapper the {@link ObjectMapper}
   * @param handler the {@code handler}
   */
  public JsonRpcServer(ObjectMapper mapper, Object handler) {
    this(mapper, handler, null);
  }

  /**
   * Creates the server with a default {@link ObjectMapper} delegating
   * all calls to the given {@code handler} {@link Object} but only
   * methods available on the {@code remoteInterface}.
   * @param handler the {@code handler}
   * @param remoteInterface the interface
   */
  public JsonRpcServer(Object handler, Class<?> remoteInterface) {
    this(new ObjectMapper(), handler, remoteInterface);
  }

  /**
   * Creates the server with a default {@link ObjectMapper} delegating
   * all calls to the given {@code handler}.
   * @param handler the {@code handler}
   */
  public JsonRpcServer(Object handler) {
    this(new ObjectMapper(), handler, null);
  }

  /**
   * Handles a single request from the given {@link InputStream},
   * that is to say that a single {@link JsonNode} is read from
   * the stream and treated as a JSON-RPC request.  All responses
   * are written to the given {@link OutputStream}.
   * @param ips the {@link InputStream}
   * @param ops the {@link OutputStream}
   * @throws JsonParseException
   * @throws JsonMappingException
   * @throws IOException
   */
  public void handle(HttpServletRequest request, HttpServletResponse response)
    throws JsonParseException,
    JsonMappingException,
    IOException {

      // set response type
      response.setContentType(JSONRPC_RESPONSE_CONTENT_TYPE);
      // setup streams
      InputStream input   = null;
      OutputStream output  = response.getOutputStream();

    // POST
    if (request.getMethod().equals("POST")) {
      input = request.getInputStream();

    // GET
    } else if (request.getMethod().equals("GET")) {

      // get parameters
      String method  = request.getParameter("method");
      String id    = request.getParameter("id");
      String params   = URLDecoder.decode(new String(Base64.decode(
        request.getParameter("params"))), "UTF-8");

      // create full RPC request
      StringBuilder buff = new StringBuilder();
      buff.append("{ ")
        .append("\"id\": \"").append(id).append("\", ")
        .append("\"method\": \"").append(method).append("\", ")
        .append("\"params\": ").append(params).append(" ")
        .append("}");

      // setup stream to byte array
      input = new ByteArrayInputStream(buff.toString().getBytes());

    // invalid request
    } else {
      throw new IOException(
        "Invalid request method, only POST and GET is supported");
    }

    // service the request
    handleNode(mapper.readValue(input, JsonNode.class), output);
  }

  /**
   * Handles a single request from the given {@link InputStream},
   * that is to say that a single {@link JsonNode} is read from
   * the stream and treated as a JSON-RPC request.  All responses
   * are written to the given {@link OutputStream}.
   * @param ips the {@link InputStream}
   * @param ops the {@link OutputStream}
   * @throws JsonParseException
   * @throws JsonMappingException
   * @throws IOException
   */
  public void handle(InputStream ips, OutputStream ops)
    throws JsonParseException,
    JsonMappingException,
    IOException {
    handleNode(mapper.readValue(ips, JsonNode.class), ops);
  }

  /**
   * Returns the handler's class or interface.
   * @return the class
   */
  private Class<?> getHandlerClass() {
    return (remoteInterface != null)
      ? remoteInterface : handler.getClass();
  }

  /**
   * Handles the given {@link JsonNode} and writes the
   * responses to the given {@link OutputStream}.
   * @param node the {@link JsonNode}
   * @param ops the {@link OutputStream}
   * @throws JsonGenerationException
   * @throws JsonMappingException
   * @throws IOException
   */
  private void handleNode(JsonNode node, OutputStream ops)
    throws JsonGenerationException,
    JsonMappingException,
    IOException {

    // handle objects
    if (node.isObject()) {
      handleObject(ObjectNode.class.cast(node), ops);

    // handle arrays
    } else if (node.isArray()) {
      handleArray(ArrayNode.class.cast(node), ops);

    // bail on bad data
    } else {
      throw new IllegalArgumentException(
        "Invalid JsonNode type: "+node.getClass().getName());
    }
  }

  /**
   * Handles the given {@link ArrayNode} and writes the
   * responses to the given {@link OutputStream}.
   * @param node the {@link JsonNode}
   * @param ops the {@link OutputStream}
   * @throws JsonGenerationException
   * @throws JsonMappingException
   * @throws IOException
   */
  private void handleArray(ArrayNode node, OutputStream ops)
    throws JsonGenerationException,
    JsonMappingException,
    IOException {

    // loop through each array element
    for (int i=0; i<node.size(); i++) {
      handleNode(node.get(i), ops);
    }
  }

  /**
   * Handles the given {@link ObjectNode} and writes the
   * responses to the given {@link OutputStream}.
   * @param node the {@link JsonNode}
   * @param ops the {@link OutputStream}
   * @throws JsonGenerationException
   * @throws JsonMappingException
   * @throws IOException
   */
  private void handleObject(ObjectNode node, OutputStream ops)
    throws JsonGenerationException,
    JsonMappingException,
    IOException {

    // validate request
    if (!node.has("jsonrpc") || !node.has("method")) {
      mapper.writeValue(ops, createErrorResponse(
        "jsonrpc", "null", -32600, "Invalid Request", null));
      return;
    }

    // parse request
    String jsonRpc    = node.get("jsonrpc").getValueAsText();
    String methodName  = node.get("method").getValueAsText();
    String id      = node.get("id").getValueAsText();
    JsonNode params    = node.get("params");
    int paramCount    = (params!=null) ? params.size() : 0;

    // find methods
    Set<Method> methods = new HashSet<Method>();
    methods.addAll(ReflectionUtil.findMethods(getHandlerClass(), methodName));

    // iterate through the methods and remove
    // the one's who's parameter count's don't
    // match the request
    Iterator<Method> itr = methods.iterator();
    while (itr.hasNext()) {
      Method method = itr.next();
      if (method.getParameterTypes().length!=paramCount) {
        itr.remove();
      }
    }

    // method not found
    if (methods.isEmpty()) {
      mapper.writeValue(ops, createErrorResponse(
        jsonRpc, id, -32601, "Method not found", null));
      return;
    }

    // choose a method
    Method method = null;
    List<JsonNode> paramNodes = new ArrayList<JsonNode>();

    // handle param arrays, no params
    if (paramCount==0 || params.isArray()) {
      method = methods.iterator().next();
      for (int i=0; i<paramCount; i++) {
        paramNodes.add(params.get(i));
      }

    // handle named params
    } else if (params.isObject()) {

      // loop through each method
      for (Method m : methods) {

        // get method annotations
        Annotation[][] annotations = m.getParameterAnnotations();
        boolean found = true;
        List<JsonNode> namedParams = new ArrayList<JsonNode>();
        for (int i=0; i<annotations.length; i++) {

          // look for param name annotations
          String paramName = null;
          for (int j=0; j<annotations[i].length; j++) {
            if (!JsonRpcParamName.class.isInstance(annotations[i][j])) {
              continue;
            } else {
              paramName = JsonRpcParamName.class.cast(annotations[i][j]).value();
              continue;
            }
          }

          // bail if param name wasn't found
          if (paramName==null) {
            found = false;
            break;

          // found it by name
          } else if (params.has(paramName)) {
            namedParams.add(params.get(paramName));
          }
        }

        // did we find it?
        if (found) {
          method = m;
          paramNodes.addAll(namedParams);
          break;
        }
      }
    }

    // invalid parameters
    if (method==null) {
      mapper.writeValue(ops, createErrorResponse(
        jsonRpc, id, -32602, "Invalid method parameters.", null));
      return;
    }

    // get @JsonRpcErrors and nested @JsonRpcError annotations on method
    Annotation[] methodAnnotations = method.getAnnotations();
    JsonRpcError[] errorMappings = {};
    for (Annotation a : methodAnnotations) {
      if (!JsonRpcErrors.class.isInstance(a)) {
        continue;
      } else {
        errorMappings = JsonRpcErrors.class.cast(a).value();
      }
    }

    // invoke the method
    JsonNode result = null;
    ObjectNode error = null;
    Throwable thrown = null;
    try {
      result = invoke(method, paramNodes);
    } catch (Throwable e) {
      thrown = e;
      if (InvocationTargetException.class.isInstance(e)) {
        e = InvocationTargetException.class.cast(e).getTargetException();
      }
      error = mapper.createObjectNode();

      // use error
      boolean errorMapped = false;
      for (JsonRpcError em : errorMappings) {
        if (em.exception().isInstance(e)) {
          error.put("code", em.code());
          error.put("message", em.message());

          // get data from annotation
          String data = em.data();

          // default to exception message
          if("".equals(data)) {
            data = e.getMessage();
          }

          // only add the data if we have a value
          if (data != null && !"".equals(data)) {
            error.put("data", data);
          }

          // we used an annotation for the exception
          errorMapped = true;
          break;
        }
      }

      // generate generic error response
      if (!errorMapped) {
        error.put("code", 0);
        error.put("message", e.getMessage());
        error.put("data", e.getClass().getName());
      }
    }

    // bail if notification request
    if (id==null) {
      return;
    }

    // create response
    ObjectNode response = mapper.createObjectNode();
    response.put("jsonrpc", jsonRpc);
    response.put("id", id);
    if (error==null) {
      response.put("result", result);
    } else if (error!=null) {
      response.put("error", error);
    }

    // write it
    mapper.writeValue(ops, response);

    // log and potentially re-throw errors
    if (thrown!=null) {
      LOGGER.log(Level.SEVERE, "Error in JSON-RPC Service", thrown);
      if (rethrowExceptions) {
        throw new RuntimeException(thrown);
      }
    }
  }

  /**
   * Invokes the given method on the {@code handler} passing
   * the given params (after converting them to beans\objects)
   * to it.
   * @param m the method to invoke
   * @param params the params to pass to the method
   * @return the return value (or null if no return)
   * @throws JsonParseException
   * @throws JsonMappingException
   * @throws IOException
   * @throws IllegalArgumentException
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  private JsonNode invoke(Method m, List<JsonNode> params)
    throws JsonParseException,
    JsonMappingException,
    IOException,
    IllegalArgumentException,
    IllegalAccessException,
    InvocationTargetException {

    // convert the parameters
    Object[] convertedParams = new Object[params.size()];
    Type[] parameterTypes = m.getGenericParameterTypes();
    for (int i=0; i<parameterTypes.length; i++) {
      convertedParams[i] = mapper.readValue(
        params.get(i), TypeFactory.type(parameterTypes[i]));
    }

    // invoke the method
    Object result = m.invoke(handler, convertedParams);
    return (m.getGenericReturnType()!=null) ? mapper.valueToTree(result) : null;
  }

  /**
   * Convenience method for creating an error response
   * @param jsonRpc the jsonrpc string
   * @param id the id
   * @param code the error code
   * @param message the error message
   * @param data the error data (if any)
   * @return the error response
   */
  private ObjectNode createErrorResponse(
    String jsonRpc, String id, int code, String message, Object data) {
    ObjectNode response = mapper.createObjectNode();
    ObjectNode error = mapper.createObjectNode();
    error.put("code", code);
    error.put("message", message);
    if (data!=null) {
      error.put("data",  mapper.valueToTree(data));
    }
    response.put("jsonrpc", jsonRpc);
    response.put("id", id);
    response.put("error", error);
    return response;
  }

  /**
   * Indicates whether or not the server is re-throwing exceptions.
   * @return true if re-throwing, false otherwise
   */
  public boolean isRethrowExceptions() {
    return rethrowExceptions;
  }

  /**
   * Sets whether or not the server should re-throw exceptions.
   * @param rethrowExceptions true or false
   */
  public void setRethrowExceptions(boolean rethrowExceptions) {
    this.rethrowExceptions = rethrowExceptions;
  }

}
TOP

Related Classes of com.googlecode.jsonrpc4j.JsonRpcServer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.