/*******************************************************************************
* Copyright (c) 2006-2008, Cloudsmith Inc.
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the copyright holder
* listed above, as the Initial Contributor under such license. The text of
* such license is available at www.eclipse.org.
******************************************************************************/
package org.eclipse.buckminster.p2.remote.client;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.buckminster.p2.remote.IRepositoryDataStream;
import org.eclipse.buckminster.p2.remote.IRepositoryFacade;
import org.eclipse.buckminster.p2.remote.marshall.IUMarshaller;
import org.eclipse.buckminster.p2.remote.marshall.URISerializer;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.jabsorb.JSONRPCBridge;
import org.jabsorb.JSONRPCResult;
import org.jabsorb.JSONSerializer;
import org.jabsorb.client.ErrorResponse;
import org.jabsorb.client.Session;
import org.jabsorb.serializer.AbstractSerializer;
import org.jabsorb.serializer.MarshallException;
import org.jabsorb.serializer.ObjectMatch;
import org.jabsorb.serializer.SerializerState;
import org.jabsorb.serializer.UnmarshallException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* @author Thomas Hallgren
*/
public class Client extends AbstractSerializer implements InvocationHandler
{
private static final long serialVersionUID = 6731191127498719759L;
private static Pattern s_stacktraceLine = Pattern.compile("^\\s+at\\s([A-Za-z0-9_.-]+)\\.([A-Za-z0-9_-]+)\\(([A-Za-z0-9_.-]+):([0-9]+)\\)$");
public static Client create(Session session) throws Exception
{
JSONSerializer serializer = new JSONSerializer();
serializer.registerDefaultSerializers();
Client client = new Client(session, serializer);
serializer.registerSerializer(client);
serializer.registerSerializer(new IUMarshaller());
serializer.registerSerializer(new URISerializer());
return client;
}
private final WeakHashMap<Object, JSONObject> m_proxies = new WeakHashMap<Object, JSONObject>();
private final Session m_session;
private final JSONSerializer m_serializer;
private Client(Session session, JSONSerializer serializer)
{
m_session = session;
m_serializer = serializer;
}
@SuppressWarnings("unchecked")
public Class[] getJSONClasses()
{
return new Class[] { JSONObject.class };
}
@SuppressWarnings("unchecked")
public Class[] getSerializableClasses()
{
return new Class[] { IRepositoryDataStream.class, IRepositoryFacade.class };
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
String methodName = method.getName();
if(methodName.equals("hashCode"))
{
return new Integer(System.identityHashCode(proxy));
}
else if(methodName.equals("equals"))
{
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
}
else if(methodName.equals("toString"))
{
return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());
}
return invoke(m_proxies.get(proxy), method.getName(), args, method.getReturnType());
}
public Object marshall(SerializerState state, Object p, Object o) throws MarshallException
{
JSONObject json = m_proxies.get(o);
if(json == null)
throw new MarshallException("Not a callable reference");
return json;
}
public Object openProxy(String serviceName, Class<?> cls) throws JSONException
{
Object result = Proxy.newProxyInstance(cls.getClassLoader(), new Class[] { cls }, this);
JSONObject jso = new JSONObject();
jso.put("JSONRPCType", "CallableReference");
jso.put("javaClass", cls.getName());
jso.put("objectID", serviceName);
m_proxies.put(result, jso);
return result;
}
@SuppressWarnings("unchecked")
public ObjectMatch tryUnmarshall(SerializerState state, Class clazz, Object json)
throws UnmarshallException
{
state.setSerialized(json, ObjectMatch.OKAY);
return ObjectMatch.OKAY;
}
@SuppressWarnings("unchecked")
public Object unmarshall(SerializerState state, Class clazz, Object json) throws UnmarshallException
{
JSONObject jso = (JSONObject)json;
if("CallableReference".equals(jso.opt("JSONRPCType")))
{
try
{
Class<?> cls = Class.forName(jso.getString("javaClass"));
Object result = Proxy.newProxyInstance(cls.getClassLoader(), new Class[] { cls }, this);
m_proxies.put(result, jso);
return result;
}
catch(Exception e)
{
throw new UnmarshallException(e.getMessage(), e);
}
}
throw new UnmarshallException("Not a callable reference");
}
/**
* Generate and throw exception based on the data in the 'responseMessage'
*/
protected void processException(JSONObject responseMessage) throws Exception
{
JSONObject error = (JSONObject)responseMessage.get("error");
if(error == null)
throw new ErrorResponse(new Integer(JSONRPCResult.CODE_ERR_PARSE), "Unknown response:"
+ responseMessage.toString(2), null);
Integer code = new Integer(error.has("code") ? error.getInt("code") : 0);
String trace = error.has("trace") ? error.getString("trace") : null;
String msg = error.has("msg") ? error.getString("msg") : null;
ErrorResponse er = new ErrorResponse(code, msg, trace);
if(trace == null)
throw er;
int colonIdx = trace.indexOf(':');
if(colonIdx <= 0)
throw er;
Class<? extends Exception> exClass = null;
try
{
exClass = Class.forName(trace.substring(0, colonIdx)).asSubclass(Exception.class);
}
catch(Exception e)
{
throw er;
}
Constructor<? extends Exception> exCtorMsgEx = null;
Constructor<? extends Exception> exCtorStatus = null;
Constructor<? extends Exception> exCtorMsg = null;
try
{
exCtorMsgEx = exClass.getConstructor(String.class, Throwable.class);
}
catch(Exception e)
{
try
{
exCtorStatus = exClass.getConstructor(IStatus.class);
}
catch(Exception e2)
{
try
{
exCtorMsg = exClass.getConstructor(String.class);
}
catch(Exception e3)
{
}
}
}
Exception ex = null;
if(exCtorMsgEx != null)
ex = exCtorMsgEx.newInstance(msg, er);
else if(exCtorStatus != null)
ex = exCtorStatus.newInstance(new Status(IStatus.ERROR, Activator.ID, msg, er));
else if(exCtorMsg != null)
ex = exCtorMsg.newInstance(msg);
else
throw er;
ArrayList<StackTraceElement> traces = new ArrayList<StackTraceElement>();
StringTokenizer tokens = new StringTokenizer(trace, "\n");
if(tokens.hasMoreTokens())
{
tokens.nextToken(); // Skip first line
while(tokens.hasMoreTokens())
{
Matcher m = s_stacktraceLine.matcher(tokens.nextToken());
if(!m.matches())
break;
traces.add(new StackTraceElement(m.group(1), m.group(2), m.group(3),
Integer.parseInt(m.group(4))));
}
}
ex.setStackTrace(traces.toArray(new StackTraceElement[traces.size()]));
throw ex;
}
private Object invoke(JSONObject jso, String methodName, Object[] args, Class<?> returnType)
throws Exception
{
JSONObject message = new JSONObject();
Object objId = jso.get("objectID");
String methodTag;
if(objId instanceof Integer)
methodTag = JSONRPCBridge.OBJECT_METHOD_PREFIX + '[' + objId + "].";
else
methodTag = jso.getString("objectID") + ".";
methodTag += methodName;
message.put("method", methodTag);
JSONArray params = new JSONArray();
if(args != null)
{
for(int argNo = 0; argNo < args.length; argNo++)
{
Object arg = args[argNo];
SerializerState state = new SerializerState();
params.put(m_serializer.marshall(state, /* parent */null, arg, new Integer(argNo)));
}
}
message.put("params", params);
message.put("id", 1);
JSONObject responseMessage = m_session.sendAndReceive(message);
if(!responseMessage.has("result"))
processException(responseMessage);
Object rawResult = responseMessage.get("result");
if(rawResult == null)
{
processException(responseMessage);
}
if(returnType.equals(Void.TYPE))
return null;
SerializerState state = new SerializerState();
return m_serializer.unmarshall(state, returnType, rawResult);
}
}