org.jwebsocket.plugins.rpc.RPCPlugIn.java Source code

Java tutorial

Introduction

Here is the source code for org.jwebsocket.plugins.rpc.RPCPlugIn.java

Source

//   ---------------------------------------------------------------------------
//   jWebSocket - RPCPlugIn Plug-In
//   Copyright (c) 2010 Alexander Schulze, Innotrade GmbH
//   ---------------------------------------------------------------------------
//   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/lgpl.html>.
//   ---------------------------------------------------------------------------
package org.jwebsocket.plugins.rpc;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.Map.Entry;
import javolution.util.FastMap;
import org.apache.log4j.Logger;
import org.json.JSONObject;
import org.jwebsocket.api.WebSocketConnector;
import org.jwebsocket.api.WebSocketEngine;
import org.jwebsocket.config.JWebSocketConfig;
import org.jwebsocket.config.JWebSocketServerConstants;
import org.jwebsocket.factory.JWebSocketJarClassLoader;
import org.jwebsocket.logging.Logging;
import org.jwebsocket.kit.PlugInResponse;
import org.jwebsocket.plugins.TokenPlugIn;
import org.jwebsocket.security.SecurityFactory;
import org.jwebsocket.token.Token;

/**
 * This plug-in provides all the functionality for remote procedure calls
 * (RPC) for client-to-server (C2S) apps, and reverse remote procedure calls
 * (RRPC) for server-to-client (S2C) or client-to-client apps (C2C).
 * @author aschulze
 */
public class RPCPlugIn extends TokenPlugIn {

    private static Logger mLog = Logging.getLogger(RPCPlugIn.class);
    private Map<String, Object> mGrantedProcs = new FastMap<String, Object>();
    // private DemoRPCServer mRpcServer = null;
    // if namespace changed update client plug-in accordingly!
    private String NS_RPC_DEFAULT = JWebSocketServerConstants.NS_BASE + ".plugins.rpc";
    // private Map<String, String> mJars = new FastMap<String, String>();
    // TODO: use RpcCallable instead of Object here!
    private Map<String, Object> mClasses = new FastMap<String, Object>();

    // TODO: RRPC calls do not yet allow quotes in arguments
    // TODO: We need simple unique IDs to address a certain target, session id not suitable here.
    // TODO: Show target(able) clients in a drop down box
    // TODO: RPC demo does not show other clients logging in
    /**
     *
     */
    public RPCPlugIn() {
        if (mLog.isDebugEnabled()) {
            mLog.debug("Instantiating rpc plug-in...");
        }
        // specify default name space
        this.setNamespace(NS_RPC_DEFAULT);

        // currently this is the only supported RPCPlugIn server
        // mRpcServer = new DemoRPCServer();

    }

    @Override
    public void engineStarted(WebSocketEngine aEngine) {

        // TODO: move JWebSocketJarClassLoader into ServerAPI module ?
        JWebSocketJarClassLoader lClassLoader = new JWebSocketJarClassLoader();
        Class lClass = null;

        // load map of RPC libraries first
        // also load map of granted procs
        Map<String, String> lSettings = getSettings();
        String lKey;
        String lValue;
        for (Entry<String, String> lSetting : lSettings.entrySet()) {
            lKey = lSetting.getKey();
            lValue = lSetting.getValue();
            if (lKey.startsWith("class:")) {
                String lClassName = lKey.substring(6);
                try {
                    if (mLog.isDebugEnabled()) {
                        mLog.debug("Trying to load class '" + lClassName + "' from classpath...");
                    }
                    lClass = Class.forName(lClassName);
                } catch (Exception ex) {
                    mLog.error(ex.getClass().getSimpleName() + " loading class from classpath: " + ex.getMessage()
                            + ", hence trying to load from jar.");
                }
                // if class could not be loaded from classpath...
                if (lClass == null) {
                    String lJarFilePath = null;
                    try {
                        lJarFilePath = JWebSocketConfig.getLibraryFolderPath(lValue);
                        if (mLog.isDebugEnabled()) {
                            mLog.debug(
                                    "Trying to load class '" + lClassName + "' from jar '" + lJarFilePath + "'...");
                        }
                        lClassLoader.addFile(lJarFilePath);
                        lClass = lClassLoader.loadClass(lClassName);
                        if (mLog.isDebugEnabled()) {
                            mLog.debug(
                                    "Class '" + lClassName + "' successfully loaded from '" + lJarFilePath + "'.");
                        }
                    } catch (Exception ex) {
                        mLog.error(ex.getClass().getSimpleName() + " loading jar '" + lJarFilePath + "': "
                                + ex.getMessage());
                    }
                }
                // could the class be loaded?
                if (lClass != null) {
                    try {
                        Object lInstance = lClass.newInstance();
                        mClasses.put(lClassName, lInstance);
                    } catch (Exception ex) {
                        mLog.error(ex.getClass().getSimpleName() + " creating '" + lClassName + "' instance : "
                                + ex.getMessage());
                    }
                }
            }
        }
        for (Entry<String, String> lSetting : lSettings.entrySet()) {
            lKey = lSetting.getKey();
            lValue = lSetting.getValue();
            if (lKey.startsWith("roles:")) {
                lKey = lKey.substring(6);
                mGrantedProcs.put(lKey, lValue);
            }
        }
        if (mLog.isDebugEnabled()) {
            mLog.debug("Available RPC classes: " + mClasses.toString());
            mLog.debug("Granted RPC methods: " + mGrantedProcs.toString());
        }
    }

    /*
    @Override
    public void connectorStarted(WebSocketConnector aConnector) {
    }
     */
    @Override
    public void processToken(PlugInResponse aResponse, WebSocketConnector aConnector, Token aToken) {
        String lType = aToken.getType();
        String lNS = aToken.getNS();

        if (lType != null && (lNS == null || lNS.equals(getNamespace()))) {
            // remote procedure call
            if (lType.equals("rpc")) {
                rpc(aConnector, aToken);
                // reverse remote procedure call
            } else if (lType.equals("rrpc")) {
                rrpc(aConnector, aToken);
            }
        }
    }

    /**
     * remote procedure call (RPC)
     * @param aConnector 
     * @param aToken
     */
    public void rpc(WebSocketConnector aConnector, Token aToken) {
        // check if user is allowed to run 'rpc' command
        if (!SecurityFactory.checkRight(getUsername(aConnector), NS_RPC_DEFAULT + ".rpc")) {
            sendToken(aConnector, aConnector, createAccessDenied(aToken));
            return;
        }

        Token lResponseToken = createResponse(aToken);

        String lClassName = aToken.getString("classname");
        String lMethod = aToken.getString("method");
        Object lArgs = aToken.get("args");
        // TODO: Tokens should always be a map of maps
        if (lArgs instanceof JSONObject) {
            lArgs = new Token((JSONObject) lArgs);
        }

        String lMsg = null;

        if (mLog.isDebugEnabled()) {
            mLog.debug("Processing RPC to class '" + lClassName + "', method '" + lMethod + "', args: '" + lArgs
                    + "'...");
        }

        String lKey = lClassName + "." + lMethod;
        if (mGrantedProcs.containsKey(lKey)) {
            // class is ignored until security restrictions are finished.
            try {
                // TODO: use RpcCallable here!
                Object lInstance = mClasses.get(lClassName);
                if (lInstance != null) {
                    Object lObj = call(lInstance, lMethod, lArgs);
                    lResponseToken.put("result", lObj);
                } else {
                    lMsg = "Class '" + lClassName + "' not found or not properly loaded.";
                }
            } catch (NoSuchMethodException ex) {
                lMsg = "NoSuchMethodException calling '" + lMethod + "' for class " + lClassName + ": "
                        + ex.getMessage();
            } catch (IllegalAccessException ex) {
                lMsg = "IllegalAccessException calling '" + lMethod + "' for class " + lClassName + ": "
                        + ex.getMessage();
            } catch (InvocationTargetException ex) {
                lMsg = "InvocationTargetException calling '" + lMethod + "' for class " + lClassName + ": "
                        + ex.getMessage();
            }
        } else {
            lMsg = "Call to " + lKey + " is not granted!";
        }
        if (lMsg != null) {
            lResponseToken.put("code", -1);
            lResponseToken.put("msg", lMsg);
        }

        /* just for testing purposes of multi-threaded rpc's
        try {
        Thread.sleep(3000);
        } catch (InterruptedException ex) {
        }
         */

        sendToken(aConnector, aConnector, lResponseToken);
    }

    /**
     * reverse remote procedure call (RRPC)
     * @param aConnector
     * @param aToken
     */
    public void rrpc(WebSocketConnector aConnector, Token aToken) {
        // check if user is allowed to run 'rrpc' command
        if (!SecurityFactory.checkRight(getUsername(aConnector), NS_RPC_DEFAULT + ".rrpc")) {
            sendToken(aConnector, aConnector, createAccessDenied(aToken));
            return;
        }

        String lNS = aToken.getNS();

        // get the target
        String lTargetId = aToken.getString("targetId");
        // get the remote classname
        String lClassname = aToken.getString("classname");
        // get the remote method name
        String lMethod = aToken.getString("method");
        // get the remote arguments
        String lArgs = aToken.getString("args");

        // TODO: find solutions for hardcoded engine id
        WebSocketConnector lTargetConnector = getServer().getConnector("tcp0", lTargetId);

        if (mLog.isDebugEnabled()) {
            mLog.debug("Processing 'rrpc'...");
        }
        if (lTargetConnector != null) {
            Token lRRPC = new Token(lNS, "rrpc");
            lRRPC.put("classname", lClassname);
            lRRPC.put("method", lMethod);
            lRRPC.put("args", lArgs);
            lRRPC.put("sourceId", aConnector.getRemotePort());

            sendToken(aConnector, lTargetConnector, lRRPC);
        } else {
            Token lResponse = createResponse(aToken);
            lResponse.put("code", -1);
            lResponse.put("error", "Target " + lTargetId + " not found.");
            sendToken(aConnector, aConnector, lResponse);
        }
    }

    /**
     *
     * @param aClassName
     * @param aURL
     * @return
     */
    public static Class loadClass(String aClassName, String aURL) {
        Class lClass = null;
        try {
            URLClassLoader lUCL = new URLClassLoader(new URL[] { new URL(aURL) });
            // load class using previously defined class loader
            lClass = Class.forName(aClassName, true, lUCL);
            if (mLog.isDebugEnabled()) {
                mLog.debug("Class '" + lClass.getName() + "' loaded!");
            }
        } catch (ClassNotFoundException ex) {
            mLog.error("Class not found exception: " + ex.getMessage());
        } catch (MalformedURLException ex) {
            mLog.error("MalformesURL exception: " + ex.getMessage());
        }
        return lClass;
    }

    /**
     *
     * @param aClassName
     * @return
     */
    public static Class loadClass(String aClassName) {
        // return loadClass(aClassName, "file:/" + JWebSocketServerConstants.CLASS_OUT_PATH);
        return null;
    }

    /**
     *
     * @param aClass
     * @param aArgs
     * @return
     */
    public static Object createInstance(Class aClass, Object[] aArgs) {
        Object lObj = null;
        try {
            Class[] lCA = new Class[aArgs != null ? aArgs.length : 0];
            for (int i = 0; i < lCA.length; i++) {
                lCA[i] = aArgs[i].getClass();
            }
            Constructor lConstructor = aClass.getConstructor(lCA);
            lObj = lConstructor.newInstance(aArgs);
            if (mLog.isDebugEnabled()) {
                mLog.debug("Object '" + aClass.getName() + "' instantiated!");
            }
        } catch (Exception ex) {
            mLog.error("Exception instantiating class " + aClass.getName() + ": " + ex.getMessage());
        }
        return lObj;
    }

    /**
     *
     * @param aClass
     * @return
     */
    public static Object createInstance(Class aClass) {
        return createInstance(aClass, null);
    }

    /**
     *
     * @param aClassName
     * @return
     */
    public static Object createInstance(String aClassName) {
        Class lClass = loadClass(aClassName);
        return createInstance(lClass, null);
    }

    /**
     *
     * @param aClassName
     * @param aArgs
     * @return
     */
    public static Object createInstance(String aClassName, Object[] aArgs) {
        Class lClass = loadClass(aClassName);
        return createInstance(lClass, aArgs);
    }

    /**
     *
     * @param aInstance
     * @param aName
     * @param aArgs
     * @return
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static Object call(Object aInstance, String aName, Object aArgs)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Object lObj = null;

        Class lClass = aInstance.getClass();
        Class[] lCA;
        if (aArgs != null) {
            lCA = new Class[] { aArgs.getClass() };
        } else {
            lCA = new Class[0];
        }
        Method lMethod = lClass.getMethod(aName, lCA);
        Object lArg = aArgs;
        lObj = lMethod.invoke(aInstance, lArg);

        return lObj;
    }

    /*
    public static Object call(Object aInstance, String aName, Object... aArgs) {
    Object lObj = null;
    try {
    Class lClass = aInstance.getClass();
    Method lMethod = lClass.getMethod(aName, new Class[]{Object.class});
    Object lArg = aArgs;
    lObj = lMethod.invoke(aInstance, lArg);
    } catch (NoSuchMethodException ex) {
    log.debug("No such method exception calling '" + aName + "' for class " + aInstance.getClass().getName() + ": " + ex.getMessage());
    } catch (Exception ex) {
    log.debug("Exception calling '" + aName + "' for class " + aInstance.getClass().getName() + ": " + ex.getMessage());
    }
    return lObj;
    }
     */
}