/*
* 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.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());
}
public int hashCode()
{
return cb.hashCode() * contextInterface.hashCode();
}
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());
}
public int hashCode()
{
return argResolver.hashCode() * argClazz.hashCode() *
contextInterface.hashCode();
}
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
{