wsky / top-link

embedded duplex multi-channel endpoint and connection management for c#/java/...
6 stars 1 forks source link

how to cross-language? add json serialization support to remoting #43

Closed wsky closed 11 years ago

wsky commented 11 years ago

need support multilanguage

wsky commented 11 years ago

remoting sink design is necessarily in next version, channel property passing some protocol info that making something special for different language

wsky commented 11 years ago

about MethodReturn.Exception

public class MethodReturn implements Message {
    public Object ReturnValue;
    public Throwable Exception;
}

exception not friendly for cross-language,

java have it's own format:

{"Exception":{"@type":"com.taobao.top.link.LinkException","cause":{"@type":"java.lang.NullPointerException","stackTrace":[{"className":"com.taobao.top.link.remoting.JsonSerializerTest","fileName":"JsonSerializerTest.java","lineNumber":46,"methodName":"methodReturn_test","nativeMethod":false},{"className":"sun.reflect.NativeMethodAccessorImpl","lineNumber":-2,"methodName":"invoke0","nativeMethod":true},{"className":"sun.reflect.NativeMethodAccessorImpl","lineNumber":-1,"methodName":"invoke","nativeMethod":false},{"className":"sun.reflect.DelegatingMethodAccessorImpl","lineNumber":-1,"methodName":"invoke","nativeMethod":false},{"className":"java.lang.reflect.Method","lineNumber":-1,"methodName":"invoke","nativeMethod":false},{"className":"org.junit.runners.model.FrameworkMethod$1","fileName":"FrameworkMethod.java","lineNumber":44,"methodName":"runReflectiveCall","nativeMethod":false},{"className":"org.junit.internal.runners.model.ReflectiveCallable","fileName":"ReflectiveCallable.java","lineNumber":15,"methodName":"run","nativeMethod":false},{"className":"org.junit.runners.model.FrameworkMethod","fileName":"FrameworkMethod.java","lineNumber":41,"methodName":"invokeExplosively","nativeMethod":false},{"className":"org.junit.internal.runners.statements.InvokeMethod","fileName":"InvokeMethod.java","lineNumber":20,"methodName":"evaluate","nativeMethod":false},{"className":"org.junit.runners.BlockJUnit4ClassRunner","fileName":"BlockJUnit4ClassRunner.java","lineNumber":79,"methodName":"runNotIgnored","nativeMethod":false},{"className":"org.junit.runners.BlockJUnit4ClassRunner","fileName":"BlockJUnit4ClassRunner.java","lineNumber":71,"methodName":"runChild","nativeMethod":false},{"className":"org.junit.runners.BlockJUnit4ClassRunner","fileName":"BlockJUnit4ClassRunner.java","lineNumber":49,"methodName":"runChild","nativeMethod":false},{"className":"org.junit.runners.ParentRunner$3","fileName":"ParentRunner.java","lineNumber":193,"methodName":"run","nativeMethod":false},{"className":"org.junit.runners.ParentRunner$1","fileName":"ParentRunner.java","lineNumber":52,"methodName":"schedule","nativeMethod":false},{"className":"org.junit.runners.ParentRunner","fileName":"ParentRunner.java","lineNumber":191,"methodName":"runChildren","nativeMethod":false},{"className":"org.junit.runners.ParentRunner","fileName":"ParentRunner.java","lineNumber":42,"methodName":"access$000","nativeMethod":false},{"className":"org.junit.runners.ParentRunner$2","fileName":"ParentRunner.java","lineNumber":184,"methodName":"evaluate","nativeMethod":false},{"className":"org.junit.runners.ParentRunner","fileName":"ParentRunner.java","lineNumber":236,"methodName":"run","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference","fileName":"JUnit4TestReference.java","lineNumber":50,"methodName":"run","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit.runner.TestExecution","fileName":"TestExecution.java","lineNumber":38,"methodName":"run","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit.runner.RemoteTestRunner","fileName":"RemoteTestRunner.java","lineNumber":467,"methodName":"runTests","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit.runner.RemoteTestRunner","fileName":"RemoteTestRunner.java","lineNumber":683,"methodName":"runTests","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit.runner.RemoteTestRunner","fileName":"RemoteTestRunner.java","lineNumber":390,"methodName":"run","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit.runner.RemoteTestRunner","fileName":"RemoteTestRunner.java","lineNumber":197,"methodName":"main","nativeMethod":false}]},"errorCode":0,"localizedMessage":"error","message":"error","stackTrace":[{"className":"com.taobao.top.link.remoting.JsonSerializerTest","fileName":"JsonSerializerTest.java","lineNumber":46,"methodName":"methodReturn_test","nativeMethod":false},{"className":"sun.reflect.NativeMethodAccessorImpl","lineNumber":-2,"methodName":"invoke0","nativeMethod":true},{"className":"sun.reflect.NativeMethodAccessorImpl","lineNumber":-1,"methodName":"invoke","nativeMethod":false},{"className":"sun.reflect.DelegatingMethodAccessorImpl","lineNumber":-1,"methodName":"invoke","nativeMethod":false},{"className":"java.lang.reflect.Method","lineNumber":-1,"methodName":"invoke","nativeMethod":false},{"className":"org.junit.runners.model.FrameworkMethod$1","fileName":"FrameworkMethod.java","lineNumber":44,"methodName":"runReflectiveCall","nativeMethod":false},{"className":"org.junit.internal.runners.model.ReflectiveCallable","fileName":"ReflectiveCallable.java","lineNumber":15,"methodName":"run","nativeMethod":false},{"className":"org.junit.runners.model.FrameworkMethod","fileName":"FrameworkMethod.java","lineNumber":41,"methodName":"invokeExplosively","nativeMethod":false},{"className":"org.junit.internal.runners.statements.InvokeMethod","fileName":"InvokeMethod.java","lineNumber":20,"methodName":"evaluate","nativeMethod":false},{"className":"org.junit.runners.BlockJUnit4ClassRunner","fileName":"BlockJUnit4ClassRunner.java","lineNumber":79,"methodName":"runNotIgnored","nativeMethod":false},{"className":"org.junit.runners.BlockJUnit4ClassRunner","fileName":"BlockJUnit4ClassRunner.java","lineNumber":71,"methodName":"runChild","nativeMethod":false},{"className":"org.junit.runners.BlockJUnit4ClassRunner","fileName":"BlockJUnit4ClassRunner.java","lineNumber":49,"methodName":"runChild","nativeMethod":false},{"className":"org.junit.runners.ParentRunner$3","fileName":"ParentRunner.java","lineNumber":193,"methodName":"run","nativeMethod":false},{"className":"org.junit.runners.ParentRunner$1","fileName":"ParentRunner.java","lineNumber":52,"methodName":"schedule","nativeMethod":false},{"className":"org.junit.runners.ParentRunner","fileName":"ParentRunner.java","lineNumber":191,"methodName":"runChildren","nativeMethod":false},{"className":"org.junit.runners.ParentRunner","fileName":"ParentRunner.java","lineNumber":42,"methodName":"access$000","nativeMethod":false},{"className":"org.junit.runners.ParentRunner$2","fileName":"ParentRunner.java","lineNumber":184,"methodName":"evaluate","nativeMethod":false},{"className":"org.junit.runners.ParentRunner","fileName":"ParentRunner.java","lineNumber":236,"methodName":"run","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference","fileName":"JUnit4TestReference.java","lineNumber":50,"methodName":"run","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit.runner.TestExecution","fileName":"TestExecution.java","lineNumber":38,"methodName":"run","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit.runner.RemoteTestRunner","fileName":"RemoteTestRunner.java","lineNumber":467,"methodName":"runTests","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit.runner.RemoteTestRunner","fileName":"RemoteTestRunner.java","lineNumber":683,"methodName":"runTests","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit.runner.RemoteTestRunner","fileName":"RemoteTestRunner.java","lineNumber":390,"methodName":"run","nativeMethod":false},{"className":"org.eclipse.jdt.internal.junit.runner.RemoteTestRunner","fileName":"RemoteTestRunner.java","lineNumber":197,"methodName":"main","nativeMethod":false}]},"ReturnType":"com.taobao.top.link.remoting.MethodReturn","ReturnValue":"{\"ReturnValue\":\"abc\"}"}

but in .net or other not, so just pass error stack string, and typeless exception

wsky commented 11 years ago

following impl just works in java, maybe can hack by protocol special language, like .NET/Java

@Override
    public byte[] serializeMethodReturn(MethodReturn methodReturn) throws FormatterException {
        MethodReturnWrapper wrapper = new MethodReturnWrapper();
        if (methodReturn.ReturnValue != null) {
            wrapper.ReturnValue = JSON.toJSONString(methodReturn.ReturnValue);
            wrapper.ReturnType = methodReturn.ReturnValue.getClass().getName();
        }
        if (methodReturn.Exception != null) {
            wrapper.Exception = JSON.toJSONString(methodReturn.Exception);
            wrapper.ExceptionType = methodReturn.Exception.getClass().getName();
        }
        return JSON.toJSONBytes(wrapper);
    }
@Override
    public MethodReturn deserializeMethodReturn(byte[] input) throws FormatterException {
        MethodReturnWrapper wrapper = JSON.parseObject(input, MethodReturnWrapper.class);
        MethodReturn methodReturn = new MethodReturn();
        if (wrapper.ReturnValue != null && wrapper.ReturnType != null) {
            try {
                methodReturn.ReturnValue = JSON.parseObject(wrapper.ReturnValue,
                        Class.forName(wrapper.ReturnType, false, this.getClass().getClassLoader()));
            } catch (ClassNotFoundException e) {
                throw new FormatterException("parse ReturnValue error", e);
            }
        }
        if (wrapper.Exception != null && wrapper.ExceptionType != null) {
            try {
                methodReturn.Exception = (Throwable) JSON.parseObject(wrapper.Exception,
                        Class.forName(wrapper.ExceptionType, false, this.getClass().getClassLoader()));
            } catch (ClassNotFoundException e) {
                throw new FormatterException("parse Exception error", e);
            }
        }
        return methodReturn;
    }
wsky commented 11 years ago

base type like int/long/... should be given a common name for cross-language, special type need thought about

private String parseTypeName(Class<?> type) {
        if (String.class.equals(type))
            return "string";
        if (Byte.class.equals(type))
            return "byte";
        if (Double.class.equals(type))
            return "double";
        if (Float.class.equals(type))
            return "float";
        if (Integer.class.equals(type))
            return "int";
        if (Long.class.equals(type))
            return "long";
        if (Short.class.equals(type))
            return "short";
        if (Date.class.equals(type))
            return "date";
        if (Map.class.equals(type) || Map.class.isAssignableFrom(type))
            return "map";
        return type.getName();
    }

    private Class<?> parseType(String typeName) throws ClassNotFoundException {
        if ("string".equalsIgnoreCase(typeName))
            return String.class;
        if ("byte".equalsIgnoreCase(typeName))
            return Byte.class;
        if ("double".equalsIgnoreCase(typeName))
            return Double.class;
        if ("float".equalsIgnoreCase(typeName))
            return Float.class;
        if ("int".equalsIgnoreCase(typeName))
            return Integer.class;
        if ("long".equalsIgnoreCase(typeName))
            return Long.class;
        if ("short".equalsIgnoreCase(typeName))
            return Short.class;
        if ("date".equalsIgnoreCase(typeName))
            return Date.class;
        if ("map".equalsIgnoreCase(typeName))
            return HashMap.class;
        return Class.forName(typeName, false, this.getClass().getClassLoader());
    }
wsky commented 11 years ago
wsky commented 11 years ago
{"Args":["abc",1,1.1,1.2,1,1,1,1369015078327,{"k":"k"},{"array":["abc"],"long":0,"map":{"k":"k"},"string":"string"},["abc"]],"MethodName":"echo","MethodSignature":["","b","d","f","i","l","s","t","m","com.taobao.top.link.remoting.Entity","["],"TypeName":"serviceType","Uri":"uri"}

type define https://github.com/wsky/top-link/blob/551a0cb0b055fc20521f4e1c0af97228d7ac3c3c/java/remoting/main/com/taobao/top/link/remoting/CrossLanguageJsonSerializer.java#L97

private String parseTypeName(Class<?> type) {
        if (String.class.equals(type))
            return "";
        if (Byte.class.equals(type) || byte.class.equals(type))
            return "b";
        if (Double.class.equals(type) || double.class.equals(type))
            return "d";
        if (Float.class.equals(type) || float.class.equals(type))
            return "f";
        if (Integer.class.equals(type) || int.class.equals(type))
            return "i";
        if (Long.class.equals(type) || long.class.equals(type))
            return "l";
        if (Short.class.equals(type) || short.class.equals(type))
            return "s";
        if (Date.class.equals(type))
            return "t";
        if (Map.class.equals(type) || Map.class.isAssignableFrom(type))
            return "m";
        if (type.isArray())
            return String.format("[%s", this.parseTypeName(type.getComponentType()));
        return type.getName();
    }
wsky commented 11 years ago

fastjson deserialize "null" field https://github.com/alibaba/fastjson/issues/39

wsky commented 11 years ago

impl cross-language serialization

wsky commented 11 years ago

hessian serialization also works http://hessian.caucho.com/doc/hessian-serialization.html

wsky commented 11 years ago

said, java generics was not real type, so HashMap.class did not work for json deserialize, and HashMap<Object, Object>.class was wrong

if (methodCall.MethodSignature[i].equals(HashMap.class))
                    methodCall.Args[i] = JSON.parseObject(
                            args.getJSONObject(i).toJSONString(),
                            new TypeReference<HashMap<Object, Object>>() {
                            });
                else
                    methodCall.Args[i] = args.getObject(i, methodCall.MethodSignature[i]);

server can support deserialize HashMap<Object,Object>

wsky commented 11 years ago

c# DateTime serilize should be same as java, convert to long