You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by la...@apache.org on 2021/07/25 15:43:01 UTC
[dubbo-go] branch 3.0 updated: Ftr: Generic invocation supports
Generalizer (#1315)
This is an automated email from the ASF dual-hosted git repository.
laurence pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/3.0 by this push:
new 9aeb7ff Ftr: Generic invocation supports Generalizer (#1315)
9aeb7ff is described below
commit 9aeb7ff3c612e1920698b52c2c1010cf63328d37
Author: XavierNiu <a...@nxw.name>
AuthorDate: Sun Jul 25 23:42:53 2021 +0800
Ftr: Generic invocation supports Generalizer (#1315)
* generic filter
* sync
* feat: get java name from POJO
* todo: unittests for generic filter
* unittests for generic filter
* map generalizer
* adjust import block
* generic service filter is ready for generalizer
* add unittests for GenericServiceFilter
* ReferenceConfig supports new format of generic key
* go fmt
* fix license
* fix license
* fix bugs encountering in integrated tests
* go fmt
* fix serialization bugs
* ftr: generic invocation supports protobuf-json
* go fmt
* go fmt
* disable protobuf-json
* go fmt
* update comments
---
common/constant/key.go | 8 +
.../constant/{serializtion.go => serialization.go} | 0
common/rpc_service.go | 2 +-
config/config_loader.go | 2 +-
config/reference_config.go | 6 +-
filter/generic/filter.go | 139 ++++-------
filter/generic/filter_test.go | 163 +++++--------
filter/generic/generalizer/example.pb.go | 256 ++++++++++++++++++++
.../generic/generalizer/generalizer.go | 26 ++-
filter/generic/generalizer/map.go | 206 ++++++++++++++++
.../{filter_test.go => generalizer/map_test.go} | 170 +++++++++++---
filter/generic/generalizer/protobuf_json.go | 90 +++++++
filter/generic/generalizer/protobuf_json_test.go | 59 +++++
filter/generic/service_filter.go | 133 +++++------
filter/generic/service_filter_test.go | 259 +++++++++++++--------
filter/generic/util.go | 77 ++++++
go.mod | 1 +
protocol/dubbo/hessian2/hessian_request.go | 91 +-------
protocol/dubbo/hessian2/java_class.go | 200 ++++++++++++++++
protocol/dubbo/hessian2/java_class_test.go | 132 +++++++++++
20 files changed, 1522 insertions(+), 498 deletions(-)
diff --git a/common/constant/key.go b/common/constant/key.go
index 4d258bb..54a3c26 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -307,3 +307,11 @@ const (
// SERVICE_DISCOVERY_KEY indicate which service discovery instance will be used
SERVICE_DISCOVERY_KEY = "service_discovery"
)
+
+// Generic Filter
+
+const (
+ GenericSerializationDefault = "true"
+ // disable "protobuf-json" temporarily
+ //GenericSerializationProtobuf = "protobuf-json"
+)
diff --git a/common/constant/serializtion.go b/common/constant/serialization.go
similarity index 100%
copy from common/constant/serializtion.go
copy to common/constant/serialization.go
diff --git a/common/rpc_service.go b/common/rpc_service.go
index 57c8f1c..224f8c8 100644
--- a/common/rpc_service.go
+++ b/common/rpc_service.go
@@ -366,7 +366,7 @@ func suiteMethod(method reflect.Method) *MethodType {
// The latest return type of the method must be error.
if returnType := mtype.Out(outNum - 1); returnType != typeOfError {
- logger.Warnf("the latest return type %s of method %q is not error", returnType, mname)
+ logger.Debugf(`"%s" method will not be exported because its last return type %v doesn't have error`, mname, returnType)
return nil
}
diff --git a/config/config_loader.go b/config/config_loader.go
index d3752dd..65f0a4a 100644
--- a/config/config_loader.go
+++ b/config/config_loader.go
@@ -160,7 +160,7 @@ func loadConsumerConfig() {
checkRegistries(consumerConfig.Registries, consumerConfig.Registry)
for key, ref := range consumerConfig.References {
- if ref.Generic {
+ if ref.Generic != "" {
genericService := NewGenericService(key)
SetConsumerService(genericService)
}
diff --git a/config/reference_config.go b/config/reference_config.go
index cd90747..c45bf2a 100644
--- a/config/reference_config.go
+++ b/config/reference_config.go
@@ -61,7 +61,7 @@ type ReferenceConfig struct {
Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"`
invoker protocol.Invoker
urls []*common.URL
- Generic bool `yaml:"generic" json:"generic,omitempty" property:"generic"`
+ Generic string `yaml:"generic" json:"generic,omitempty" property:"generic"`
Sticky bool `yaml:"sticky" json:"sticky,omitempty" property:"sticky"`
RequestTimeout string `yaml:"timeout" json:"timeout,omitempty" property:"timeout"`
ForceTag bool `yaml:"force.tag" json:"force.tag,omitempty" property:"force.tag"`
@@ -236,7 +236,7 @@ func (c *ReferenceConfig) getURLMap() url.Values {
urlMap.Set(constant.RETRIES_KEY, c.Retries)
urlMap.Set(constant.GROUP_KEY, c.Group)
urlMap.Set(constant.VERSION_KEY, c.Version)
- urlMap.Set(constant.GENERIC_KEY, strconv.FormatBool(c.Generic))
+ urlMap.Set(constant.GENERIC_KEY, c.Generic)
urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER))
urlMap.Set(constant.PROVIDED_BY, c.ProvidedBy)
urlMap.Set(constant.SERIALIZATION_KEY, c.Serialization)
@@ -262,7 +262,7 @@ func (c *ReferenceConfig) getURLMap() url.Values {
// filter
defaultReferenceFilter := constant.DEFAULT_REFERENCE_FILTERS
- if c.Generic {
+ if c.Generic != "" {
defaultReferenceFilter = constant.GENERIC_REFERENCE_FILTERS + "," + defaultReferenceFilter
}
urlMap.Set(constant.REFERENCE_FILTER_KEY, mergeValue(consumerConfig.Filter, c.Filter, defaultReferenceFilter))
diff --git a/filter/generic/filter.go b/filter/generic/filter.go
index df435bb..4aa2d6e 100644
--- a/filter/generic/filter.go
+++ b/filter/generic/filter.go
@@ -19,9 +19,6 @@ package generic
import (
"context"
- "reflect"
- "strings"
- "time"
)
import (
@@ -31,6 +28,7 @@ import (
import (
"dubbo.apache.org/dubbo-go/v3/common/constant"
"dubbo.apache.org/dubbo-go/v3/common/extension"
+ "dubbo.apache.org/dubbo-go/v3/common/logger"
"dubbo.apache.org/dubbo-go/v3/filter"
"dubbo.apache.org/dubbo-go/v3/protocol"
invocation2 "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
@@ -42,30 +40,52 @@ func init() {
})
}
-// when do a generic invoke, struct need to be map
-
-// nolint
+// Filter ensures the structs are converted to maps, this filter is for consumer
type Filter struct{}
// Invoke turns the parameters to map for generic method
func (f *Filter) 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) {
+
+ mtdname := invocation.MethodName()
+ oldargs := invocation.Arguments()
+
+ types := make([]string, 0, len(oldargs))
+ args := make([]hessian.Object, 0, len(oldargs))
- if oldParams, ok := oldArguments[2].([]interface{}); ok {
- newParams := make([]hessian.Object, 0, len(oldParams))
- for i := range oldParams {
- newParams = append(newParams, hessian.Object(struct2MapAll(oldParams[i])))
+ // 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 := getGeneralizer(generic)
+
+ 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)
}
@@ -75,84 +95,3 @@ func (f *Filter) OnResponse(_ context.Context, result protocol.Result, _ protoco
_ protocol.Invocation) protocol.Result {
return result
}
-
-func struct2MapAll(obj interface{}) interface{} {
- if obj == nil {
- return obj
- }
- t := reflect.TypeOf(obj)
- v := reflect.ValueOf(obj)
- if t.Kind() == reflect.Struct {
- result := make(map[string]interface{}, t.NumField())
- for i := 0; i < t.NumField(); i++ {
- field := t.Field(i)
- value := v.Field(i)
- kind := value.Kind()
- if kind == reflect.Struct || kind == reflect.Slice || kind == reflect.Map {
- if value.CanInterface() {
- tmp := value.Interface()
- if _, ok := tmp.(time.Time); ok {
- setInMap(result, field, tmp)
- continue
- }
- setInMap(result, field, struct2MapAll(tmp))
- }
- } else {
- if value.CanInterface() {
- setInMap(result, field, value.Interface())
- }
- }
- }
- return result
- } else if t.Kind() == reflect.Slice {
- value := reflect.ValueOf(obj)
- newTemps := make([]interface{}, 0, value.Len())
- for i := 0; i < value.Len(); i++ {
- newTemp := struct2MapAll(value.Index(i).Interface())
- newTemps = append(newTemps, newTemp)
- }
- return newTemps
- } else if t.Kind() == 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[convertMapKey(key)] = struct2MapAll(mapV)
- }
- return newTempMap
- } else {
- return obj
- }
-}
-
-func convertMapKey(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:
- return key.String()
- }
-}
-
-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[headerAtoa(structField.Name)] = value
- } else {
- result[tagName] = value
- }
- return
-}
-
-func headerAtoa(a string) (b string) {
- b = strings.ToLower(a[:1]) + a[1:]
- return
-}
diff --git a/filter/generic/filter_test.go b/filter/generic/filter_test.go
index 815a6ae..b516bca 100644
--- a/filter/generic/filter_test.go
+++ b/filter/generic/filter_test.go
@@ -18,120 +18,83 @@
package generic
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 := struct2MapAll(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))
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+ "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
+ "dubbo.apache.org/dubbo-go/v3/protocol/mock"
+)
- 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))
-}
+// test isCallingToGenericService branch
+func TestFilter_Invoke(t *testing.T) {
+ invokeUrl := common.NewURLWithOptions(
+ common.WithParams(url.Values{}),
+ common.WithParamsValue(constant.GENERIC_KEY, constant.GenericSerializationDefault))
+ filter := &Filter{}
-type testStruct struct {
- AaAa string
- BaBa string `m:"baBa"`
- XxYy struct {
- xxXx string `m:"xxXx"`
- Xx string `m:"xx"`
- } `m:"xxYy"`
-}
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
-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 := struct2MapAll(testData).(map[string]interface{})
+ normalInvocation := invocation.NewRPCInvocation("Hello", []interface{}{"arg1"}, make(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))
+ 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, constant.GenericSerializationDefault, invocation.AttachmentsByKey(constant.GENERIC_KEY, ""))
+ return &protocol.RPCResult{}
+ })
- 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())
+ result := filter.Invoke(context.Background(), mockInvoker, normalInvocation)
+ assert.NotNil(t, result)
}
-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{})
+// test isMakingAGenericCall branch
+func TestFilter_InvokeWithGenericCall(t *testing.T) {
+ invokeUrl := common.NewURLWithOptions(
+ common.WithParams(url.Values{}),
+ common.WithParamsValue(constant.GENERIC_KEY, constant.GenericSerializationDefault))
+ filter := &Filter{}
+
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ genericInvocation := invocation.NewRPCInvocation(constant.GENERIC, []interface{}{
+ "hello",
+ []string{"java.lang.String"},
+ []string{"arg1"},
+ }, make(map[string]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 := struct2MapAll(testData)
+ 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, constant.GenericSerializationDefault, invocation.AttachmentsByKey(constant.GENERIC_KEY, ""))
+ return &protocol.RPCResult{}
+ })
- 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])
+ result := filter.Invoke(context.Background(), mockInvoker, genericInvocation)
+ assert.NotNil(t, result)
}
diff --git a/filter/generic/generalizer/example.pb.go b/filter/generic/generalizer/example.pb.go
new file mode 100644
index 0000000..1f6faeb
--- /dev/null
+++ b/filter/generic/generalizer/example.pb.go
@@ -0,0 +1,256 @@
+/*
+ * 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.
+ */
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.26.0
+// protoc v3.17.3
+// source: example.proto
+
+package generalizer
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type ResponseType struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Code int64 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
+ Id int64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"`
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+ Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"`
+}
+
+func (x *ResponseType) Reset() {
+ *x = ResponseType{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_example_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ResponseType) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ResponseType) ProtoMessage() {}
+
+func (x *ResponseType) ProtoReflect() protoreflect.Message {
+ mi := &file_example_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ResponseType.ProtoReflect.Descriptor instead.
+func (*ResponseType) Descriptor() ([]byte, []int) {
+ return file_example_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *ResponseType) GetCode() int64 {
+ if x != nil {
+ return x.Code
+ }
+ return 0
+}
+
+func (x *ResponseType) GetId() int64 {
+ if x != nil {
+ return x.Id
+ }
+ return 0
+}
+
+func (x *ResponseType) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *ResponseType) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+type RequestType struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+}
+
+func (x *RequestType) Reset() {
+ *x = RequestType{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_example_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RequestType) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RequestType) ProtoMessage() {}
+
+func (x *RequestType) ProtoReflect() protoreflect.Message {
+ mi := &file_example_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RequestType.ProtoReflect.Descriptor instead.
+func (*RequestType) Descriptor() ([]byte, []int) {
+ return file_example_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *RequestType) GetId() int64 {
+ if x != nil {
+ return x.Id
+ }
+ return 0
+}
+
+var File_example_proto protoreflect.FileDescriptor
+
+var file_example_proto_rawDesc = []byte{
+ 0x0a, 0x0d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
+ 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x22, 0x60, 0x0a, 0x0c,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04,
+ 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65,
+ 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64,
+ 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+ 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18,
+ 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x1d,
+ 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a,
+ 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x32, 0x52, 0x0a,
+ 0x0e, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
+ 0x40, 0x0a, 0x09, 0x51, 0x75, 0x65, 0x72, 0x79, 0x55, 0x73, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x67,
+ 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x1a, 0x19, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c,
+ 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70,
+ 0x65, 0x42, 0x10, 0x5a, 0x0e, 0x2e, 0x2f, 0x3b, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x69,
+ 0x7a, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_example_proto_rawDescOnce sync.Once
+ file_example_proto_rawDescData = file_example_proto_rawDesc
+)
+
+func file_example_proto_rawDescGZIP() []byte {
+ file_example_proto_rawDescOnce.Do(func() {
+ file_example_proto_rawDescData = protoimpl.X.CompressGZIP(file_example_proto_rawDescData)
+ })
+ return file_example_proto_rawDescData
+}
+
+var file_example_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_example_proto_goTypes = []interface{}{
+ (*ResponseType)(nil), // 0: generalizer.ResponseType
+ (*RequestType)(nil), // 1: generalizer.RequestType
+}
+var file_example_proto_depIdxs = []int32{
+ 1, // 0: generalizer.ExampleService.QueryUser:input_type -> generalizer.RequestType
+ 0, // 1: generalizer.ExampleService.QueryUser:output_type -> generalizer.ResponseType
+ 1, // [1:2] is the sub-list for method output_type
+ 0, // [0:1] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_example_proto_init() }
+func file_example_proto_init() {
+ if File_example_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_example_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ResponseType); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_example_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RequestType); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_example_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_example_proto_goTypes,
+ DependencyIndexes: file_example_proto_depIdxs,
+ MessageInfos: file_example_proto_msgTypes,
+ }.Build()
+ File_example_proto = out.File
+ file_example_proto_rawDesc = nil
+ file_example_proto_goTypes = nil
+ file_example_proto_depIdxs = nil
+}
diff --git a/common/constant/serializtion.go b/filter/generic/generalizer/generalizer.go
similarity index 56%
rename from common/constant/serializtion.go
rename to filter/generic/generalizer/generalizer.go
index 60c51df..c76c7ad 100644
--- a/common/constant/serializtion.go
+++ b/filter/generic/generalizer/generalizer.go
@@ -15,15 +15,21 @@
* limitations under the License.
*/
-package constant
+package generalizer
-const (
- S_Hessian2 byte = 2
- S_Proto byte = 21
-)
+import "reflect"
-const (
- HESSIAN2_SERIALIZATION = "hessian2"
- PROTOBUF_SERIALIZATION = "protobuf"
- MSGPACK_SERIALIZATION = "msgpack"
-)
+type Generalizer interface {
+
+ // Generalize generalizes the object to a general struct.
+ // For example:
+ // map, the type of the `obj` allows a basic type, e.g. string, and a complicated type which is a POJO, see also
+ // `hessian.POJO` at [apache/dubbo-go-hessian2](github.com/apache/dubbo-go-hessian2).
+ Generalize(obj interface{}) (interface{}, error)
+
+ // Realize realizes a general struct, described in `obj`, to an object for Golang.
+ Realize(obj interface{}, typ reflect.Type) (interface{}, error)
+
+ // GetType returns the type of the `obj`
+ GetType(obj interface{}) (string, error)
+}
diff --git a/filter/generic/generalizer/map.go b/filter/generic/generalizer/map.go
new file mode 100644
index 0000000..d649bae
--- /dev/null
+++ b/filter/generic/generalizer/map.go
@@ -0,0 +1,206 @@
+/*
+ * 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 generalizer
+
+import (
+ "reflect"
+ "strings"
+ "sync"
+ "time"
+)
+
+import (
+ hessian "github.com/apache/dubbo-go-hessian2"
+ "github.com/mitchellh/mapstructure"
+ perrors "github.com/pkg/errors"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/logger"
+ "dubbo.apache.org/dubbo-go/v3/protocol/dubbo/hessian2"
+)
+
+var (
+ mapGeneralizer Generalizer
+ mapGeneralizerOnce sync.Once
+)
+
+func GetMapGeneralizer() Generalizer {
+ mapGeneralizerOnce.Do(func() {
+ mapGeneralizer = &MapGeneralizer{}
+ })
+ return mapGeneralizer
+}
+
+type MapGeneralizer struct{}
+
+func (g *MapGeneralizer) Generalize(obj interface{}) (gobj interface{}, err error) {
+ gobj = objToMap(obj)
+ return
+}
+
+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 reflect.ValueOf(newobj).Elem().Interface(), nil
+}
+
+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
+ }
+
+ 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
+ }
+}
+
+// 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
+}
+
+// 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
+}
diff --git a/filter/generic/filter_test.go b/filter/generic/generalizer/map_test.go
similarity index 50%
copy from filter/generic/filter_test.go
copy to filter/generic/generalizer/map_test.go
index 815a6ae..e90f679 100644
--- a/filter/generic/filter_test.go
+++ b/filter/generic/generalizer/map_test.go
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package generic
+package generalizer
import (
"reflect"
@@ -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 := struct2MapAll(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
@@ -85,7 +87,7 @@ func TestStruct2MapAllSlice(t *testing.T) {
tmp.XxYy.xxXx = "3"
tmp.XxYy.Xx = "3"
testData.CaCa = append(testData.CaCa, tmp)
- m := struct2MapAll(testData).(map[string]interface{})
+ m := objToMap(testData).(map[string]interface{})
assert.Equal(t, "1", m["aaAa"].(string))
assert.Equal(t, "1", m["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{}
@@ -120,7 +122,7 @@ func TestStruct2MapAllMap(t *testing.T) {
testData.CaCa["k1"] = "v1"
testData.CaCa["kv2"] = "v2"
testData.IntMap[1] = 1
- m := struct2MapAll(testData)
+ m := objToMap(testData)
assert.Equal(t, reflect.Map, reflect.TypeOf(m).Kind())
mappedStruct := m.(map[string]interface{})
@@ -135,3 +137,113 @@ func TestStruct2MapAllMap(t *testing.T) {
assert.Equal(t, reflect.Map, reflect.TypeOf(intMap).Kind())
assert.Equal(t, 1, intMap.(map[interface{}]interface{})[1])
}
+
+var mockMapGeneralizer = GetMapGeneralizer()
+
+type mockParent struct {
+ Gender, Email, Name string
+ Age int
+ Child *mockChild
+}
+
+func (p mockParent) JavaClassName() string {
+ return "org.apache.dubbo.mockParent"
+}
+
+type mockChild struct {
+ Gender, Email, Name string
+ Age int
+}
+
+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/generic/generalizer/protobuf_json.go b/filter/generic/generalizer/protobuf_json.go
new file mode 100644
index 0000000..a590d0b
--- /dev/null
+++ b/filter/generic/generalizer/protobuf_json.go
@@ -0,0 +1,90 @@
+/*
+ * 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 generalizer
+
+import (
+ "reflect"
+ "sync"
+)
+
+import (
+ perrors "github.com/pkg/errors"
+ "google.golang.org/protobuf/encoding/protojson"
+ "google.golang.org/protobuf/proto"
+)
+
+var (
+ protobufJsonGeneralizer Generalizer
+ protobufJsonGeneralizerOnce sync.Once
+)
+
+func GetProtobufJsonGeneralizer() Generalizer {
+ protobufJsonGeneralizerOnce.Do(func() {
+ protobufJsonGeneralizer = &ProtobufJsonGeneralizer{}
+ })
+ return protobufJsonGeneralizer
+}
+
+// ProtobufJsonGeneralizer generalizes an object to json and realizes an object from json using protobuf.
+// Currently, ProtobufJsonGeneralizer is disabled temporarily until the triple protocol is ready.
+type ProtobufJsonGeneralizer struct{}
+
+func (g *ProtobufJsonGeneralizer) Generalize(obj interface{}) (interface{}, error) {
+ message, ok := obj.(proto.Message)
+ if !ok {
+ return nil, perrors.Errorf("unexpected type of obj(=%T), wanted is proto.Message", obj)
+ }
+
+ jsonbytes, err := protojson.Marshal(message)
+ if err != nil {
+ return nil, err
+ }
+
+ return string(jsonbytes), nil
+}
+
+func (g *ProtobufJsonGeneralizer) Realize(obj interface{}, typ reflect.Type) (interface{}, error) {
+ jsonbytes, ok := obj.(string)
+ if !ok {
+ return nil, perrors.Errorf("unexpected type of obj(=%T), wanted is string", obj)
+ }
+
+ // typ represents a struct instead of a pointer
+ for typ.Kind() == reflect.Ptr {
+ typ = typ.Elem()
+ }
+
+ // create the target object
+ ret, ok := reflect.New(typ).Interface().(proto.Message)
+ if !ok {
+ return nil, perrors.Errorf("the type of obj(=%s) should be proto.Message", typ)
+ }
+
+ // get the values from json
+ err := protojson.Unmarshal([]byte(jsonbytes), ret)
+ if err != nil {
+ return nil, err
+ }
+
+ return ret, nil
+}
+
+// GetType returns empty string for "protobuf-json"
+func (g *ProtobufJsonGeneralizer) GetType(_ interface{}) (string, error) {
+ return "", nil
+}
diff --git a/filter/generic/generalizer/protobuf_json_test.go b/filter/generic/generalizer/protobuf_json_test.go
new file mode 100644
index 0000000..7103141
--- /dev/null
+++ b/filter/generic/generalizer/protobuf_json_test.go
@@ -0,0 +1,59 @@
+/*
+ * 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 generalizer
+
+import (
+ "reflect"
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+func TestProtobufJsonGeneralizer(t *testing.T) {
+ g := GetProtobufJsonGeneralizer()
+
+ req := &RequestType{
+ Id: 1,
+ }
+ reqjson, err := g.Generalize(req)
+ assert.Nil(t, err)
+ rreq, err := g.Realize(reqjson, reflect.TypeOf(req))
+ assert.Nil(t, err)
+ reqobj, ok := rreq.(*RequestType)
+ assert.True(t, ok)
+ assert.Equal(t, req.Id, reqobj.GetId())
+
+ resp := &ResponseType{
+ Code: 200,
+ Id: 1,
+ Name: "xavierniu",
+ Message: "Nice to meet you",
+ }
+ respjson, err := g.Generalize(resp)
+ assert.Nil(t, err)
+ rresp, err := g.Realize(respjson, reflect.TypeOf(resp))
+ assert.Nil(t, err)
+ respobj, ok := rresp.(*ResponseType)
+ assert.True(t, ok)
+ assert.Equal(t, resp.Code, respobj.GetCode())
+ assert.Equal(t, resp.Id, respobj.GetId())
+ assert.Equal(t, resp.Name, respobj.GetName())
+ assert.Equal(t, resp.Message, respobj.GetMessage())
+}
diff --git a/filter/generic/service_filter.go b/filter/generic/service_filter.go
index 1bcd982..7deeaf8 100644
--- a/filter/generic/service_filter.go
+++ b/filter/generic/service_filter.go
@@ -19,12 +19,10 @@ package generic
import (
"context"
- "reflect"
)
import (
hessian "github.com/apache/dubbo-go-hessian2"
- "github.com/mitchellh/mapstructure"
perrors "github.com/pkg/errors"
)
@@ -38,90 +36,95 @@ import (
invocation2 "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
)
-const (
- // nolint
- GENERIC_SERIALIZATION_DEFAULT = "true"
-)
-
func init() {
extension.SetFilter(constant.GenericServiceFilterKey, func() filter.Filter {
return &ServiceFilter{}
})
}
-// nolint
+// ServiceFilter is for Server
type ServiceFilter struct{}
-// Invoke is used to call service method by invocation
func (f *ServiceFilter) 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 {
+ if !isGenericInvocation(invocation) {
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, constant.GenericSerializationDefault)
+ // get generalizer according to value in the `generic`
+ g := getGeneralizer(generic)
+
+ //if strings.ToLower(generic) == constant.GenericSerializationProtobuf {
+ // if len(args) > 1 {
+ // logger.Warnf("\"%s\" only supports one argument, but we get %d arguments actually",
+ // constant.GenericSerializationProtobuf, len(args))
+ // }
+ //}
+
+ 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 (f *ServiceFilter) 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()
+func (f *ServiceFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, invocation protocol.Invocation) protocol.Result {
+ if isGenericInvocation(invocation) && result.Result() != nil {
+ // 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 := getGeneralizer(generic)
+
+ 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(struct2MapAll(v.Interface()))
+ result.SetResult(obj)
}
return result
}
diff --git a/filter/generic/service_filter_test.go b/filter/generic/service_filter_test.go
index f049b27..ccdf930 100644
--- a/filter/generic/service_filter_test.go
+++ b/filter/generic/service_filter_test.go
@@ -19,132 +19,193 @@ package generic
import (
"context"
- "errors"
- "reflect"
+ "fmt"
+ "net/url"
"testing"
)
import (
hessian "github.com/apache/dubbo-go-hessian2"
+ "github.com/golang/mock/gomock"
+ perrors "github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
import (
"dubbo.apache.org/dubbo-go/v3/common"
- "dubbo.apache.org/dubbo-go/v3/common/proxy/proxy_factory"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/filter/generic/generalizer"
"dubbo.apache.org/dubbo-go/v3/protocol"
"dubbo.apache.org/dubbo-go/v3/protocol/invocation"
+ "dubbo.apache.org/dubbo-go/v3/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"),
- },
+func (s *MockHelloService) HelloPB(req *generalizer.RequestType) (*generalizer.ResponseType, error) {
+ if req.GetId() == 1 {
+ return &generalizer.ResponseType{
+ Code: 200,
+ Id: 1,
+ Name: "xavierniu",
+ Message: "Nice to meet you",
+ }, nil
}
- s := &TestService{}
- _, _ = common.ServiceMap.Register("com.test.Path", "testprotocol", "", "", s)
- rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil)
+ return nil, perrors.Errorf("people not found")
+}
+
+func TestServiceFilter_Invoke(t *testing.T) {
filter := &ServiceFilter{}
- 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)
+
+ 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, constant.GenericSerializationDefault))
+ // 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",
+ })
+ // invoke a method without errors using protobuf-json generalization
+ //invocation7 := invocation.NewRPCInvocation(constant.GENERIC,
+ // []interface{}{
+ // "HelloPB",
+ // []string{},
+ // []hessian.Object{"{\"id\":1}"},
+ // }, map[string]interface{}{
+ // constant.GENERIC_KEY: constant.GenericSerializationProtobuf,
+ // })
+
+ 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,
+ }
+ case "HelloPB":
+ req := invocation.Arguments()[0].(*generalizer.RequestType)
+ result, _ := service.HelloPB(req)
+ 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())
+
+ 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)))
+
+ //result = filter.Invoke(context.Background(), mockInvoker, invocation7)
+ //assert.Equal(t, int64(200), result.Result().(*generalizer.ResponseType).GetCode())
+ //assert.Equal(t, int64(1), result.Result().(*generalizer.ResponseType).GetId())
+ //assert.Equal(t, "xavierniu", result.Result().(*generalizer.ResponseType).GetName())
+ //assert.Equal(t, "Nice to meet you", result.Result().(*generalizer.ResponseType).GetMessage())
+
}
-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},
- }
+func TestServiceFilter_OnResponse(t *testing.T) {
filter := &ServiceFilter{}
- 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)
-}
-func TestGenericServiceFilterResponseString(t *testing.T) {
- str := "111"
- result := &protocol.RPCResult{
- Rest: str,
- }
- aurguments := []interface{}{
- "MethodOne",
- nil,
- []hessian.Object{nil},
+ // 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 := &ServiceFilter{}
- 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/filter/generic/util.go b/filter/generic/util.go
new file mode 100644
index 0000000..61ad4a4
--- /dev/null
+++ b/filter/generic/util.go
@@ -0,0 +1,77 @@
+/*
+ * 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 generic
+
+import (
+ "strings"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/common/logger"
+ "dubbo.apache.org/dubbo-go/v3/filter/generic/generalizer"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+// 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
+}
+
+// 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
+}
+
+// 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 == constant.GenericSerializationDefault
+}
+
+// isGenericInvocation determines if the invocation has generic format
+func isGenericInvocation(invocation protocol.Invocation) bool {
+ return 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:]
+}
+
+// toExport is to upper the first letter
+func toExport(a string) string {
+ return strings.ToUpper(a[:1]) + a[1:]
+}
+
+func getGeneralizer(generic string) (g generalizer.Generalizer) {
+ switch strings.ToLower(generic) {
+ case constant.GenericSerializationDefault:
+ g = generalizer.GetMapGeneralizer()
+ default:
+ logger.Debugf("\"%s\" is not supported, use the default generalizer(MapGeneralizer)", generic)
+ g = generalizer.GetMapGeneralizer()
+ }
+ return
+}
diff --git a/go.mod b/go.mod
index e11fc0c..853ffe9 100644
--- a/go.mod
+++ b/go.mod
@@ -42,6 +42,7 @@ require (
go.uber.org/atomic v1.7.0
go.uber.org/zap v1.16.0
google.golang.org/grpc v1.38.0
+ google.golang.org/protobuf v1.26.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.16.9
k8s.io/apimachinery v0.16.9
diff --git a/protocol/dubbo/hessian2/hessian_request.go b/protocol/dubbo/hessian2/hessian_request.go
index ab564b0..194897a 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{}