diff --git a/common/constant/key.go b/common/constant/key.go index 54a3c26919..ae27581f46 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -314,4 +314,5 @@ const ( GenericSerializationDefault = "true" // disable "protobuf-json" temporarily //GenericSerializationProtobuf = "protobuf-json" + GenericSerializationGson = "gson" ) diff --git a/filter/generic/generalizer/gson.go b/filter/generic/generalizer/gson.go new file mode 100644 index 0000000000..ffa2114fbd --- /dev/null +++ b/filter/generic/generalizer/gson.go @@ -0,0 +1,99 @@ +/* + * 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 ( + "encoding/json" + "reflect" + "sync" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common/logger" + "dubbo.apache.org/dubbo-go/v3/protocol/dubbo/hessian2" +) + +var ( + jsonGeneralizer Generalizer + jsonGeneralizerOnce sync.Once +) + +func GetGsonGeneralizer() Generalizer { + jsonGeneralizerOnce.Do(func() { + jsonGeneralizer = &GsonGeneralizer{} + }) + return jsonGeneralizer +} + +type GsonGeneralizer struct{} + +func (GsonGeneralizer) Generalize(obj interface{}) (interface{}, error) { + newObj, ok := obj.(hessian.POJO) + if !ok { + return nil, perrors.Errorf("unexpected type of obj(=%T), wanted is hessian pojo", obj) + } + + jsonbytes, err := json.Marshal(newObj) + if err != nil { + return nil, err + } + + return string(jsonbytes), nil +} + +func (GsonGeneralizer) 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) + } + + // create the target object + ret, ok := reflect.New(typ).Interface().(hessian.POJO) + if !ok { + return nil, perrors.Errorf("the type of obj(=%s) should be hessian pojo", typ) + } + + err := json.Unmarshal([]byte(jsonbytes), ret) + if err != nil { + return nil, err + } + + return ret, nil +} + +func (GsonGeneralizer) 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 +} diff --git a/filter/generic/generalizer/gson_test.go b/filter/generic/generalizer/gson_test.go new file mode 100644 index 0000000000..96006f03b7 --- /dev/null +++ b/filter/generic/generalizer/gson_test.go @@ -0,0 +1,97 @@ +/* + * 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" +) + +var mockGsonGeneralizer = GetGsonGeneralizer() + +type mockGsonParent struct { + Gender, Email, Name string + Age int + Child *mockGsonChild +} + +func (p mockGsonParent) JavaClassName() string { + return "org.apache.dubbo.mockGsonParent" +} + +type mockGsonChild struct { + Gender, Email, Name string + Age int +} + +func (p mockGsonChild) JavaClassName() string { + return "org.apache.dubbo.mockGsonChild" +} + +func TestGsonGeneralizer(t *testing.T) { + c := &mockGsonChild{ + Age: 20, + Gender: "male", + Email: "childName@example.com", + Name: "childName", + } + p := mockGsonParent{ + Age: 30, + Gender: "male", + Email: "enableasync@example.com", + Name: "enableasync", + Child: c, + } + + m, err := mockGsonGeneralizer.Generalize(p) + assert.Nil(t, err) + assert.Equal(t, "{\"Gender\":\"male\",\"Email\":\"enableasync@example.com\",\"Name\":\"enableasync\",\"Age\":30,\"Child\":{\"Gender\":\"male\",\"Email\":\"childName@example.com\",\"Name\":\"childName\",\"Age\":20}}", m) + + r, err := mockGsonGeneralizer.Realize(m, reflect.TypeOf(p)) + assert.Nil(t, err) + rMockParent, ok := r.(*mockGsonParent) + assert.True(t, ok) + // parent + assert.Equal(t, "enableasync", rMockParent.Name) + assert.Equal(t, 30, rMockParent.Age) + // child + assert.Equal(t, "childName", rMockParent.Child.Name) + assert.Equal(t, 20, rMockParent.Child.Age) +} + +func TestGsonPointer(t *testing.T) { + c := &mockGsonChild{ + Age: 20, + Gender: "male", + Email: "childName@example.com", + Name: "childName", + } + + m, err := mockMapGeneralizer.Generalize(c) + assert.Nil(t, err) + newC, err := mockMapGeneralizer.Realize(m, reflect.TypeOf(c)) + assert.Nil(t, err) + rMockChild, ok := newC.(*mockGsonChild) + assert.True(t, ok) + assert.Equal(t, "childName", rMockChild.Name) + assert.Equal(t, 20, rMockChild.Age) +} diff --git a/filter/generic/util.go b/filter/generic/util.go index 61ad4a459e..a7fe467bb5 100644 --- a/filter/generic/util.go +++ b/filter/generic/util.go @@ -69,6 +69,9 @@ func getGeneralizer(generic string) (g generalizer.Generalizer) { switch strings.ToLower(generic) { case constant.GenericSerializationDefault: g = generalizer.GetMapGeneralizer() + case constant.GenericSerializationGson: + g = generalizer.GetGsonGeneralizer() + default: logger.Debugf("\"%s\" is not supported, use the default generalizer(MapGeneralizer)", generic) g = generalizer.GetMapGeneralizer()