You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2021/08/27 14:13:42 UTC
[dubbo-go] branch 1.5 updated: fix generic call bug (#1413)
This is an automated email from the ASF dual-hosted git repository.
alexstocks pushed a commit to branch 1.5
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/1.5 by this push:
new 826f525 fix generic call bug (#1413)
826f525 is described below
commit 826f525903f031d97c2ee051dfc2de38781d83e1
Author: alchemy-lee <27...@qq.com>
AuthorDate: Fri Aug 27 22:13:36 2021 +0800
fix generic call bug (#1413)
---
filter/filter_impl/generic_filter.go | 183 +++++------------
filter/filter_impl/generic_filter_test.go | 191 +++++++----------
...eneric_filter.go => generic_generalizer_map.go} | 82 ++++----
...ter_test.go => generic_generalizer_map_test.go} | 156 +++++++++++---
filter/filter_impl/generic_service_filter.go | 111 +++++-----
filter/filter_impl/generic_service_filter_test.go | 228 ++++++++++++---------
protocol/dubbo/hessian2/hessian_request.go | 91 +-------
protocol/dubbo/hessian2/java_class.go | 200 ++++++++++++++++++
protocol/dubbo/hessian2/java_class_test.go | 132 ++++++++++++
9 files changed, 802 insertions(+), 572 deletions(-)
diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go
index 86f8cfd..facdfda 100644
--- a/filter/filter_impl/generic_filter.go
+++ b/filter/filter_impl/generic_filter.go
@@ -19,9 +19,7 @@ package filter_impl
import (
"context"
- "reflect"
"strings"
- "time"
)
import (
@@ -54,23 +52,47 @@ type GenericFilter struct{}
// Invoke turns the parameters to map for generic method
func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
- if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 {
- oldArguments := invocation.Arguments()
+ if isCallingToGenericService(invoker, invocation) {
- if oldParams, ok := oldArguments[2].([]interface{}); ok {
- newParams := make([]hessian.Object, 0, len(oldParams))
- for i := range oldParams {
- newParams = append(newParams, hessian.Object(objToMap(oldParams[i])))
+ mtdname := invocation.MethodName()
+ oldargs := invocation.Arguments()
+
+ types := make([]string, 0, len(oldargs))
+ args := make([]hessian.Object, 0, len(oldargs))
+
+ // get generic info from attachments of invocation, the default value is "true"
+ //generic := invocation.AttachmentsByKey(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT)
+ // get generalizer according to value in the `generic`
+ g := GetMapGeneralizer()
+
+ for _, arg := range oldargs {
+ // use the default generalizer(MapGeneralizer)
+ typ, err := g.GetType(arg)
+ if err != nil {
+ logger.Errorf("failed to get type, %v", err)
}
- newArguments := []interface{}{
- oldArguments[0],
- oldArguments[1],
- newParams,
+ obj, err := g.Generalize(arg)
+ if err != nil {
+ logger.Errorf("generalization failed, %v", err)
+ return invoker.Invoke(ctx, invocation)
}
- newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments())
- newInvocation.SetReply(invocation.Reply())
- return invoker.Invoke(ctx, newInvocation)
+ types = append(types, typ)
+ args = append(args, obj)
}
+
+ // construct a new invocation for generic call
+ newargs := []interface{}{
+ mtdname,
+ types,
+ args,
+ }
+ newivc := invocation2.NewRPCInvocation(constant.GENERIC, newargs, invocation.Attachments())
+ newivc.SetReply(invocation.Reply())
+ newivc.Attachments()[constant.GENERIC_KEY] = invoker.GetURL().GetParam(constant.GENERIC_KEY, "")
+
+ return invoker.Invoke(ctx, newivc)
+ } else if isMakingAGenericCall(invoker, invocation) {
+ invocation.Attachments()[constant.GENERIC_KEY] = invoker.GetURL().GetParam(constant.GENERIC_KEY, "")
}
return invoker.Invoke(ctx, invocation)
}
@@ -86,125 +108,22 @@ func GetGenericFilter() filter.Filter {
return &GenericFilter{}
}
-func objToMap(obj interface{}) interface{} {
- if obj == nil {
- return obj
- }
-
- t := reflect.TypeOf(obj)
- v := reflect.ValueOf(obj)
-
- // if obj is a POJO, get the struct from the pointer (if it is a pointer)
- pojo, isPojo := obj.(hessian.POJO)
- if isPojo {
- for t.Kind() == reflect.Ptr {
- t = t.Elem()
- v = v.Elem()
- }
- }
-
- switch t.Kind() {
- case reflect.Struct:
- result := make(map[string]interface{}, t.NumField())
- if isPojo {
- result["class"] = pojo.JavaClassName()
- }
- for i := 0; i < t.NumField(); i++ {
- field := t.Field(i)
- value := v.Field(i)
- kind := value.Kind()
- if !value.CanInterface() {
- logger.Debugf("objToMap for %v is skipped because it couldn't be converted to interface", field)
- continue
- }
- valueIface := value.Interface()
- switch kind {
- case reflect.Ptr:
- if value.IsNil() {
- setInMap(result, field, nil)
- continue
- }
- setInMap(result, field, objToMap(valueIface))
- case reflect.Struct, reflect.Slice, reflect.Map:
- if isPrimitive(valueIface) {
- logger.Warnf("\"%s\" is primitive. The application may crash if it's transferred between "+
- "systems implemented by different languages, e.g. dubbo-go <-> dubbo-java. We recommend "+
- "you represent the object by basic types, like string.", value.Type())
- setInMap(result, field, valueIface)
- continue
- }
-
- setInMap(result, field, objToMap(valueIface))
- default:
- setInMap(result, field, valueIface)
- }
- }
- return result
- case reflect.Array, reflect.Slice:
- value := reflect.ValueOf(obj)
- newTemps := make([]interface{}, 0, value.Len())
- for i := 0; i < value.Len(); i++ {
- newTemp := objToMap(value.Index(i).Interface())
- newTemps = append(newTemps, newTemp)
- }
- return newTemps
- case reflect.Map:
- newTempMap := make(map[interface{}]interface{}, v.Len())
- iter := v.MapRange()
- for iter.Next() {
- if !iter.Value().CanInterface() {
- continue
- }
- key := iter.Key()
- mapV := iter.Value().Interface()
- newTempMap[mapKey(key)] = objToMap(mapV)
- }
- return newTempMap
- case reflect.Ptr:
- return objToMap(v.Elem().Interface())
- default:
- return obj
- }
-}
-
-// mapKey converts the map key to interface type
-func mapKey(key reflect.Value) interface{} {
- switch key.Kind() {
- case reflect.Bool, reflect.Int, reflect.Int8,
- reflect.Int16, reflect.Int32, reflect.Int64,
- reflect.Uint, reflect.Uint8, reflect.Uint16,
- reflect.Uint32, reflect.Uint64, reflect.Float32,
- reflect.Float64, reflect.String:
- return key.Interface()
- default:
- name := key.String()
- if name == "class" {
- panic(`"class" is a reserved keyword`)
- }
- return name
- }
+// isCallingToGenericService check if it calls to a generic service
+func isCallingToGenericService(invoker protocol.Invoker, invocation protocol.Invocation) bool {
+ return isGeneric(invoker.GetURL().GetParam(constant.GENERIC_KEY, "")) &&
+ invocation.MethodName() != constant.GENERIC
}
-// setInMap sets the struct into the map using the tag or the name of the struct as the key
-func setInMap(m map[string]interface{}, structField reflect.StructField, value interface{}) (result map[string]interface{}) {
- result = m
- if tagName := structField.Tag.Get("m"); tagName == "" {
- result[toUnexport(structField.Name)] = value
- } else {
- result[tagName] = value
- }
- return
+// isMakingAGenericCall check if it is making a generic call to a generic service
+func isMakingAGenericCall(invoker protocol.Invoker, invocation protocol.Invocation) bool {
+ return isGeneric(invoker.GetURL().GetParam(constant.GENERIC_KEY, "")) &&
+ invocation.MethodName() == constant.GENERIC &&
+ invocation.Arguments() != nil &&
+ len(invocation.Arguments()) == 3
}
-// toUnexport is to lower the first letter
-func toUnexport(a string) string {
- return strings.ToLower(a[:1]) + a[1:]
-}
-
-// isPrimitive determines if the object is primitive
-func isPrimitive(obj interface{}) bool {
- if _, ok := obj.(time.Time); ok {
- return true
- }
- return false
+// isGeneric receives a generic field from url of invoker to determine whether the service is generic or not
+func isGeneric(generic string) bool {
+ lowerGeneric := strings.ToLower(generic)
+ return lowerGeneric == GENERIC_SERIALIZATION_DEFAULT
}
diff --git a/filter/filter_impl/generic_filter_test.go b/filter/filter_impl/generic_filter_test.go
index 4a1f12e..bc861fd 100644
--- a/filter/filter_impl/generic_filter_test.go
+++ b/filter/filter_impl/generic_filter_test.go
@@ -18,136 +18,83 @@
package filter_impl
import (
- "reflect"
+ "context"
+ "net/url"
"testing"
- "time"
)
import (
+ hessian "github.com/apache/dubbo-go-hessian2"
+ "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)
-func TestStruct2MapAll(t *testing.T) {
- var testData struct {
- AaAa string `m:"aaAa"`
- BaBa string
- CaCa struct {
- AaAa string
- BaBa string `m:"baBa"`
- XxYy struct {
- xxXx string `m:"xxXx"`
- Xx string `m:"xx"`
- } `m:"xxYy"`
- } `m:"caCa"`
- DaDa time.Time
- EeEe int
- }
- testData.AaAa = "1"
- testData.BaBa = "1"
- testData.CaCa.BaBa = "2"
- testData.CaCa.AaAa = "2"
- testData.CaCa.XxYy.xxXx = "3"
- testData.CaCa.XxYy.Xx = "3"
- testData.DaDa = time.Date(2020, 10, 29, 2, 34, 0, 0, time.Local)
- testData.EeEe = 100
- m := objToMap(testData).(map[string]interface{})
- assert.Equal(t, "1", m["aaAa"].(string))
- assert.Equal(t, "1", m["baBa"].(string))
- assert.Equal(t, "2", m["caCa"].(map[string]interface{})["aaAa"].(string))
- assert.Equal(t, "3", m["caCa"].(map[string]interface{})["xxYy"].(map[string]interface{})["xx"].(string))
-
- assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"]).Kind())
- assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].(map[string]interface{})["xxYy"]).Kind())
- assert.Equal(t, "2020-10-29 02:34:00", m["daDa"].(time.Time).Format("2006-01-02 15:04:05"))
- assert.Equal(t, 100, m["eeEe"].(int))
-}
-
-type testStruct struct {
- AaAa string
- BaBa string `m:"baBa"`
- XxYy struct {
- xxXx string `m:"xxXx"`
- Xx string `m:"xx"`
- } `m:"xxYy"`
-}
-
-func TestStruct2MapAllSlice(t *testing.T) {
- var testData struct {
- AaAa string `m:"aaAa"`
- BaBa string
- CaCa []testStruct `m:"caCa"`
- }
- testData.AaAa = "1"
- testData.BaBa = "1"
- var tmp testStruct
- tmp.BaBa = "2"
- tmp.AaAa = "2"
- tmp.XxYy.xxXx = "3"
- tmp.XxYy.Xx = "3"
- testData.CaCa = append(testData.CaCa, tmp)
- m := objToMap(testData).(map[string]interface{})
-
- assert.Equal(t, "1", m["aaAa"].(string))
- assert.Equal(t, "1", m["baBa"].(string))
- assert.Equal(t, "2", m["caCa"].([]interface{})[0].(map[string]interface{})["aaAa"].(string))
- assert.Equal(t, "3", m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"].(map[string]interface{})["xx"].(string))
-
- assert.Equal(t, reflect.Slice, reflect.TypeOf(m["caCa"]).Kind())
- assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"]).Kind())
-}
-
-func TestStruct2MapAllMap(t *testing.T) {
- var testData struct {
- AaAa string
- Baba map[string]interface{}
- CaCa map[string]string
- DdDd map[string]interface{}
- IntMap map[int]interface{}
- }
- testData.AaAa = "aaaa"
- testData.Baba = make(map[string]interface{})
- testData.CaCa = make(map[string]string)
- testData.DdDd = nil
- testData.IntMap = make(map[int]interface{})
-
- testData.Baba["kk"] = 1
- var structData struct {
- Str string
- }
- structData.Str = "str"
- testData.Baba["struct"] = structData
- testData.Baba["nil"] = nil
- testData.CaCa["k1"] = "v1"
- testData.CaCa["kv2"] = "v2"
- testData.IntMap[1] = 1
- m := objToMap(testData)
-
- assert.Equal(t, reflect.Map, reflect.TypeOf(m).Kind())
- mappedStruct := m.(map[string]interface{})
- assert.Equal(t, reflect.String, reflect.TypeOf(mappedStruct["aaAa"]).Kind())
- assert.Equal(t, reflect.Map, reflect.TypeOf(mappedStruct["baba"]).Kind())
- assert.Equal(t, reflect.Map, reflect.TypeOf(mappedStruct["baba"].(map[interface{}]interface{})["struct"]).Kind())
- assert.Equal(t, "str", mappedStruct["baba"].(map[interface{}]interface{})["struct"].(map[string]interface{})["str"])
- assert.Equal(t, nil, mappedStruct["baba"].(map[interface{}]interface{})["nil"])
- assert.Equal(t, reflect.Map, reflect.TypeOf(mappedStruct["caCa"]).Kind())
- assert.Equal(t, reflect.Map, reflect.TypeOf(mappedStruct["ddDd"]).Kind())
- intMap := mappedStruct["intMap"]
- assert.Equal(t, reflect.Map, reflect.TypeOf(intMap).Kind())
- assert.Equal(t, 1, intMap.(map[interface{}]interface{})[1])
-}
-
-type mockParent struct {
- Children []*mockChild
-}
-
-func (p *mockParent) JavaClassName() string {
- return "org.apache.dubbo.mockParent"
-}
+import (
+ "github.com/apache/dubbo-go/common"
+ "github.com/apache/dubbo-go/common/constant"
+ "github.com/apache/dubbo-go/protocol"
+ "github.com/apache/dubbo-go/protocol/invocation"
+ "github.com/apache/dubbo-go/protocol/mock"
+)
-type mockChild struct {
- Name string
+// test isCallingToGenericService branch
+func TestFilter_Invoke(t *testing.T) {
+ invokeUrl := common.NewURLWithOptions(
+ common.WithParams(url.Values{}),
+ common.WithParamsValue(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT))
+ filter := &GenericFilter{}
+
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ normalInvocation := invocation.NewRPCInvocation("Hello", []interface{}{"arg1"}, make(map[string]interface{}))
+
+ mockInvoker := mock.NewMockInvoker(ctrl)
+ mockInvoker.EXPECT().GetURL().Return(invokeUrl).Times(2)
+ mockInvoker.EXPECT().Invoke(gomock.Not(normalInvocation)).DoAndReturn(
+ func(invocation protocol.Invocation) protocol.Result {
+ assert.Equal(t, constant.GENERIC, invocation.MethodName())
+ args := invocation.Arguments()
+ assert.Equal(t, "Hello", args[0])
+ assert.Equal(t, "java.lang.String", args[1].([]string)[0])
+ assert.Equal(t, "arg1", args[2].([]hessian.Object)[0].(string))
+ assert.Equal(t, GENERIC_SERIALIZATION_DEFAULT, invocation.AttachmentsByKey(constant.GENERIC_KEY, ""))
+ return &protocol.RPCResult{}
+ })
+
+ result := filter.Invoke(context.Background(), mockInvoker, normalInvocation)
+ assert.NotNil(t, result)
}
-func (p *mockChild) JavaClassName() string {
- return "org.apache.dubbo.mockChild"
+// test isMakingAGenericCall branch
+func TestFilter_InvokeWithGenericCall(t *testing.T) {
+ invokeUrl := common.NewURLWithOptions(
+ common.WithParams(url.Values{}),
+ common.WithParamsValue(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT))
+ filter := &GenericFilter{}
+
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ genericInvocation := invocation.NewRPCInvocation(constant.GENERIC, []interface{}{
+ "hello",
+ []string{"java.lang.String"},
+ []string{"arg1"},
+ }, make(map[string]interface{}))
+
+ mockInvoker := mock.NewMockInvoker(ctrl)
+ mockInvoker.EXPECT().GetURL().Return(invokeUrl).Times(3)
+ mockInvoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(
+ func(invocation protocol.Invocation) protocol.Result {
+ assert.Equal(t, constant.GENERIC, invocation.MethodName())
+ args := invocation.Arguments()
+ assert.Equal(t, "hello", args[0])
+ assert.Equal(t, "java.lang.String", args[1].([]string)[0])
+ assert.Equal(t, "arg1", args[2].([]string)[0])
+ assert.Equal(t, GENERIC_SERIALIZATION_DEFAULT, invocation.AttachmentsByKey(constant.GENERIC_KEY, ""))
+ return &protocol.RPCResult{}
+ })
+
+ result := filter.Invoke(context.Background(), mockInvoker, genericInvocation)
+ assert.NotNil(t, result)
}
diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_generalizer_map.go
similarity index 72%
copy from filter/filter_impl/generic_filter.go
copy to filter/filter_impl/generic_generalizer_map.go
index 86f8cfd..73531a3 100644
--- a/filter/filter_impl/generic_filter.go
+++ b/filter/filter_impl/generic_generalizer_map.go
@@ -18,74 +18,70 @@
package filter_impl
import (
- "context"
"reflect"
"strings"
+ "sync"
"time"
)
import (
hessian "github.com/apache/dubbo-go-hessian2"
+ "github.com/mitchellh/mapstructure"
+ perrors "github.com/pkg/errors"
)
import (
- "github.com/apache/dubbo-go/common/constant"
- "github.com/apache/dubbo-go/common/extension"
"github.com/apache/dubbo-go/common/logger"
- "github.com/apache/dubbo-go/filter"
- "github.com/apache/dubbo-go/protocol"
- invocation2 "github.com/apache/dubbo-go/protocol/invocation"
+ "github.com/apache/dubbo-go/protocol/dubbo/hessian2"
)
-const (
- // GENERIC
- //generic module name
- GENERIC = "generic"
+var (
+ mapGeneralizer *MapGeneralizer
+ mapGeneralizerOnce sync.Once
)
-func init() {
- extension.SetFilter(GENERIC, GetGenericFilter)
+func GetMapGeneralizer() *MapGeneralizer {
+ mapGeneralizerOnce.Do(func() {
+ mapGeneralizer = &MapGeneralizer{}
+ })
+ return mapGeneralizer
}
-// when do a generic invoke, struct need to be map
+type MapGeneralizer struct{}
-// nolint
-type GenericFilter struct{}
-
-// Invoke turns the parameters to map for generic method
-func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
- if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 {
- oldArguments := invocation.Arguments()
+func (g *MapGeneralizer) Generalize(obj interface{}) (gobj interface{}, err error) {
+ gobj = objToMap(obj)
+ return
+}
- if oldParams, ok := oldArguments[2].([]interface{}); ok {
- newParams := make([]hessian.Object, 0, len(oldParams))
- for i := range oldParams {
- newParams = append(newParams, hessian.Object(objToMap(oldParams[i])))
- }
- newArguments := []interface{}{
- oldArguments[0],
- oldArguments[1],
- newParams,
- }
- newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments())
- newInvocation.SetReply(invocation.Reply())
- return invoker.Invoke(ctx, newInvocation)
- }
+func (g *MapGeneralizer) Realize(obj interface{}, typ reflect.Type) (interface{}, error) {
+ newobj := reflect.New(typ).Interface()
+ err := mapstructure.Decode(obj, newobj)
+ if err != nil {
+ return nil, perrors.Errorf("realizing map failed, %v", err)
}
- return invoker.Invoke(ctx, invocation)
-}
-// OnResponse dummy process, returns the result directly
-func (ef *GenericFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker,
- _ protocol.Invocation) protocol.Result {
- return result
+ return reflect.ValueOf(newobj).Elem().Interface(), nil
}
-// GetGenericFilter returns GenericFilter instance
-func GetGenericFilter() filter.Filter {
- return &GenericFilter{}
+func (g *MapGeneralizer) GetType(obj interface{}) (typ string, err error) {
+ typ, err = hessian2.GetJavaName(obj)
+ // no error or error is not NilError
+ if err == nil || err != hessian2.NilError {
+ return
+ }
+
+ typ = "java.lang.Object"
+ if err == hessian2.NilError {
+ logger.Debugf("the type of nil object couldn't be inferred, use the default value(\"%s\")", typ)
+ return
+ }
+
+ logger.Debugf("the type of object(=%T) couldn't be recognized as a POJO, use the default value(\"%s\")", obj, typ)
+ return
}
+// objToMap converts an object(interface{}) to a map
func objToMap(obj interface{}) interface{} {
if obj == nil {
return obj
diff --git a/filter/filter_impl/generic_filter_test.go b/filter/filter_impl/generic_generalizer_map_test.go
similarity index 53%
copy from filter/filter_impl/generic_filter_test.go
copy to filter/filter_impl/generic_generalizer_map_test.go
index 4a1f12e..cb072d1 100644
--- a/filter/filter_impl/generic_filter_test.go
+++ b/filter/filter_impl/generic_generalizer_map_test.go
@@ -27,30 +27,32 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestStruct2MapAll(t *testing.T) {
- var testData struct {
- AaAa string `m:"aaAa"`
- BaBa string
- CaCa struct {
- AaAa string
- BaBa string `m:"baBa"`
- XxYy struct {
- xxXx string `m:"xxXx"`
- Xx string `m:"xx"`
- } `m:"xxYy"`
- } `m:"caCa"`
- DaDa time.Time
- EeEe int
- }
- testData.AaAa = "1"
- testData.BaBa = "1"
- testData.CaCa.BaBa = "2"
- testData.CaCa.AaAa = "2"
- testData.CaCa.XxYy.xxXx = "3"
- testData.CaCa.XxYy.Xx = "3"
- testData.DaDa = time.Date(2020, 10, 29, 2, 34, 0, 0, time.Local)
- testData.EeEe = 100
- m := objToMap(testData).(map[string]interface{})
+type testPlainObj struct {
+ AaAa string `m:"aaAa"`
+ BaBa string
+ CaCa struct {
+ AaAa string
+ BaBa string `m:"baBa"`
+ XxYy struct {
+ xxXx string `m:"xxXx"`
+ Xx string `m:"xx"`
+ } `m:"xxYy"`
+ } `m:"caCa"`
+ DaDa time.Time
+ EeEe int
+}
+
+func TestObjToMap(t *testing.T) {
+ obj := &testPlainObj{}
+ obj.AaAa = "1"
+ obj.BaBa = "1"
+ obj.CaCa.BaBa = "2"
+ obj.CaCa.AaAa = "2"
+ obj.CaCa.XxYy.xxXx = "3"
+ obj.CaCa.XxYy.Xx = "3"
+ obj.DaDa = time.Date(2020, 10, 29, 2, 34, 0, 0, time.Local)
+ obj.EeEe = 100
+ m := objToMap(obj).(map[string]interface{})
assert.Equal(t, "1", m["aaAa"].(string))
assert.Equal(t, "1", m["baBa"].(string))
assert.Equal(t, "2", m["caCa"].(map[string]interface{})["aaAa"].(string))
@@ -71,7 +73,7 @@ type testStruct struct {
} `m:"xxYy"`
}
-func TestStruct2MapAllSlice(t *testing.T) {
+func TestObjToMap_Slice(t *testing.T) {
var testData struct {
AaAa string `m:"aaAa"`
BaBa string
@@ -96,7 +98,7 @@ func TestStruct2MapAllSlice(t *testing.T) {
assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"]).Kind())
}
-func TestStruct2MapAllMap(t *testing.T) {
+func TestObjToMap_Map(t *testing.T) {
var testData struct {
AaAa string
Baba map[string]interface{}
@@ -136,18 +138,112 @@ func TestStruct2MapAllMap(t *testing.T) {
assert.Equal(t, 1, intMap.(map[interface{}]interface{})[1])
}
+var mockMapGeneralizer = GetMapGeneralizer()
+
type mockParent struct {
- Children []*mockChild
+ Gender, Email, Name string
+ Age int
+ Child *mockChild
}
-func (p *mockParent) JavaClassName() string {
+func (p mockParent) JavaClassName() string {
return "org.apache.dubbo.mockParent"
}
type mockChild struct {
- Name string
+ Gender, Email, Name string
+ Age int
}
-func (p *mockChild) JavaClassName() string {
+func (c *mockChild) JavaClassName() string {
return "org.apache.dubbo.mockChild"
}
+
+func TestPOJOClassName(t *testing.T) {
+ c := &mockChild{
+ Age: 20,
+ Gender: "male",
+ Email: "lmc@example.com",
+ Name: "lmc",
+ }
+ p := mockParent{
+ Age: 30,
+ Gender: "male",
+ Email: "xavierniu@example.com",
+ Name: "xavierniu",
+ Child: c,
+ }
+
+ m, err := mockMapGeneralizer.Generalize(p)
+ assert.Nil(t, err)
+ // parent
+ assert.Equal(t, "xavierniu", m.(map[string]interface{})["name"].(string))
+ assert.Equal(t, 30, m.(map[string]interface{})["age"].(int))
+ assert.Equal(t, "org.apache.dubbo.mockParent", m.(map[string]interface{})["class"].(string))
+ // child
+ assert.Equal(t, 20, m.(map[string]interface{})["child"].(map[string]interface{})["age"].(int))
+ assert.Equal(t, "lmc", m.(map[string]interface{})["child"].(map[string]interface{})["name"].(string))
+ assert.Equal(t, "org.apache.dubbo.mockChild", m.(map[string]interface{})["child"].(map[string]interface{})["class"].(string))
+
+ r, err := mockMapGeneralizer.Realize(m, reflect.TypeOf(p))
+ assert.Nil(t, err)
+ rMockParent, ok := r.(mockParent)
+ assert.True(t, ok)
+ // parent
+ assert.Equal(t, "xavierniu", rMockParent.Name)
+ assert.Equal(t, 30, rMockParent.Age)
+ // child
+ assert.Equal(t, "lmc", rMockParent.Child.Name)
+ assert.Equal(t, 20, rMockParent.Child.Age)
+}
+
+func TestPOJOArray(t *testing.T) {
+ c1 := &mockChild{
+ Age: 20,
+ Gender: "male",
+ Email: "lmc@example.com",
+ Name: "lmc",
+ }
+ c2 := &mockChild{
+ Age: 21,
+ Gender: "male",
+ Email: "lmc1@example.com",
+ Name: "lmc1",
+ }
+
+ pojoArr := []*mockChild{c1, c2}
+
+ m, err := mockMapGeneralizer.Generalize(pojoArr)
+ assert.Nil(t, err)
+ assert.Equal(t, "lmc", m.([]interface{})[0].(map[string]interface{})["name"].(string))
+ assert.Equal(t, 20, m.([]interface{})[0].(map[string]interface{})["age"].(int))
+ assert.Equal(t, "lmc1", m.([]interface{})[1].(map[string]interface{})["name"].(string))
+ assert.Equal(t, 21, m.([]interface{})[1].(map[string]interface{})["age"].(int))
+
+ r, err := mockMapGeneralizer.Realize(m, reflect.TypeOf(pojoArr))
+ assert.Nil(t, err)
+ rPojoArr, ok := r.([]*mockChild)
+ assert.True(t, ok)
+ assert.Equal(t, "lmc", rPojoArr[0].Name)
+ assert.Equal(t, 20, rPojoArr[0].Age)
+ assert.Equal(t, "lmc1", rPojoArr[1].Name)
+ assert.Equal(t, 21, rPojoArr[1].Age)
+}
+
+func TestNullField(t *testing.T) {
+ p := mockParent{
+ Age: 30,
+ Gender: "male",
+ Email: "xavierniu@example.com",
+ Name: "xavierniu",
+ }
+
+ m, _ := mockMapGeneralizer.Generalize(p)
+ assert.Nil(t, m.(map[string]interface{})["child"])
+
+ r, err := mockMapGeneralizer.Realize(m, reflect.TypeOf(p))
+ assert.Nil(t, err)
+ rMockParent, ok := r.(mockParent)
+ assert.True(t, ok)
+ assert.Nil(t, rMockParent.Child)
+}
diff --git a/filter/filter_impl/generic_service_filter.go b/filter/filter_impl/generic_service_filter.go
index 6056224..886bb05 100644
--- a/filter/filter_impl/generic_service_filter.go
+++ b/filter/filter_impl/generic_service_filter.go
@@ -19,12 +19,10 @@ package filter_impl
import (
"context"
- "reflect"
)
import (
hessian "github.com/apache/dubbo-go-hessian2"
- "github.com/mitchellh/mapstructure"
perrors "github.com/pkg/errors"
)
@@ -54,74 +52,79 @@ type GenericServiceFilter struct{}
// Invoke is used to call service method by invocation
func (ef *GenericServiceFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
- logger.Infof("invoking generic service filter.")
- logger.Debugf("generic service filter methodName:%v,args:%v", invocation.MethodName(), len(invocation.Arguments()))
-
if invocation.MethodName() != constant.GENERIC || len(invocation.Arguments()) != 3 {
return invoker.Invoke(ctx, invocation)
}
- var (
- ok bool
- err error
- methodName string
- newParams []interface{}
- genericKey string
- argsType []reflect.Type
- oldParams []hessian.Object
- )
-
- url := invoker.GetURL()
- methodName = invocation.Arguments()[0].(string)
- // get service
- svc := common.ServiceMap.GetServiceByServiceKey(url.Protocol, url.ServiceKey())
- // get method
- method := svc.Method()[methodName]
+ // get real invocation info from the generic invocation
+ mtdname := invocation.Arguments()[0].(string)
+ // types are not required in dubbo-go, for dubbo-go client to dubbo-go server, types could be nil
+ types := invocation.Arguments()[1]
+ args := invocation.Arguments()[2].([]hessian.Object)
+
+ logger.Debugf(`received a generic invocation:
+ MethodName: %s,
+ Types: %s,
+ Args: %s
+ `, mtdname, types, args)
+
+ // get the type of the argument
+ ivkUrl := invoker.GetURL()
+ svc := common.ServiceMap.GetServiceByServiceKey(ivkUrl.Protocol, ivkUrl.ServiceKey())
+ method := svc.Method()[mtdname]
if method == nil {
- logger.Errorf("[Generic Service Filter] Don't have this method: %s", methodName)
- return &protocol.RPCResult{}
- }
- argsType = method.ArgsType()
- genericKey = invocation.AttachmentsByKey(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT)
- if genericKey == GENERIC_SERIALIZATION_DEFAULT {
- oldParams, ok = invocation.Arguments()[2].([]hessian.Object)
- } else {
- logger.Errorf("[Generic Service Filter] Don't support this generic: %s", genericKey)
- return &protocol.RPCResult{}
- }
- if !ok {
- logger.Errorf("[Generic Service Filter] wrong serialization")
- return &protocol.RPCResult{}
+ return &protocol.RPCResult{
+ Err: perrors.Errorf("\"%s\" method is not found, service key: %s", mtdname, ivkUrl.ServiceKey()),
+ }
}
- if len(oldParams) != len(argsType) {
- logger.Errorf("[Generic Service Filter] method:%s invocation arguments number was wrong", methodName)
- return &protocol.RPCResult{}
+ argsType := method.ArgsType()
+
+ // get generic info from attachments of invocation, the default value is "true"
+ //generic := invocation.AttachmentsByKey(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT)
+ // get generalizer according to value in the `generic`
+ g := GetMapGeneralizer()
+
+ if len(args) != len(argsType) {
+ return &protocol.RPCResult{
+ Err: perrors.Errorf("the number of args(=%d) is not matched with \"%s\" method", len(args), mtdname),
+ }
}
- // oldParams convert to newParams
- newParams = make([]interface{}, len(oldParams))
- for i := range argsType {
- newParam := reflect.New(argsType[i]).Interface()
- err = mapstructure.Decode(oldParams[i], newParam)
- newParam = reflect.ValueOf(newParam).Elem().Interface()
+
+ // realize
+ newargs := make([]interface{}, len(argsType))
+ for i := 0; i < len(argsType); i++ {
+ newarg, err := g.Realize(args[i], argsType[i])
if err != nil {
- logger.Errorf("[Generic Service Filter] decode arguments map to struct wrong: error{%v}", perrors.WithStack(err))
- return &protocol.RPCResult{}
+ return &protocol.RPCResult{
+ Err: perrors.Errorf("realization failed, %v", err),
+ }
}
- newParams[i] = newParam
+ newargs[i] = newarg
}
- newInvocation := invocation2.NewRPCInvocation(methodName, newParams, invocation.Attachments())
- newInvocation.SetReply(invocation.Reply())
- return invoker.Invoke(ctx, newInvocation)
+
+ // build a normal invocation
+ newivc := invocation2.NewRPCInvocation(mtdname, newargs, invocation.Attachments())
+ newivc.SetReply(invocation.Reply())
+
+ return invoker.Invoke(ctx, newivc)
}
// nolint
func (ef *GenericServiceFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 && result.Result() != nil {
- v := reflect.ValueOf(result.Result())
- if v.Kind() == reflect.Ptr {
- v = v.Elem()
+ // get generic info from attachments of invocation, the default value is "true"
+ //generic := invocation.AttachmentsByKey(constant.GENERIC_KEY, constant.GenericSerializationDefault)
+ // get generalizer according to value in the `generic`
+ g := GetMapGeneralizer()
+
+ obj, err := g.Generalize(result.Result())
+ if err != nil {
+ err = perrors.Errorf("generalizaion failed, %v", err)
+ result.SetError(err)
+ result.SetResult(nil)
+ return result
}
- result.SetResult(objToMap(v.Interface()))
+ result.SetResult(obj)
}
return result
}
diff --git a/filter/filter_impl/generic_service_filter_test.go b/filter/filter_impl/generic_service_filter_test.go
index c755a2d..d32e803 100644
--- a/filter/filter_impl/generic_service_filter_test.go
+++ b/filter/filter_impl/generic_service_filter_test.go
@@ -19,131 +19,157 @@ package filter_impl
import (
"context"
- "errors"
- "reflect"
+ "fmt"
+ "net/url"
"testing"
)
import (
hessian "github.com/apache/dubbo-go-hessian2"
+ "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)
import (
"github.com/apache/dubbo-go/common"
- "github.com/apache/dubbo-go/common/proxy/proxy_factory"
+ "github.com/apache/dubbo-go/common/constant"
"github.com/apache/dubbo-go/protocol"
"github.com/apache/dubbo-go/protocol/invocation"
+ "github.com/apache/dubbo-go/protocol/mock"
)
-type TestStruct struct {
- AaAa string
- BaBa string `m:"baBa"`
- XxYy struct {
- xxXx string `m:"xxXx"`
- Xx string `m:"xx"`
- } `m:"xxYy"`
-}
+type MockHelloService struct{}
-func (c *TestStruct) JavaClassName() string {
- return "com.test.testStruct"
+func (s *MockHelloService) Hello(who string) (string, error) {
+ return fmt.Sprintf("hello, %s", who), nil
}
-type TestService struct{}
-
-// nolint
-func (ts *TestService) MethodOne(_ context.Context, test1 *TestStruct, test2 []TestStruct,
- test3 interface{}, test4 []interface{}, test5 *string) (*TestStruct, error) {
- if test1 == nil {
- return nil, errors.New("param test1 is nil")
- }
- if test2 == nil {
- return nil, errors.New("param test2 is nil")
- }
- if test3 == nil {
- return nil, errors.New("param test3 is nil")
- }
- if test4 == nil {
- return nil, errors.New("param test4 is nil")
- }
- if test5 == nil {
- return nil, errors.New("param test5 is nil")
- }
- return &TestStruct{}, nil
+func (s *MockHelloService) JavaClassName() string {
+ return "org.apache.dubbo.hello"
}
-// nolint
-func (*TestService) Reference() string {
- return "com.test.Path"
+func (s *MockHelloService) Reference() string {
+ return "org.apache.dubbo.test"
}
-func TestGenericServiceFilterInvoke(t *testing.T) {
- hessian.RegisterPOJO(&TestStruct{})
- methodName := "$invoke"
- m := make(map[string]interface{})
- m["AaAa"] = "nihao"
- x := make(map[string]interface{})
- x["xxXX"] = "nihaoxxx"
- m["XxYy"] = x
- aurguments := []interface{}{
- "MethodOne",
- nil,
- []hessian.Object{
- hessian.Object(m),
- hessian.Object(append(make([]map[string]interface{}, 1), m)),
- hessian.Object("111"),
- hessian.Object(append(make([]map[string]interface{}, 1), m)),
- hessian.Object("222")},
- }
- s := &TestService{}
- _, _ = common.ServiceMap.Register("com.test.Path", "testprotocol", "", "", s)
- rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil)
- filter := GetGenericServiceFilter()
- url, _ := common.NewURL("testprotocol://127.0.0.1:20000/com.test.Path")
- result := filter.Invoke(context.Background(), &proxy_factory.ProxyInvoker{BaseInvoker: *protocol.NewBaseInvoker(url)}, rpcInvocation)
- assert.NotNil(t, result)
+func TestServiceFilter_Invoke(t *testing.T) {
+ filter := &GenericServiceFilter{}
+
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ mockInvoker := mock.NewMockInvoker(ctrl)
+
+ // methodName is not "$invoke"
+ invocation1 := invocation.NewRPCInvocation("test", nil, nil)
+ mockInvoker.EXPECT().Invoke(gomock.Eq(invocation1))
+ _ = filter.Invoke(context.Background(), mockInvoker, invocation1)
+ // arguments are nil
+ invocation2 := invocation.NewRPCInvocation(constant.GENERIC, nil, nil)
+ mockInvoker.EXPECT().Invoke(gomock.Eq(invocation2))
+ _ = filter.Invoke(context.Background(), mockInvoker, invocation2)
+ // the number of arguments is not 3
+ invocation3 := invocation.NewRPCInvocation(constant.GENERIC, []interface{}{"hello"}, nil)
+ mockInvoker.EXPECT().Invoke(gomock.Eq(invocation3))
+ _ = filter.Invoke(context.Background(), mockInvoker, invocation3)
+
+ // hello service
+ service := &MockHelloService{}
+ // invoke URL
+ ivkUrl := common.NewURLWithOptions(
+ common.WithProtocol("test"),
+ common.WithParams(url.Values{}),
+ common.WithParamsValue(constant.INTERFACE_KEY, service.Reference()),
+ common.WithParamsValue(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT))
+ // registry RPC service
+ _, err := common.ServiceMap.Register(ivkUrl.GetParam(constant.INTERFACE_KEY, ""),
+ ivkUrl.Protocol,
+ "",
+ "",
+ service)
+ assert.Nil(t, err)
+
+ // mock
+ mockInvoker.EXPECT().GetURL().Return(ivkUrl).Times(3)
+
+ // invoke a method without errors using default generalization
+ invocation4 := invocation.NewRPCInvocation(constant.GENERIC,
+ []interface{}{
+ "Hello",
+ []string{"java.lang.String"},
+ []hessian.Object{"world"},
+ }, map[string]interface{}{
+ constant.GENERIC_KEY: "true",
+ })
+ // invoke a non-existed method
+ invocation5 := invocation.NewRPCInvocation(constant.GENERIC,
+ []interface{}{
+ "hello11",
+ []string{"java.lang.String"},
+ []hessian.Object{"world"},
+ }, map[string]interface{}{
+ constant.GENERIC_KEY: "true",
+ })
+ // invoke a method with incorrect arguments
+ invocation6 := invocation.NewRPCInvocation(constant.GENERIC,
+ []interface{}{
+ "Hello",
+ []string{"java.lang.String", "java.lang.String"},
+ []hessian.Object{"world", "haha"},
+ }, map[string]interface{}{
+ constant.GENERIC_KEY: "true",
+ })
+
+ mockInvoker.EXPECT().Invoke(gomock.All(
+ gomock.Not(invocation1),
+ gomock.Not(invocation2),
+ gomock.Not(invocation3),
+ )).DoAndReturn(
+ func(invocation protocol.Invocation) protocol.Result {
+ switch invocation.MethodName() {
+ case "Hello":
+ who := invocation.Arguments()[0].(string)
+ result, _ := service.Hello(who)
+ return &protocol.RPCResult{
+ Rest: result,
+ }
+ default:
+ panic("this branch shouldn't be reached")
+ }
+ }).AnyTimes()
+
+ result := filter.Invoke(context.Background(), mockInvoker, invocation4)
assert.Nil(t, result.Error())
-}
+ assert.Equal(t, "hello, world", result.Result())
-func TestGenericServiceFilterResponseTestStruct(t *testing.T) {
- ts := &TestStruct{
- AaAa: "aaa",
- BaBa: "bbb",
- XxYy: struct {
- xxXx string `m:"xxXx"`
- Xx string `m:"xx"`
- }{},
- }
- result := &protocol.RPCResult{
- Rest: ts,
- }
- aurguments := []interface{}{
- "MethodOne",
- nil,
- []hessian.Object{nil},
- }
- filter := GetGenericServiceFilter()
- methodName := "$invoke"
- rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil)
- r := filter.OnResponse(context.TODO(), result, nil, rpcInvocation)
- assert.NotNil(t, r.Result())
- assert.Equal(t, reflect.ValueOf(r.Result()).Kind(), reflect.Map)
+ result = filter.Invoke(context.Background(), mockInvoker, invocation5)
+ assert.Equal(t,
+ fmt.Sprintf("\"hello11\" method is not found, service key: %s", ivkUrl.ServiceKey()),
+ fmt.Sprintf("%v", result.Error().(error)))
+
+ result = filter.Invoke(context.Background(), mockInvoker, invocation6)
+ assert.Equal(t,
+ "the number of args(=2) is not matched with \"Hello\" method",
+ fmt.Sprintf("%v", result.Error().(error)))
}
-func TestGenericServiceFilterResponseString(t *testing.T) {
- str := "111"
- result := &protocol.RPCResult{
- Rest: str,
- }
- aurguments := []interface{}{
- "MethodOne",
- nil,
- []hessian.Object{nil},
+func TestServiceFilter_OnResponse(t *testing.T) {
+ filter := &GenericServiceFilter{}
+
+ // invoke a method without errors
+ invocation1 := invocation.NewRPCInvocation(constant.GENERIC,
+ []interface{}{
+ "hello",
+ []interface{}{"java.lang.String"},
+ []interface{}{"world"},
+ }, map[string]interface{}{
+ constant.GENERIC_KEY: "true",
+ })
+
+ rpcResult := &protocol.RPCResult{
+ Rest: "result",
}
- filter := GetGenericServiceFilter()
- methodName := "$invoke"
- rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil)
- r := filter.OnResponse(context.TODO(), result, nil, rpcInvocation)
- assert.NotNil(t, r.Result())
- assert.Equal(t, reflect.ValueOf(r.Result()).Kind(), reflect.String)
+
+ result := filter.OnResponse(context.Background(), rpcResult, nil, invocation1)
+ assert.Equal(t, "result", result.Result())
}
diff --git a/protocol/dubbo/hessian2/hessian_request.go b/protocol/dubbo/hessian2/hessian_request.go
index 94aa34d..b281273 100644
--- a/protocol/dubbo/hessian2/hessian_request.go
+++ b/protocol/dubbo/hessian2/hessian_request.go
@@ -19,7 +19,6 @@ package hessian2
import (
"encoding/binary"
- "reflect"
"strconv"
"strings"
"time"
@@ -39,95 +38,7 @@ import (
/////////////////////////////////////////
func getArgType(v interface{}) string {
- if v == nil {
- return "V"
- }
-
- switch v := v.(type) {
- // Serialized tags for base types
- case nil:
- return "V"
- case bool:
- return "Z"
- case []bool:
- return "[Z"
- case byte:
- return "B"
- case []byte:
- return "[B"
- case int8:
- return "B"
- case []int8:
- return "[B"
- case int16:
- return "S"
- case []int16:
- return "[S"
- case uint16: // Equivalent to Char of Java
- return "C"
- case []uint16:
- return "[C"
- // case rune:
- // return "C"
- case int:
- return "J"
- case []int:
- return "[J"
- case int32:
- return "I"
- case []int32:
- return "[I"
- case int64:
- return "J"
- case []int64:
- return "[J"
- case time.Time:
- return "java.util.Date"
- case []time.Time:
- return "[Ljava.util.Date"
- case float32:
- return "F"
- case []float32:
- return "[F"
- case float64:
- return "D"
- case []float64:
- return "[D"
- case string:
- return "java.lang.String"
- case []string:
- return "[Ljava.lang.String;"
- case []hessian.Object:
- return "[Ljava.lang.Object;"
- case map[interface{}]interface{}:
- // return "java.util.HashMap"
- return "java.util.Map"
- case hessian.POJOEnum:
- return v.(hessian.POJOEnum).JavaClassName()
- // Serialized tags for complex types
- default:
- t := reflect.TypeOf(v)
- if reflect.Ptr == t.Kind() {
- t = reflect.TypeOf(reflect.ValueOf(v).Elem())
- }
- switch t.Kind() {
- case reflect.Struct:
- return "java.lang.Object"
- case reflect.Slice, reflect.Array:
- if t.Elem().Kind() == reflect.Struct {
- return "[Ljava.lang.Object;"
- }
- // return "java.util.ArrayList"
- return "java.util.List"
- case reflect.Map: // Enter here, map may be map[string]int
- return "java.util.Map"
- default:
- return ""
- }
- }
-
- // unreachable
- // return "java.lang.RuntimeException"
+ return GetClassDesc(v)
}
func getArgsTypeList(args []interface{}) (string, error) {
diff --git a/protocol/dubbo/hessian2/java_class.go b/protocol/dubbo/hessian2/java_class.go
new file mode 100644
index 0000000..781ff21
--- /dev/null
+++ b/protocol/dubbo/hessian2/java_class.go
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 hessian2
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "time"
+)
+
+import (
+ hessian "github.com/apache/dubbo-go-hessian2"
+ perrors "github.com/pkg/errors"
+)
+
+var (
+ NilError = perrors.Errorf("object should not be nil")
+ UnexpectedTypeError = perrors.Errorf("object should be a POJO")
+ notBasicClassError = perrors.Errorf("object isn't a basic class")
+)
+
+// GetJavaName returns java name of an object
+func GetJavaName(obj interface{}) (string, error) {
+ if obj == nil {
+ return "", NilError
+ }
+
+ t := reflect.TypeOf(obj)
+
+ // basic types, e.g. bool, int, etc.
+ if jtype, err := getBasicJavaName(t); err == nil {
+ return jtype, nil
+ }
+
+ // complicated types, e.g. array, slice, etc.
+ switch t.Kind() {
+ case reflect.Array, reflect.Slice:
+ sb := &strings.Builder{}
+ itemtyp := t
+ for itemtyp.Kind() == reflect.Array || itemtyp.Kind() == reflect.Slice {
+ sb.WriteString("[]")
+ itemtyp = itemtyp.Elem()
+ }
+ var (
+ javaName string
+ err error
+ )
+ if javaName, err = getBasicJavaName(itemtyp); err != nil {
+ if javaName, err = GetJavaName(reflect.New(itemtyp).Elem().Interface()); err != nil {
+ return "", err
+ }
+ }
+ return fmt.Sprintf("%s%s", javaName, sb), nil
+ case reflect.Map:
+ return "java.util.Map", nil
+ default:
+ pojo, ok := obj.(hessian.POJO)
+ if !ok {
+ return "", UnexpectedTypeError
+ }
+ return pojo.JavaClassName(), nil
+ }
+}
+
+func getBasicJavaName(typ reflect.Type) (string, error) {
+ switch typ.Kind() {
+ case reflect.Bool:
+ return "boolean", nil
+ case reflect.Int, reflect.Int64: // in 64-bit processor, Int takes a 64-bit space
+ return "long", nil
+ case reflect.Int32:
+ return "int", nil
+ case reflect.Int8, reflect.Int16:
+ return "short", nil
+ case reflect.Uint, reflect.Uint64: // in 64-bit processor, Uint takes a 64-bit space
+ return "unsigned long", nil
+ case reflect.Uint32:
+ return "unsigned int", nil
+ case reflect.Uint16:
+ return "unsigned short", nil
+ case reflect.Uint8:
+ return "char", nil
+ case reflect.Float32:
+ return "float", nil
+ case reflect.Float64:
+ return "double", nil
+ case reflect.String:
+ return "java.lang.String", nil
+ }
+
+ return "", notBasicClassError
+}
+
+// GetClassDesc get class desc.
+// - boolean[].class => "[Z"
+// - Object.class => "Ljava/lang/Object;"
+func GetClassDesc(v interface{}) string {
+ if v == nil {
+ return "V"
+ }
+
+ switch v := v.(type) {
+ // Serialized tags for base types
+ case nil:
+ return "V"
+ case bool:
+ return "Z"
+ case []bool:
+ return "[Z"
+ case byte:
+ return "B"
+ case []byte:
+ return "[B"
+ case int8:
+ return "B"
+ case []int8:
+ return "[B"
+ case int16:
+ return "S"
+ case []int16:
+ return "[S"
+ case uint16: // Equivalent to Char of Java
+ return "C"
+ case []uint16:
+ return "[C"
+ // case rune:
+ // return "C"
+ case int:
+ return "J"
+ case []int:
+ return "[J"
+ case int32:
+ return "I"
+ case []int32:
+ return "[I"
+ case int64:
+ return "J"
+ case []int64:
+ return "[J"
+ case time.Time:
+ return "java.util.Date"
+ case []time.Time:
+ return "[Ljava.util.Date"
+ case float32:
+ return "F"
+ case []float32:
+ return "[F"
+ case float64:
+ return "D"
+ case []float64:
+ return "[D"
+ case string:
+ return "java.lang.String"
+ case []string:
+ return "[Ljava.lang.String;"
+ case []hessian.Object:
+ return "[Ljava.lang.Object;"
+ case map[interface{}]interface{}:
+ // return "java.util.HashMap"
+ return "java.util.Map"
+ case hessian.POJOEnum:
+ return v.(hessian.POJOEnum).JavaClassName()
+ // Serialized tags for complex types
+ default:
+ t := reflect.TypeOf(v)
+ if reflect.Ptr == t.Kind() {
+ t = reflect.TypeOf(reflect.ValueOf(v).Elem())
+ }
+ switch t.Kind() {
+ case reflect.Struct:
+ return "java.lang.Object"
+ case reflect.Slice, reflect.Array:
+ if t.Elem().Kind() == reflect.Struct {
+ return "[Ljava.lang.Object;"
+ }
+ // return "java.util.ArrayList"
+ return "java.util.List"
+ case reflect.Map: // Enter here, map may be map[string]int
+ return "java.util.Map"
+ default:
+ return ""
+ }
+ }
+}
diff --git a/protocol/dubbo/hessian2/java_class_test.go b/protocol/dubbo/hessian2/java_class_test.go
new file mode 100644
index 0000000..246f139
--- /dev/null
+++ b/protocol/dubbo/hessian2/java_class_test.go
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 hessian2
+
+import (
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetJavaName(t *testing.T) {
+ _, err := GetJavaName(nil)
+ assert.Equal(t, NilError, err)
+
+ typ, err := GetJavaName(true)
+ assert.Equal(t, "boolean", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName(1)
+ assert.Equal(t, "long", typ)
+ assert.Nil(t, err)
+ typ, err = GetJavaName(int64(1))
+ assert.Equal(t, "long", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName(int32(1))
+ assert.Equal(t, "int", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName(int16(1))
+ assert.Equal(t, "short", typ)
+ assert.Nil(t, err)
+ typ, err = GetJavaName(int8(1))
+ assert.Equal(t, "short", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName(uint(1))
+ assert.Equal(t, "unsigned long", typ)
+ assert.Nil(t, err)
+ typ, err = GetJavaName(uint64(1))
+ assert.Equal(t, "unsigned long", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName(uint32(1))
+ assert.Equal(t, "unsigned int", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName(uint16(1))
+ assert.Equal(t, "unsigned short", typ)
+ assert.Nil(t, err)
+ typ, err = GetJavaName(byte('a'))
+ assert.Equal(t, "char", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName(float32(1.0))
+ assert.Equal(t, "float", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName(1.0)
+ assert.Equal(t, "double", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName("hello")
+ assert.Equal(t, "java.lang.String", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName([]string{"hello"})
+ assert.Equal(t, "java.lang.String[]", typ)
+ assert.Nil(t, err)
+ typ, err = GetJavaName([]*mockPOJOPtr{{}})
+ assert.Equal(t, "org.apache.dubbo.mockPOJOPtr[]", typ)
+ assert.Nil(t, err)
+ typ, err = GetJavaName([]mockPOJO{{}})
+ assert.Equal(t, "org.apache.dubbo.mockPOJO[]", typ)
+ assert.Nil(t, err)
+ typ, err = GetJavaName([][]string{{"hello"}})
+ assert.Equal(t, "java.lang.String[][]", typ)
+ assert.Nil(t, err)
+ typ, err = GetJavaName([][]*mockPOJOPtr{{&mockPOJOPtr{}}})
+ assert.Equal(t, "org.apache.dubbo.mockPOJOPtr[][]", typ)
+ assert.Nil(t, err)
+ typ, err = GetJavaName([1]string{"hello"})
+ assert.Equal(t, "java.lang.String[]", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName(map[string]string{"key1": "value1"})
+ assert.Equal(t, "java.util.Map", typ)
+ assert.Nil(t, err)
+
+ typ, err = GetJavaName(mockPOJO{})
+ assert.Equal(t, "org.apache.dubbo.mockPOJO", typ)
+ assert.Nil(t, err)
+ typ, err = GetJavaName(&mockPOJOPtr{})
+ assert.Equal(t, "org.apache.dubbo.mockPOJOPtr", typ)
+ assert.Nil(t, err)
+
+ _, err = GetJavaName(&mockNonPOJO{})
+ assert.Equal(t, UnexpectedTypeError, err)
+}
+
+type mockPOJOPtr struct {
+ TestField string
+}
+
+func (m *mockPOJOPtr) JavaClassName() string {
+ return "org.apache.dubbo.mockPOJOPtr"
+}
+
+type mockPOJO struct{}
+
+func (m mockPOJO) JavaClassName() string {
+ return "org.apache.dubbo.mockPOJO"
+}
+
+type mockNonPOJO struct{}