org.ms123.common.rpc.JsonRpcServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.ms123.common.rpc.JsonRpcServlet.java

Source

/*
 Java JSON RPC
 RPC Java POJO by Novlog
 http://www.novlog.com
    
 This library is dual-licensed under the GNU Lesser General Public License (LGPL) and the Eclipse Public License (EPL).
 Check http://qooxdoo.org/license
    
 This library is also licensed under the Apache license.
 Check http://www.apache.org/licenses/LICENSE-2.0
    
 Contribution:
 This contribution is provided by Novlog company.
 http://www.novlog.com
 */
package org.ms123.common.rpc;

import org.json.JSONException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceReference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import static org.apache.commons.io.FileUtils.copyFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("unchecked")
public class JsonRpcServlet extends HttpServlet {

    private flexjson.JSONSerializer m_js = new flexjson.JSONSerializer();

    private flexjson.JSONDeserializer m_ds = new flexjson.JSONDeserializer();

    private BundleContext m_bundleContext;
    private CallService m_callService;

    private Map<String, String> m_serviceMapping;

    public static final String DOPOST_RESPONSE_CONTENTTYPE = "text/plain; charset=UTF-8";

    private static final String CONTENT_TYPE = "Content-Type";

    private static final String DOPOST_REQUEST_CONTENTTYPE = "application/json";

    private static final String DOGET_RESPONSE_CONTENTTYPE = "text/javascript; charset=UTF-8";

    private static final String SCRIPT_TRANSPORT_ID = "_ScriptTransport_id";

    private static final String SCRIPT_TRANSPORT_DATA = "_ScriptTransport_data";

    private static final int READ_BUFFER_SIZE = 8192;

    /*
     * Error codes
     */
    // origin of error
    public static final int ERROR_FROM_SERVER = 1;

    public static final int ERROR_FROM_METHOD = 2;

    // error codes for errors that come from server (origin = 1 = ERROR_FROM_SERVER)
    public static final int ILLEGAL_SERVICE = 1;

    public static final int SERVICE_NOT_FOUND = 2;

    public static final int METHOD_NOT_FOUND = 4;

    public static final int PARAMETER_MISMATCH = 5;

    public static final int PERMISSION_DENIED = 6;

    public static final int INTERNAL_SERVER_ERROR = 500;

    protected static final String ACCESS_DENIED_RESULT = "alert('Access denied. Please make sure that your browser sends correct referer headers.')";

    /**
     * The referrer checking method used for RPC calls.
     */
    protected static int referrerCheck;

    protected static final int REFERRER_CHECK_STRICT = 0;

    protected static final int REFERRER_CHECK_DOMAIN = 1;

    protected static final int REFERRER_CHECK_SESSION = 2;

    protected static final int REFERRER_CHECK_PUBLIC = 3;

    protected static final int REFERRER_CHECK_FAIL = 4;

    protected static final String SESSION_REFERRER_KEY = "_qooxdoo_rpc_referrer";

    protected JSONSerializer m_jsonSerializer = null;

    protected JavaSerializer m_javaSerializer = null;

    protected RemoteCallUtils remoteCallUtils = null;

    private static final String UTF_8 = "UTF-8";

    public JsonRpcServlet(BundleContext bc) {
        m_bundleContext = bc;
        m_serviceMapping = new HashMap();
    }

    /**
     * Initializes this servlet.
     *
     * @param config the servlet config.
     */
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        this.m_jsonSerializer = doGetJsonSerializer();
        this.m_javaSerializer = doGetJavaSerializer();
        this.remoteCallUtils = new RemoteCallUtils(m_javaSerializer);
        final String referrerCheckName = config.getInitParameter("referrerCheck");
        if ("strict".equals(referrerCheckName)) {
            referrerCheck = REFERRER_CHECK_STRICT;
        } else if ("domain".equals(referrerCheckName)) {
            referrerCheck = REFERRER_CHECK_DOMAIN;
        } else if ("session".equals(referrerCheckName)) {
            referrerCheck = REFERRER_CHECK_SESSION;
        } else if ("public".equals(referrerCheckName)) {
            referrerCheck = REFERRER_CHECK_PUBLIC;
        } else if ("fail".equals(referrerCheckName)) {
            referrerCheck = REFERRER_CHECK_FAIL;
        } else {
            referrerCheck = REFERRER_CHECK_PUBLIC;
            // @@@MS
            log("No referrer checking configuration found. Using strict checking as the default.");
        }
    }

    protected JSONSerializer doGetJsonSerializer() {
        return new JSONSerializer();
    }

    protected JavaSerializer doGetJavaSerializer() {
        return new JavaSerializer();
    }

    public void putServiceMapping(String rpc_prefix, String service) {
        m_serviceMapping.put(rpc_prefix, service);
    }

    /**
     * Possibility to modify here the map produced from the JSON request.
     * The modification must be done in the same map instance since it's the one that will be used.
     *
     * @param request
     */
    protected void doModifyRequestMap(Map<String, Object> request) {
    }

    /**
     * Possibility to modify here the result of the invoked method.
     * The modification must be done in the same instance since it's the one that will be used.
     *
     * @param result The result of the invoked method, that has been prepared for JSON serialization
     *               result can be :
     *               - a Map
     *               - a List
     *               - a String
     *               - a primitive wrapped into an object (Integer, Boolean, Character, etc.)
     *               - null
     *               Only Map and List results should be modified
     */
    protected void doModifyMethodResult(Object result) {
    }

    public String handleRPC(final HttpServletRequest httpRequest, String requestString,
            HttpServletResponse response) throws ServletException {
        final Map<String, Object> requestMap = extractRequestMap(requestString);
        return handleRPC(httpRequest, requestMap, response);
    }

    public String handleRPC(final HttpServletRequest httpRequest, Map<String, Object> requestMap,
            HttpServletResponse response) throws ServletException {
        Map<String, Object> responseIntermediateObject;
        try {
            beforeCallService(httpRequest, requestMap);
            debug("httpRequest.pathInfo:" + httpRequest.getPathInfo());
            final Object methodResult = executeRequest(httpRequest.getPathInfo(), requestMap, httpRequest,
                    response);
            afterCallService(httpRequest, methodResult, requestMap);
            responseIntermediateObject = buildResponse(requestMap, methodResult, null);
        } catch (RpcException e) {
            responseIntermediateObject = buildResponse(requestMap, e);
        } catch (RemoteException e) {
            responseIntermediateObject = buildResponse(requestMap, e);
        }
        // TODO: what does happen in case of other exception?
        return m_js.deepSerialize(responseIntermediateObject);
    }

    protected final void beforeCallService(final HttpServletRequest httpRequest,
            final Map<String, Object> requestMap) throws RpcException {
        final String serviceName = (String) requestMap.get("service");
        final String methodName = (String) requestMap.get("method");
        final Object methodParams = requestMap.get("params");
        if (methodParams instanceof Map) {
            doBeforeCallService(httpRequest, serviceName, methodName, (Map) methodParams);
        }
        if (methodParams instanceof List) {
            doBeforeCallService(httpRequest, serviceName, methodName, (List) methodParams);
        }
        callHooks(httpRequest, serviceName, methodName, methodParams, null, true);
    }

    protected final void afterCallService(HttpServletRequest httpRequest, Object result,
            final Map<String, Object> requestMap) throws RpcException {
        final String serviceName = (String) requestMap.get("service");
        final String methodName = (String) requestMap.get("method");
        final Object methodParams = requestMap.get("params");
        callHooks(httpRequest, serviceName, methodName, methodParams, result, false);
    }

    private void callHooks(HttpServletRequest httpRequest, String serviceName, String methodName,
            Object methodParams, Object result, boolean before) {
        CallService hs = getCallService();
        if (hs != null) {
            String[] x = httpRequest.getPathInfo().split("/");
            if (serviceName == null && x.length > 1) {
                serviceName = x[1];
            }
            if (methodName == null && x.length > 2) {
                methodName = x[2];
            }
            Map props = new HashMap();
            props.put("service", serviceName);
            props.put("method", methodName);
            props.put("params", methodParams);
            props.put("result", result);
            props.put("at", before ? "before" : "after");
            hs.callHooks(props);
        } else {
            error("CallServicce not available");
        }
    }

    protected void doBeforeCallService(final HttpServletRequest httpRequest, final String serviceName,
            final String methodName, final List<Object> methodParams) throws RpcException {
    }

    protected void doBeforeCallService(final HttpServletRequest httpRequest, final String serviceName,
            final String methodName, final Map<String, Object> methodParams) throws RpcException {
    }

    protected String getRequestString(final HttpServletRequest request) throws Exception {
        String requestString = null;
        InputStream is = null;
        Reader reader = null;
        boolean rpcForm = false;
        if (request.getPathInfo().indexOf("__rpcForm__") != -1) {
            rpcForm = true;
        } else if (request.getPathInfo().indexOf("/get") != -1) {
            rpcForm = true;
        }
        final String contentType = request.getHeader(CONTENT_TYPE);
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        debug("getRequestString:isMultipart:" + isMultipart + "/rpcForm:" + rpcForm);
        if (isMultipart) {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            factory.setSizeThreshold(0);
            ServletFileUpload upload = new ServletFileUpload(factory);
            List<FileItem> items = upload.parseRequest(request);
            if (items.size() < 1) {
                throw new RuntimeException("insert:No Uploadfile");
            }
            Map dataMap = new HashMap();
            for (FileItem fi : items) {
                if (fi.isFormField()) {
                    String fieldname = fi.getFieldName();
                    String fieldvalue = fi.getString();
                    debug("ItemValue:" + fieldname + "=" + fieldvalue);
                    dataMap.put(fieldname, fieldvalue);
                } else {
                    debug("FileItem:" + fi.getFieldName());
                    dataMap.put(fi.getFieldName(), unpackFileItem((DiskFileItem) fi));
                }
            }
            debug("dataMap:" + dataMap);
            requestString = (String) dataMap.get("__rpc__");
            if (requestString == null) {
                requestString = (String) dataMap.get("rpc");
            }
            debug("rpcParams:" + requestString);
            dataMap.remove("__rpc__");
            dataMap.remove("rpc");
            dataMap.remove("__hints__");
            Map p = (Map) m_ds.deserialize(requestString);
            Map params = (Map) p.get("params");
            params.put("fileMap", dataMap);
            params.put("docid", dataMap.get("docid"));
            flexjson.JSONSerializer js = new flexjson.JSONSerializer();
            requestString = js.deepSerialize(p);
        } else if (rpcForm) {
            requestString = request.getParameter("__rpc__");
            if (requestString == null) {
                requestString = request.getParameter("rpc");
            }
        } else {
            try {
                is = request.getInputStream();
                reader = new InputStreamReader(is, UTF_8);
                final StringBuilder sb = new StringBuilder(READ_BUFFER_SIZE);
                char[] readBuffer = new char[READ_BUFFER_SIZE];
                int length;
                while ((length = reader.read(readBuffer)) != -1) {
                    sb.append(readBuffer, 0, length);
                }
                requestString = sb.toString();
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                    }
                }
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                    }
                }
            }
        }
        return requestString;
    }

    private Map unpackFileItem(DiskFileItem fi) {
        Map ret = new HashMap();
        try {
            String storeLocation = fi.getStoreLocation().toString();
            File newFile = File.createTempFile("upload", ".file");
            File origFile = new File(storeLocation);
            //org.ms123.common.utils.FileUtils.copy(new File(storeLocation), newFile);
            copyFile(new File(storeLocation), newFile);
            ret.put("storeLocation", newFile.toString());
            ret.put("fileName", fi.getName());
            origFile.delete();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret;
    }

    public Map<String, Object> extractRequestMap(final String requestString) throws ServletException {
        Object requestIntermediateObject;
        try {
            debug("extractRequestMap:" + requestString);
            requestIntermediateObject = m_jsonSerializer.unserialize(requestString);
        } catch (JSONException e) {
            throw new ServletException("Unable to read request", e);
        }
        final Map<String, Object> requestMap = (Map<String, Object>) requestIntermediateObject;
        doModifyRequestMap(requestMap);
        return requestMap;
    }

    protected Object executeRequest(String pathInfo, final Map<String, Object> requestIntermediateObject,
            HttpServletRequest request, HttpServletResponse response) throws RpcException, RemoteException {
        debug("executeRequest:" + requestIntermediateObject);
        final String serviceName = (String) requestIntermediateObject.get("service");
        final String methodName = (String) requestIntermediateObject.get("method");
        final Object methodParams = requestIntermediateObject.get("params");
        if (CallService.CAMELSERVICENAME.equals(serviceName) || CallService.CAMELSERVICENAME2.equals(serviceName)) {
            return m_callService.callCamel(methodName, methodParams, request, response);
        }
        return callProcedure(pathInfo, serviceName, methodName, methodParams, request, response);
    }

    protected Object callProcedure(String pathInfo, String service, String method, final Object args,
            HttpServletRequest request, HttpServletResponse response) throws RpcException, RemoteException {
        Remote serviceInstance;
        debug("callProcedure:" + service + "/method:" + method + "/args:" + args);
        debug("ServiceMapping:" + m_serviceMapping);
        String[] x = pathInfo.split("/");
        if (service == null && x.length > 1) {
            service = x[1];
        }
        if (method == null && x.length > 2) {
            method = x[2];
        }
        String _service = m_serviceMapping.get(service);
        if (_service != null) {
            service = _service;
        }
        ServiceReference sr = m_bundleContext.getServiceReference(service);
        debug("sr:" + sr);
        if (sr == null) {
            throw new RpcException(ERROR_FROM_SERVER, SERVICE_NOT_FOUND, "Service " + service + " not found");
        }
        Object methodResult;
        try {
            Object o = m_bundleContext.getService(sr);
            methodResult = remoteCallUtils.callCompatibleMethod(o, method, args != null ? args : new HashMap(),
                    request, response);
        } catch (NoSuchMethodException e) {
            throw new RpcException(ERROR_FROM_SERVER, METHOD_NOT_FOUND, "Method " + method + " not found", e);
        } catch (IllegalAccessException e) {
            throw new RpcException(ERROR_FROM_SERVER, METHOD_NOT_FOUND, "Method " + method + " not found", e);
        } catch (InvocationTargetException e) {
            final Throwable raisedException = e.getCause();
            throw new RpcException(ERROR_FROM_METHOD, null, "An exception (" + raisedException.getClass()
                    + ") was raised by the call to method " + method + "(...) : " + raisedException.getMessage(),
                    raisedException);
        }
        return methodResult;
    }

    protected Map<String, Object> buildResponse(final Map<String, Object> request, final Object methodResult,
            final Map<Class, List<String>> wantedFields) {
        Map<String, Object> response;
        try {
            final Object result = m_javaSerializer.serialize(methodResult, wantedFields, 0, 10);
            doModifyMethodResult(result);
            response = new HashMap<String, Object>(3);
            response.put("id", request.get("id"));
            response.put("error", null);
            response.put("result", methodResult);
        } catch (SerializationException e) {
            response = buildResponse(request, new RpcException(ERROR_FROM_SERVER, INTERNAL_SERVER_ERROR,
                    "Unable to serialize method result.", e));
        }
        return response;
    }

    protected Map<String, Object> buildResponse(final Map<String, Object> request, final RpcException exception) {
        final Map<String, Object> response = new HashMap<String, Object>(3);
        final Map<String, Object> error = new HashMap<String, Object>(6);
        error.put("origin", exception.getOrigin());
        error.put("code", exception.getErrorCode());
        String cmessage = null;
        Throwable cause = exception.getCause();
        if (cause != null) {
            while (cause.getCause() != null) {
                cause = cause.getCause();
            }
            cmessage = cause.toString();
            if (cmessage == null) {
                cmessage = cause.toString();
            }
        }
        error.put("message", constructMessage(exception.getMessage(), cmessage));
        error.put("class", exception.getClass().getName());
        response.put("id", request.get("id"));
        response.put("error", error);
        response.put("result", null);
        exception.printStackTrace();
        return response;
    }

    protected Map<String, Object> buildResponse(final Map<String, Object> request,
            final RemoteException exception) {
        final Map<String, Object> response = new HashMap<String, Object>(3);
        final Map<String, Object> error = new HashMap<String, Object>(6);
        error.put("origin", ERROR_FROM_METHOD);
        error.put("code", null);
        error.put("message", exception.getMessage());
        error.put("class", exception.getClass().getName());
        final Throwable cause = exception.getCause();
        if (cause != null) {
        }
        response.put("id", request.get("id"));
        response.put("error", error);
        response.put("result", null);
        return response;
    }

    protected Map<String, Object> convertException(Throwable exception) {
        final Map<String, Object> error = new HashMap<String, Object>(4);
        error.put("message", exception.getClass().getName() + ": " + exception.getMessage());
        error.put("origMessage", exception.getMessage());
        error.put("class", exception.getClass().getName());
        final Throwable cause = exception.getCause();
        if (cause != null) {
            error.put("cause", convertException(cause));
        }
        return error;
    }

    private String constructMessage(String m1, String m2) {
        if (m2 == null) {
            return m1;
        }
        if (m1 != null && !m1.endsWith(":")) {
            return m1 + ":" + m2;
        }
        return m1 + m2;
    }

    /**
     * Checks the referrer based on the configured strategy
     *
     * @param request the incoming request.
     * @return whether or not the request should be granted.
     */
    protected boolean checkReferrer(HttpServletRequest request) {
        if (referrerCheck == REFERRER_CHECK_PUBLIC) {
            return true;
        }
        if (referrerCheck == REFERRER_CHECK_FAIL) {
            return false;
        }
        String referrer = request.getHeader("Referer");
        debug("Referer:" + referrer);
        if (referrer == null) {
            return false;
        }
        if (referrerCheck == REFERRER_CHECK_STRICT) {
            String contextURL = getContextURL(request);
            return referrer.startsWith(contextURL);
        }
        if (referrerCheck == REFERRER_CHECK_DOMAIN) {
            String domainURL = getDomainURL(request);
            return referrer.startsWith(domainURL);
        }
        if (referrerCheck == REFERRER_CHECK_SESSION) {
            // find the domain part of the referrer
            int colonIndex = referrer.indexOf(":");
            if (colonIndex == -1) {
                return false;
            }
            int referrerLength = referrer.length();
            int i;
            for (i = colonIndex + 1;; ++i) {
                if (i >= referrerLength) {
                    return false;
                }
                if (referrer.charAt(i) != '/') {
                    break;
                }
            }
            int slashIndex = referrer.indexOf("/", i + 1);
            if (slashIndex == -1) {
                return false;
            }
            String referrerDomain = referrer.substring(0, slashIndex + 1);
            HttpSession session = request.getSession();
            String oldReferrerDomain = (String) session.getAttribute(SESSION_REFERRER_KEY);
            if (oldReferrerDomain == null) {
                session.setAttribute(SESSION_REFERRER_KEY, referrerDomain);
            } else {
                if (!oldReferrerDomain.equals(referrerDomain)) {
                    return false;
                }
            }
            return true;
        }
        throw new IllegalStateException("Internal error: unknown referrer checking configuration");
    }

    protected String getContextURL(HttpServletRequest request) {
        // reconstruct the start of the URL
        StringBuffer contextURL = new StringBuffer();
        String scheme = request.getScheme();
        int port = request.getServerPort();
        contextURL.append(scheme);
        contextURL.append("://");
        contextURL.append(request.getServerName());
        if ((scheme.equals("http") && port != 80) || (scheme.equals("https") && port != 443)) {
            contextURL.append(':');
            contextURL.append(request.getServerPort());
        }
        contextURL.append(request.getContextPath());
        return contextURL.toString();
    }

    protected String getDomainURL(HttpServletRequest request) {
        // reconstruct the start of the URL
        StringBuffer domainURL = new StringBuffer();
        String scheme = request.getScheme();
        int port = request.getServerPort();
        domainURL.append(scheme);
        domainURL.append("://");
        domainURL.append(request.getServerName());
        if ((scheme.equals("http") && port != 80) || (scheme.equals("https") && port != 443)) {
            domainURL.append(':');
            domainURL.append(request.getServerPort());
        }
        domainURL.append('/');
        return domainURL.toString();
    }

    /**
     * @param request  the servlet request.
     * @param response the servlet response.
     * @throws ServletException thrown when executing the method or writing the response fails.
     */
    @Override
    public void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException {
        debug("RPC.doOptions:" + request);
        String origin = request.getHeader("Origin");
        debug("doOptions:" + origin);
        if (origin != null) {
            response.setHeader("Access-Control-Allow-Origin", origin);
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
            response.setHeader("Access-Control-Allow-Headers", "X-PINGOTHER,Authorization,Content-Type");
            //response.setHeader("Access-Control-Max-Age", "1728000");
            response.setHeader("Keep-Alive", "timeout=2,max=100");
            response.setHeader("Connection", "Keep-Alive");
        }
    }

    /**
     * Remote method execution. The method name and parameters are expected
     * in JSON format in the request body.
     *
     * @param request  the servlet request.
     * @param response the servlet response.
     * @throws ServletException thrown when executing the method or writing the response fails.
     */
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException {
        String result = null;
        String requestString = null;
        try {
            requestString = getRequestString(request);
        } catch (Exception e) {
            throw new ServletException("Cannot read request", e);
        }
        result = handleRPC(request, requestString, response);
        if (!response.isCommitted()) {
            String origin = request.getHeader("Origin");
            if (origin != null) {
                response.setHeader("Access-Control-Allow-Origin", origin);
            }
            response.setContentType(DOPOST_RESPONSE_CONTENTTYPE);
            try {
                final Writer responseWriter = response.getWriter();
                if (request.getParameter("pretty") != null) {
                    result = makePretty(result);
                }
                responseWriter.write(result);
            } catch (IOException e) {
                throw new ServletException("Cannot write response", e);
            }
        }
    }

    private String makePretty(String s) {
        m_js.prettyPrint(true);
        return m_js.deepSerialize(m_ds.deserialize(s));
    }

    /**
     * Returns context path information to the client (as a JavaScript hash
     * map).
     *
     * @param request  the servlet request.
     * @param response the servlet response.
     * @throws ServletException thrown when writing the response fails.
     */
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
        if (request.getPathInfo().indexOf("__rpcForm__") != -1) {
            doPost(request, response);
        } else if (request.getPathInfo().indexOf("/get") != -1) {
            doPost(request, response);
        }
    }

    private CallService getCallService() {
        if (m_callService != null)
            return m_callService;
        ServiceReference ref = m_bundleContext.getServiceReference(CallService.class.getName());
        if (ref != null) {
            m_callService = (CallService) m_bundleContext.getService(ref);
        }
        return m_callService;
    }

    protected void debug(String msg) {
        //System.out.println(msg);
        m_logger.debug(msg);
    }

    protected void info(String msg) {
        System.out.println(msg);
        m_logger.info(msg);
    }

    protected void error(String msg) {
        System.out.println(msg);
        m_logger.error(msg);
    }

    private static final org.slf4j.Logger m_logger = org.slf4j.LoggerFactory.getLogger(JsonRpcServlet.class);
}