Java tutorial
/* * JSON-RPC-Java - a JSON-RPC to Java Bridge with dynamic invocation * * $Id: JSONRPCBridge.java,v 1.37.2.5 2006/03/27 05:39:21 mclark Exp $ * * Copyright Metaparadigm Pte. Ltd. 2004. * Michael Clark <michael@metaparadigm.com> * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.metaparadigm.jsonrpc; import java.util.Map; import java.util.HashSet; import java.util.HashMap; import java.util.StringTokenizer; import java.util.ArrayList; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.logging.Logger; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.InvocationTargetException; import java.io.Serializable; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONArray; /** * This class implements a bridge that unmarshalls JSON objects in JSON-RPC * request format, invokes a method on the exported object, and then marshalls * the resulting Java objects to JSON objects in JSON-RPC result format. * <p /> * An instance of the JSONRPCBridge object is automatically placed in the * HttpSession object registered under the attribute "JSONRPCBridge" by the * JSONRPCServlet. * <p /> * The bridge is implemented as session specific to improve the security of * applications by allowing exporting of object methods only to specific * HttpSessions. * <p /> * To use the bridge to allow calling of Java methods you can easily access the * HttpSession specific bridge in JSP using the usebean tag. eg. * <p /> * <code><jsp:useBean id="JSONRPCBridge" scope="session" * class="com.metaparadigm.jsonrpc.JSONRPCBridge" /></code> * <p /> * Then export the object you wish to call methods on. eg. * <p /> * <code>JSONRPCBridge.registerObject("test", testObject);</code> * <p /> * This will make available all methods of the object as * <code>test.<methodnames></code> to JSON-RPC clients. This method * should generally be performed after an authentication check to only export * specific objects to clients that are authorised to use them. * <p /> * There exists a global bridge singleton object that will allow exporting * objects to all HTTP clients. This can be used for registering factory classes * although care must be taken with authentication as these objects will be * accessible to all clients. */ public class JSONRPCBridge implements Serializable { private final static Logger log = Logger.getLogger(JSONRPCBridge.class.getName()); private boolean debug = false; public void setDebug(boolean debug) { this.debug = debug; ser.setDebug(debug); } protected boolean isDebug() { return debug; } private static JSONRPCBridge globalBridge = new JSONRPCBridge(); /** * This method retreieves the global bridge singleton. * * It should be used with care as objects should generally be registered * within session specific bridges for security reasons. * * @return returns the global bridge object. */ public static JSONRPCBridge getGlobalBridge() { return globalBridge; } // key clazz, val ClassData private static HashMap classCache = new HashMap(); // key argClazz, val HashSet<LocalArgResolverData> private static HashMap localArgResolverMap = new HashMap(); // JSONSerializer instance for this bridge private JSONSerializer ser = new JSONSerializer(); // key CallbackData private HashSet callbackSet = new HashSet(); // key "session exported class name", val Class private HashMap classMap = new HashMap(); // key "session exported instance name", val ObjectInstance private HashMap objectMap = new HashMap(); // key Integer hashcode, object held as reference protected HashMap referenceMap = new HashMap(); // ReferenceSerializer if enabled protected Serializer referenceSer = null; // key clazz, classes that should be returned as References protected HashSet referenceSet = new HashSet(); // key clazz, classes that should be returned as CallableReferences protected HashSet callableReferenceSet = new HashSet(); private static HttpServletRequestArgResolver requestResolver = new HttpServletRequestArgResolver(); private static HttpSessionArgResolver sessionResolver = new HttpSessionArgResolver(); private static JSONRPCBridgeServletArgResolver bridgeResolver = new JSONRPCBridgeServletArgResolver(); static { registerLocalArgResolver(javax.servlet.http.HttpServletRequest.class, javax.servlet.http.HttpServletRequest.class, requestResolver); registerLocalArgResolver(javax.servlet.http.HttpSession.class, javax.servlet.http.HttpServletRequest.class, sessionResolver); registerLocalArgResolver(JSONRPCBridge.class, javax.servlet.http.HttpServletRequest.class, bridgeResolver); } public JSONRPCBridge() { this(true); } public JSONRPCBridge(boolean useDefaultSerializers) { if (useDefaultSerializers) { try { ser.registerDefaultSerializers(); } catch (Exception e) { e.printStackTrace(); } } } protected static class ClassData { private Class clazz; // key methodKey, val (Method || Method[]) private HashMap methodMap; // key methodKey, val (Method || Method[]) private HashMap staticMethodMap; } protected static class CallbackData implements Serializable { private InvocationCallback cb; private Class contextInterface; public CallbackData(InvocationCallback cb, Class contextInterface) { this.cb = cb; this.contextInterface = contextInterface; } public boolean understands(Object context) { return contextInterface.isAssignableFrom(context.getClass()); } @Override public int hashCode() { return cb.hashCode() * contextInterface.hashCode(); } @Override public boolean equals(Object o) { CallbackData cmp = (CallbackData) o; return (cb.equals(cmp.cb) && contextInterface.equals(cmp.contextInterface)); } } protected static class LocalArgResolverData { private LocalArgResolver argResolver; private Class argClazz; private Class contextInterface; public LocalArgResolverData(LocalArgResolver argResolver, Class argClazz, Class contextInterface) { this.argResolver = argResolver; this.argClazz = argClazz; this.contextInterface = contextInterface; } public boolean understands(Object context) { return contextInterface.isAssignableFrom(context.getClass()); } @Override public int hashCode() { return argResolver.hashCode() * argClazz.hashCode() * contextInterface.hashCode(); } @Override public boolean equals(Object o) { LocalArgResolverData cmp = (LocalArgResolverData) o; return (argResolver.equals(cmp.argResolver) && argClazz.equals(cmp.argClazz) && contextInterface.equals(cmp.contextInterface)); } } protected static class ObjectInstance implements Serializable { private Object o; public ObjectInstance(Object o, Class clazz) { if (!clazz.isInstance(o)) { throw new ClassCastException("Attempt to register jsonrpc object with invalid class."); } this.o = o; this.clazz = clazz; } private Class clazz; public ObjectInstance(Object o) { this.o = o; clazz = o.getClass(); } public ClassData classData() { return getClassData(clazz); } } protected static class MethodKey { private String methodName; private int numArgs; public MethodKey(String methodName, int numArgs) { this.methodName = methodName; this.numArgs = numArgs; } @Override public int hashCode() { return methodName.hashCode() * numArgs; } @Override public boolean equals(Object o) { if (!(o instanceof MethodKey)) return false; return (methodName.equals(((MethodKey) o).methodName) && numArgs == ((MethodKey) o).numArgs); } } protected static class MethodCandidate { private Method method; private ObjectMatch match[]; public MethodCandidate(Method method) { this.method = method; match = new ObjectMatch[method.getParameterTypes().length]; } public ObjectMatch getMatch() { int mismatch = -1; for (int i = 0; i < match.length; i++) { mismatch = Math.max(mismatch, match[i].mismatch); } if (mismatch == -1) return ObjectMatch.OKAY; else return new ObjectMatch(mismatch); } } private static ClassData analyzeClass(Class clazz) { log.info("analyzing " + clazz.getName()); Method methods[] = clazz.getMethods(); ClassData cd = new ClassData(); cd.clazz = clazz; // Create temporary method map HashMap staticMethodMap = new HashMap(); HashMap methodMap = new HashMap(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; if (method.getDeclaringClass() == Object.class) continue; int mod = methods[i].getModifiers(); if (!Modifier.isPublic(mod)) continue; Class param[] = method.getParameterTypes(); // don't count locally resolved args int argCount = 0; synchronized (localArgResolverMap) { for (int n = 0; n < param.length; n++) { HashSet resolvers = (HashSet) localArgResolverMap.get(param[n]); if (resolvers != null) continue; argCount++; } } MethodKey mk = new MethodKey(method.getName(), argCount); ArrayList marr = (ArrayList) methodMap.get(mk); if (marr == null) { marr = new ArrayList(); methodMap.put(mk, marr); } marr.add(method); if (Modifier.isStatic(mod)) { marr = (ArrayList) staticMethodMap.get(mk); if (marr == null) { marr = new ArrayList(); staticMethodMap.put(mk, marr); } marr.add(method); } } cd.methodMap = new HashMap(); cd.staticMethodMap = new HashMap(); // Convert ArrayLists to arrays Iterator i = methodMap.entrySet().iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry) i.next(); MethodKey mk = (MethodKey) entry.getKey(); ArrayList marr = (ArrayList) entry.getValue(); if (marr.size() == 1) { cd.methodMap.put(mk, marr.get(0)); } else { cd.methodMap.put(mk, marr.toArray(new Method[0])); } } i = staticMethodMap.entrySet().iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry) i.next(); MethodKey mk = (MethodKey) entry.getKey(); ArrayList marr = (ArrayList) entry.getValue(); if (marr.size() == 1) { cd.staticMethodMap.put(mk, marr.get(0)); } else { cd.staticMethodMap.put(mk, marr.toArray(new Method[0])); } } return cd; } private static ClassData getClassData(Class clazz) { ClassData cd; synchronized (classCache) { cd = (ClassData) classCache.get(clazz); if (cd == null) { cd = analyzeClass(clazz); classCache.put(clazz, cd); } } return cd; } public void registerSerializer(Serializer s) throws Exception { ser.registerSerializer(s); } public void enableReferences() throws Exception { if (referenceSer == null) { referenceSer = new ReferenceSerializer(this); ser.registerSerializer(referenceSer); } } /** * Registers a class to be returned by reference and not by value as is done * by default. * <p /> * The JSONBridge will take a references to these objects and return an * opaque object to the JSON-RPC client. When the opaque object is passed * back through the bridge in subsequent calls, the original object is * substitued in calls to Java methods. This should be used for any objects * that contain security information or complex types that are not required * in the Javascript client but need to be passed as a reference in methods * of exported objects. * <p /> * A Reference in JSON format looks like this: * <p /> * <code>{ "javaClass":"com.metaparadigm.test.Foo",<br /> * "objectID":5535614,<br /> * "JSONRPCType":"Reference" }</code> * * @param clazz * The class object that should be marshalled as a reference. */ public void registerReference(Class clazz) throws Exception { synchronized (referenceSet) { referenceSet.add(clazz); } if (debug) log.info("registered reference " + clazz.getName()); } protected boolean isReference(Class clazz) { if (referenceSet.contains(clazz)) return true; if (this == globalBridge) return false; return globalBridge.isReference(clazz); } /** * Registers a class to be returned as a callable reference. * * The JSONBridge will return a callable reference to the JSON-RPC client * for registered classes instead of passing them by value. The JSONBridge * will take a references to these objects and the JSON-RPC client will * create an invocation proxy for objects of this class for which methods * will be called on the instance on the server. * <p /> * <b>Note:</b> A limitation exists in the JSON-RPC client where only the * top most object returned from a method can be made into a proxy. * <p /> * A Callable Reference in JSON format looks like this: * <p /> * <code>{ "javaClass":"com.metaparadigm.test.Bar",<br /> * "objectID":4827452,<br /> * "JSONRPCType":"CallableReference" }</code> * * @param clazz * The class object that should be marshalled as a callable * reference. */ public void registerCallableReference(Class clazz) throws Exception { synchronized (callableReferenceSet) { callableReferenceSet.add(clazz); } if (debug) log.info("registered callable reference " + clazz.getName()); } protected boolean isCallableReference(Class clazz) { if (callableReferenceSet.contains(clazz)) return true; if (this == globalBridge) return false; return globalBridge.isCallableReference(clazz); } /** * Registers a class to export static methods. * * The JSONBridge will export all static methods of the class. This is * useful for exporting factory classes that may then return * CallableReferences to the JSON-RPC client. * <p /> * To export instance methods you need to use registerObject. * * @param name * The named prefix to export the class as * @param clazz * The class to export static methods from. */ public void registerClass(String name, Class clazz) throws Exception { synchronized (classMap) { Class exists = (Class) classMap.get(name); if (exists != null && exists != clazz) throw new Exception("different class registered as " + name); if (exists == null) classMap.put(name, clazz); } if (debug) log.info("registered class " + clazz.getName() + " as " + name); } /** * Unregisters a class exported with registerClass. * * The JSONBridge will unexport all static methods of the class. * * @param name * The registered name of the class to unexport static methods * from. */ public void unregisterClass(String name) throws Exception { Class clazz = null; synchronized (classMap) { clazz = (Class) classMap.get(name); if (clazz != null) { classMap.remove(name); if (debug) log.info("unregistered class " + clazz.getName() + " from " + name); } } } /** * Lookup a class that is registered with this bridge. * * @param name * The registered name of the class to lookup. */ public Class lookupClass(String name) throws Exception { synchronized (classMap) { return (Class) classMap.get(name); } } protected ClassData resolveClass(String className) { Class clazz = null; ClassData cd = null; synchronized (classMap) { clazz = (Class) classMap.get(className); } if (clazz != null) cd = getClassData(clazz); if (cd != null) { if (debug) log.fine("found class " + cd.clazz.getName() + " named " + className); return cd; } if (this != globalBridge) return globalBridge.resolveClass(className); return null; } /** * Registers an object to export all instance methods and static methods. * * The JSONBridge will export all instance methods and static methods of the * particular object under the name passed in as a key. * <p /> * This will make available all methods of the object as * <code><key>.<methodnames></code> to JSON-RPC clients. * * @param key * The named prefix to export the object as * @param o * The object instance to be called upon */ public void registerObject(Object key, Object o) { Class clazz = o.getClass(); ObjectInstance oi = new ObjectInstance(o); synchronized (objectMap) { objectMap.put(key, oi); } if (debug) log.info("registered object " + o.hashCode() + " of class " + clazz.getName() + " as " + key); } /** * Registers an object to export all instance methods defined by * interfaceClass. * * The JSONBridge will export all instance methods defined by interfaceClass * of the particular object under the name passed in as a key. * * This will make available these methods of the object as * <code><key>.<methodnames></code> to JSON-RPC clients. * * @param key * The named prefix to export the object as * @param o * The object instance to be called upon * @param interfaceClass * The type that this object should be registered as. * * This can be used to restrict the exported methods to the methods defined * in a specific superclass or interface. */ public void registerObject(Object key, Object o, Class interfaceClass) { ObjectInstance inst = new ObjectInstance(o, interfaceClass); synchronized (objectMap) { objectMap.put(key, inst); } if (debug) { log.info("registered object " + o.hashCode() + " of class " + interfaceClass.getName() + " as " + key); } } /** * Unregisters an object exported with registerObject. * * The JSONBridge will unexport all instance methods and static methods of * the particular object under the name passed in as a key. * * @param key * The named prefix of the object to unexport */ public void unregisterObject(Object key) { ObjectInstance oi = null; synchronized (objectMap) { oi = (ObjectInstance) objectMap.get(key); if (oi != null) { objectMap.remove(key); Class clazz = oi.o.getClass(); if (debug) log.info("unregistered object " + oi.o.hashCode() + " of class " + clazz.getName() + " from " + key); } } } /** * Lookup an object that is registered with this bridge. * * @param key * The registered name of the object to lookup. */ public Object lookupObject(String key) throws Exception { synchronized (objectMap) { ObjectInstance oi = (ObjectInstance) objectMap.get(key); if (oi != null) return oi.o; } return null; } private ObjectInstance resolveObject(Object key) { ObjectInstance oi; synchronized (objectMap) { oi = (ObjectInstance) objectMap.get(key); } if (debug && oi != null) log.fine("found object " + oi.o.hashCode() + " of class " + oi.classData().clazz.getName() + " with key " + key); if (oi == null && this != globalBridge) return globalBridge.resolveObject(key); else return oi; } /** * Registers a callback to be called before and after method invocation * * @param callback * The object implementing the InvocationCallback Interface * @param contextInterface * The type of transport Context interface the callback is * interested in eg. HttpServletRequest.class for the servlet * transport. */ public void registerCallback(InvocationCallback callback, Class contextInterface) { synchronized (callbackSet) { callbackSet.add(new CallbackData(callback, contextInterface)); } if (debug) log.info("registered callback " + callback.getClass().getName() + " with context interface " + contextInterface.getName()); } /** * Unregisters a callback * * @param callback * The previously registered InvocationCallback object * @param contextInterface * The previously registered transport Context interface. */ public void unregisterCallback(InvocationCallback callback, Class contextInterface) { synchronized (callbackSet) { callbackSet.remove(new CallbackData(callback, contextInterface)); } if (debug) log.info("unregistered callback " + callback.getClass().getName() + " with context " + contextInterface.getName()); } /** * Registers a Class to be removed from the exported method signatures and * instead be resolved locally using context information from the transport. * * @param argClazz * The class to be resolved locally * @param argResolver * The user defined class that resolves the and returns the * method argument using transport context information * @param contextInterface * The type of transport Context object the callback is * interested in eg. HttpServletRequest.class for the servlet * transport */ public static void registerLocalArgResolver(Class argClazz, Class contextInterface, LocalArgResolver argResolver) { synchronized (localArgResolverMap) { HashSet resolverSet = (HashSet) localArgResolverMap.get(argClazz); if (resolverSet == null) { resolverSet = new HashSet(); localArgResolverMap.put(argClazz, resolverSet); } resolverSet.add(new LocalArgResolverData(argResolver, argClazz, contextInterface)); classCache = new HashMap(); // invalidate class cache } log.info("registered local arg resolver " + argResolver.getClass().getName() + " for local class " + argClazz.getName() + " with context " + contextInterface.getName()); } /** * Unregisters a LocalArgResolver</b>. * * @param argClazz * The previously registered local class * @param argResolver * The previously registered LocalArgResolver object * @param contextInterface * The previously registered transport Context interface. */ public static void unregisterLocalArgResolver(Class argClazz, Class contextInterface, LocalArgResolver argResolver) { synchronized (localArgResolverMap) { HashSet resolverSet = (HashSet) localArgResolverMap.get(argClazz); if (resolverSet == null || !resolverSet.remove(new LocalArgResolverData(argResolver, argClazz, contextInterface))) { log.warning("local arg resolver " + argResolver.getClass().getName() + " not registered for local class " + argClazz.getName() + " with context " + contextInterface.getName()); return; } if (resolverSet.isEmpty()) localArgResolverMap.remove(argClazz); classCache = new HashMap(); // invalidate class cache } log.info("unregistered local arg resolver " + argResolver.getClass().getName() + " for local class " + argClazz.getName() + " with context " + contextInterface.getName()); } private Method resolveMethod(HashMap methodMap, String methodName, JSONArray arguments) { Method method[] = null; MethodKey mk = new MethodKey(methodName, arguments.length()); Object o = methodMap.get(mk); if (o instanceof Method) { Method m = (Method) o; if (debug) log.fine("found method " + methodName + "(" + argSignature(m) + ")"); return m; } else if (o instanceof Method[]) method = (Method[]) o; else return null; ArrayList candidate = new ArrayList(); if (debug) log.fine("looking for method " + methodName + "(" + argSignature(arguments) + ")"); for (int i = 0; i < method.length; i++) { try { candidate.add(tryUnmarshallArgs(method[i], arguments)); if (debug) log.fine("+++ possible match with method " + methodName + "(" + argSignature(method[i]) + ")"); } catch (Exception e) { if (debug) log.fine("xxx " + e.getMessage() + " in " + methodName + "(" + argSignature(method[i]) + ")"); } } MethodCandidate best = null; for (int i = 0; i < candidate.size(); i++) { MethodCandidate c = (MethodCandidate) candidate.get(i); if (best == null) { best = c; continue; } final ObjectMatch bestMatch = best.getMatch(); final ObjectMatch cMatch = c.getMatch(); if (bestMatch.mismatch > cMatch.mismatch) best = c; else if (bestMatch.mismatch == cMatch.mismatch) best = betterSignature(best, c); } if (best != null) { Method m = best.method; if (debug) log.fine("found method " + methodName + "(" + argSignature(m) + ")"); return m; } return null; } private MethodCandidate betterSignature(MethodCandidate methodCandidate, MethodCandidate methodCandidate1) { final Method method = methodCandidate.method; final Method method1 = methodCandidate1.method; final Class[] parameters = method.getParameterTypes(); final Class[] parameters1 = method1.getParameterTypes(); int c = 0, c1 = 0; for (int i = 0; i < parameters.length; i++) { final Class parameterClass = parameters[i]; final Class parameterClass1 = parameters1[i]; if (parameterClass != parameterClass1) { if (parameterClass.isAssignableFrom(parameterClass1)) c1++; else c++; } } if (c1 > c) return methodCandidate1; else return methodCandidate; } private static String argSignature(Method method) { Class param[] = method.getParameterTypes(); StringBuffer buf = new StringBuffer(); for (int i = 0; i < param.length; i++) { if (i > 0) buf.append(","); buf.append(param[i].getName()); } return buf.toString(); } private static String argSignature(JSONArray arguments) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < arguments.length(); i += 1) { if (i > 0) buf.append(","); Object jso = null; try { jso = arguments.get(i); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (jso == null) { buf.append("java.lang.Object"); } else if (jso instanceof String) { buf.append("java.lang.String"); } else if (jso instanceof Number) { buf.append("java.lang.Number"); } else if (jso instanceof JSONArray) { buf.append("java.lang.Object[]"); } else { buf.append("java.lang.Object"); } } return buf.toString(); } private MethodCandidate tryUnmarshallArgs(Method method, JSONArray arguments) throws UnmarshallException { MethodCandidate candidate = new MethodCandidate(method); Class param[] = method.getParameterTypes(); int i = 0, j = 0; HashSet resolverSet; try { for (; i < param.length; i++) { SerializerState state = new SerializerState(); synchronized (localArgResolverMap) { resolverSet = (HashSet) localArgResolverMap.get(param[i]); } if (resolverSet != null) candidate.match[i] = ObjectMatch.OKAY; else candidate.match[i] = ser.tryUnmarshall(state, param[i], arguments.get(j++)); } } catch (UnmarshallException e) { throw new UnmarshallException("arg " + (i + 1) + " " + e.getMessage()); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } return candidate; } private Object resolveLocalArg(Object context[], HashSet resolverSet) throws UnmarshallException { Iterator i = resolverSet.iterator(); while (i.hasNext()) { LocalArgResolverData resolverData = (LocalArgResolverData) i.next(); for (int j = 0; j < context.length; j++) { if (resolverData.understands(context[j])) { try { return resolverData.argResolver.resolveArg(context[j]); } catch (LocalArgResolveException e) { throw new UnmarshallException("error resolving local argument: " + e); } } } } throw new UnmarshallException("couldn't find local arg resolver"); } private Object[] unmarshallArgs(Object context[], Method method, JSONArray arguments) throws UnmarshallException { Class param[] = method.getParameterTypes(); Object javaArgs[] = new Object[param.length]; int i = 0, j = 0; HashSet resolverSet; try { for (; i < param.length; i++) { SerializerState state = new SerializerState(); synchronized (localArgResolverMap) { resolverSet = (HashSet) localArgResolverMap.get(param[i]); } if (resolverSet != null) javaArgs[i] = resolveLocalArg(context, resolverSet); else javaArgs[i] = ser.unmarshall(state, param[i], arguments.get(j++)); } } catch (UnmarshallException e) { throw new UnmarshallException("arg " + (i + 1) + " " + e.getMessage()); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } return javaArgs; } private void preInvokeCallback(Object context, Object instance, Method m, Object arguments[]) throws Exception { synchronized (callbackSet) { Iterator i = callbackSet.iterator(); while (i.hasNext()) { CallbackData cbdata = (CallbackData) i.next(); if (cbdata.understands(context)) cbdata.cb.preInvoke(context, instance, m, arguments); } } } private void postInvokeCallback(Object context, Object instance, Method m, Object result) throws Exception { synchronized (callbackSet) { Iterator i = callbackSet.iterator(); while (i.hasNext()) { CallbackData cbdata = (CallbackData) i.next(); if (cbdata.understands(context)) cbdata.cb.postInvoke(context, instance, m, result); } } } public JSONRPCResult call(Object context[], JSONObject jsonReq) { JSONRPCResult result = null; String encodedMethod = null; Object requestId = null; JSONArray arguments = null; try { // Get method name, arguments and request id encodedMethod = jsonReq.getString("method"); arguments = jsonReq.getJSONArray("params"); requestId = jsonReq.opt("id"); } catch (NoSuchElementException e) { log.severe("no method or parameters in request"); return new JSONRPCResult(JSONRPCResult.CODE_ERR_NOMETHOD, null, JSONRPCResult.MSG_ERR_NOMETHOD); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (isDebug()) log.fine("call " + encodedMethod + "(" + arguments + ")" + ", requestId=" + requestId); String className = null; String methodName = null; int objectID = 0; // Parse the class and methodName StringTokenizer t = new StringTokenizer(encodedMethod, "."); if (t.hasMoreElements()) className = t.nextToken(); if (t.hasMoreElements()) methodName = t.nextToken(); // See if we have an object method in the format ".obj#<objectID>" if (encodedMethod.startsWith(".obj#")) { t = new StringTokenizer(className, "#"); t.nextToken(); objectID = Integer.parseInt(t.nextToken()); } ObjectInstance oi = null; ClassData cd = null; HashMap methodMap = null; Method method = null; Object itsThis = null; if (objectID == 0) { // Handle "system.listMethods" if (encodedMethod.equals("system.listMethods")) { HashSet m = new HashSet(); globalBridge.allInstanceMethods(m); if (globalBridge != this) { globalBridge.allStaticMethods(m); globalBridge.allInstanceMethods(m); } allStaticMethods(m); allInstanceMethods(m); JSONArray methods = new JSONArray(); Iterator i = m.iterator(); while (i.hasNext()) methods.put(i.next()); return new JSONRPCResult(JSONRPCResult.CODE_SUCCESS, requestId, methods); } // Look up the class, object instance and method objects if (className == null || methodName == null || ((oi = resolveObject(className)) == null && (cd = resolveClass(className)) == null)) return new JSONRPCResult(JSONRPCResult.CODE_ERR_NOMETHOD, requestId, JSONRPCResult.MSG_ERR_NOMETHOD); if (oi != null) { itsThis = oi.o; methodMap = oi.classData().methodMap; } else { methodMap = cd.staticMethodMap; } } else { if ((oi = resolveObject(new Integer(objectID))) == null) return new JSONRPCResult(JSONRPCResult.CODE_ERR_NOMETHOD, requestId, JSONRPCResult.MSG_ERR_NOMETHOD); itsThis = oi.o; methodMap = oi.classData().methodMap; // Handle "system.listMethods" if (methodName.equals("listMethods")) { HashSet m = new HashSet(); uniqueMethods(m, "", oi.classData().staticMethodMap); uniqueMethods(m, "", oi.classData().methodMap); JSONArray methods = new JSONArray(); Iterator i = m.iterator(); while (i.hasNext()) methods.put(i.next()); return new JSONRPCResult(JSONRPCResult.CODE_SUCCESS, requestId, methods); } } if ((method = resolveMethod(methodMap, methodName, arguments)) == null) return new JSONRPCResult(JSONRPCResult.CODE_ERR_NOMETHOD, requestId, JSONRPCResult.MSG_ERR_NOMETHOD); // Call the method try { if (debug) log.fine("invoking " + method.getReturnType().getName() + " " + method.getName() + "(" + argSignature(method) + ")"); Object javaArgs[] = unmarshallArgs(context, method, arguments); for (int i = 0; i < context.length; i++) preInvokeCallback(context[i], itsThis, method, javaArgs); Object o = method.invoke(itsThis, javaArgs); for (int i = 0; i < context.length; i++) postInvokeCallback(context[i], itsThis, method, o); SerializerState state = new SerializerState(); result = new JSONRPCResult(JSONRPCResult.CODE_SUCCESS, requestId, ser.marshall(state, o)); } catch (UnmarshallException e) { for (int i = 0; i < context.length; i++) errorCallback(context[i], itsThis, method, e); result = new JSONRPCResult(JSONRPCResult.CODE_ERR_UNMARSHALL, requestId, e.getMessage()); } catch (MarshallException e) { for (int i = 0; i < context.length; i++) errorCallback(context[i], itsThis, method, e); result = new JSONRPCResult(JSONRPCResult.CODE_ERR_MARSHALL, requestId, e.getMessage()); } catch (Throwable e) { if (e instanceof InvocationTargetException) e = ((InvocationTargetException) e).getTargetException(); for (int i = 0; i < context.length; i++) errorCallback(context[i], itsThis, method, e); result = new JSONRPCResult(JSONRPCResult.CODE_REMOTE_EXCEPTION, requestId, e); } // Return the results return result; } /** * Calls the 'invocation Error' callback handler. * * @param context * The transport context (the HttpServletRequest object in the * case of the HTTP transport). * @param instance * The object instance or null if it is a static method. * @param method * Method that failed the invocation. * @param error * Error resulting from the invocation. * @author connorb (Axxia) */ private void errorCallback(Object context, Object instance, Method method, Throwable error) { synchronized (callbackSet) { Iterator i = callbackSet.iterator(); while (i.hasNext()) { CallbackData cbdata = (CallbackData) i.next(); if (cbdata.understands(context) && (cbdata.cb instanceof ErrorInvocationCallback)) { ErrorInvocationCallback ecb = (ErrorInvocationCallback) cbdata.cb; try { ecb.invocationError(context, instance, method, error); } catch (Throwable th) { // Ignore all errors in callback, don't want // event listener to bring everything to its knees. } } } } } private void uniqueMethods(HashSet m, String prefix, HashMap methodMap) { Iterator i = methodMap.entrySet().iterator(); while (i.hasNext()) { Map.Entry mentry = (Map.Entry) i.next(); MethodKey mk = (MethodKey) mentry.getKey(); m.add(prefix + mk.methodName); } } private void allStaticMethods(HashSet m) { synchronized (classMap) { Iterator i = classMap.entrySet().iterator(); while (i.hasNext()) { Map.Entry cdentry = (Map.Entry) i.next(); String name = (String) cdentry.getKey(); Class clazz = (Class) cdentry.getValue(); ClassData cd = getClassData(clazz); uniqueMethods(m, name + ".", cd.staticMethodMap); } } } private void allInstanceMethods(HashSet m) { synchronized (objectMap) { Iterator i = objectMap.entrySet().iterator(); while (i.hasNext()) { Map.Entry oientry = (Map.Entry) i.next(); Object key = oientry.getKey(); if (!(key instanceof String)) continue; String name = (String) key; ObjectInstance oi = (ObjectInstance) oientry.getValue(); uniqueMethods(m, name + ".", oi.classData().methodMap); uniqueMethods(m, name + ".", oi.classData().staticMethodMap); } } } public JSONSerializer getSerializer() { return ser; } public void setSerializer(JSONSerializer ser) { this.ser = ser; } }