diff --git a/common/constant/default.go b/common/constant/default.go index 05461ca6e7..7ab92249ad 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -38,6 +38,8 @@ const ( PREFIX_DEFAULT_KEY = "default." DEFAULT_SERVICE_FILTERS = "echo" DEFAULT_REFERENCE_FILTERS = "" + GENERIC_REFERENCE_FILTERS = "generic" + GENERIC = "$invoke" ECHO = "$echo" ) diff --git a/common/constant/key.go b/common/constant/key.go index bca658b262..ba86c531c9 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -30,6 +30,7 @@ const ( METHODS_KEY = "methods" TIMEOUT_KEY = "timeout" BEAN_NAME_KEY = "bean.name" + GENERIC_KEY = "generic" ) const ( diff --git a/config/config_loader.go b/config/config_loader.go index 0dd0fb7f96..720f65f5de 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -66,6 +66,10 @@ func Load() { logger.Errorf("[consumer config center refresh] %#v", err) } for key, ref := range consumerConfig.References { + if ref.Generic { + genericService := NewGenericService(key) + SetConsumerService(genericService) + } rpcService := GetConsumerService(key) if rpcService == nil { diff --git a/config/generic_service.go b/config/generic_service.go new file mode 100644 index 0000000000..8a4e88df97 --- /dev/null +++ b/config/generic_service.go @@ -0,0 +1,30 @@ +/* + * 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 config + +type GenericService struct { + Invoke func(req []interface{}) (interface{}, error) `dubbo:"$invoke"` + referenceStr string +} + +func NewGenericService(referenceStr string) *GenericService { + return &GenericService{referenceStr: referenceStr} +} + +func (u *GenericService) Reference() string { + return u.referenceStr +} diff --git a/config/reference_config.go b/config/reference_config.go index a5b7d50db5..bbf3c66b06 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -55,6 +55,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"` } func (c *ReferenceConfig) Prefix() string { @@ -110,7 +111,6 @@ func (refconfig *ReferenceConfig) Refer() { regUrl.SubURL = url } } - if len(refconfig.urls) == 1 { refconfig.invoker = extension.GetProtocol(refconfig.urls[0].Protocol).Refer(*refconfig.urls[0]) } else { @@ -157,6 +157,7 @@ func (refconfig *ReferenceConfig) getUrlMap() url.Values { urlMap.Set(constant.RETRIES_KEY, strconv.FormatInt(refconfig.Retries, 10)) urlMap.Set(constant.GROUP_KEY, refconfig.Group) urlMap.Set(constant.VERSION_KEY, refconfig.Version) + urlMap.Set(constant.GENERIC_KEY, strconv.FormatBool(refconfig.Generic)) //getty invoke async or sync urlMap.Set(constant.ASYNC_KEY, strconv.FormatBool(refconfig.async)) @@ -170,7 +171,11 @@ func (refconfig *ReferenceConfig) getUrlMap() url.Values { urlMap.Set(constant.ENVIRONMENT_KEY, consumerConfig.ApplicationConfig.Environment) //filter - urlMap.Set(constant.REFERENCE_FILTER_KEY, mergeValue(consumerConfig.Filter, refconfig.Filter, constant.DEFAULT_REFERENCE_FILTERS)) + var defaultReferenceFilter = constant.DEFAULT_REFERENCE_FILTERS + if refconfig.Generic { + defaultReferenceFilter = constant.GENERIC_REFERENCE_FILTERS + defaultReferenceFilter + } + urlMap.Set(constant.REFERENCE_FILTER_KEY, mergeValue(consumerConfig.Filter, refconfig.Filter, defaultReferenceFilter)) for _, v := range refconfig.Methods { urlMap.Set("methods."+v.Name+"."+constant.LOADBALANCE_KEY, v.Loadbalance) @@ -180,3 +185,11 @@ func (refconfig *ReferenceConfig) getUrlMap() url.Values { return urlMap } +func (refconfig *ReferenceConfig) GenericLoad(id string) { + genericService := NewGenericService(refconfig.id) + SetConsumerService(genericService) + refconfig.id = id + refconfig.Refer() + refconfig.Implement(genericService) + return +} diff --git a/examples/general/dubbo/go-client/app/client.go b/examples/general/dubbo/go-client/app/client.go index e6f1ae96fb..b7ee0e662a 100644 --- a/examples/general/dubbo/go-client/app/client.go +++ b/examples/general/dubbo/go-client/app/client.go @@ -25,16 +25,15 @@ import ( "syscall" "time" ) - import ( - "github.com/apache/dubbo-go-hessian2" + hessian "github.com/apache/dubbo-go-hessian2" ) import ( "github.com/apache/dubbo-go/common/logger" _ "github.com/apache/dubbo-go/common/proxy/proxy_factory" "github.com/apache/dubbo-go/config" - _ "github.com/apache/dubbo-go/protocol/dubbo" + "github.com/apache/dubbo-go/protocol/dubbo" _ "github.com/apache/dubbo-go/registry/protocol" _ "github.com/apache/dubbo-go/filter/impl" @@ -65,7 +64,8 @@ func main() { test1() println("\n\ntest2") test2() - + println("\n\ntest3") + test3() initSignal() } @@ -288,3 +288,24 @@ func test2() { } println("error: %v", err) } +func test3() { + var appName = "UserProviderGer" + var referenceConfig = config.ReferenceConfig{ + InterfaceName: "com.ikurento.user.UserProvider", + Cluster: "failover", + Registry: "hangzhouzk", + Protocol: dubbo.DUBBO, + Generic: true, + } + referenceConfig.GenericLoad(appName) //appName is the unique identification of RPCService + + time.Sleep(3 * time.Second) + println("\n\n\nstart to generic invoke") + resp, err := referenceConfig.GetRPCService().(*config.GenericService).Invoke([]interface{}{"GetUser", []string{"java.lang.String"}, []interface{}{"A003"}}) + if err != nil { + panic(err) + } + println("res: %v\n", resp) + println("succ!") + +} diff --git a/filter/impl/generic_filter.go b/filter/impl/generic_filter.go new file mode 100644 index 0000000000..12cb4c7fa5 --- /dev/null +++ b/filter/impl/generic_filter.go @@ -0,0 +1,125 @@ +/* + * 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 impl + +import ( + "reflect" + "strings" +) +import ( + hessian "github.com/apache/dubbo-go-hessian2" +) +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" + invocation2 "github.com/apache/dubbo-go/protocol/invocation" +) + +const ( + GENERIC = "generic" +) + +func init() { + extension.SetFilter(GENERIC, GetGenericFilter) +} + +// when do a generic invoke, struct need to be map + +type GenericFilter struct{} + +func (ef *GenericFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 { + oldArguments := invocation.Arguments() + var newParams []hessian.Object + if oldParams, ok := oldArguments[2].([]interface{}); ok { + for i := range oldParams { + newParams = append(newParams, hessian.Object(struct2MapAll(oldParams[i]))) + } + } else { + return invoker.Invoke(invocation) + } + newArguments := []interface{}{ + oldArguments[0], + oldArguments[1], + newParams, + } + newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments()) + newInvocation.SetReply(invocation.Reply()) + return invoker.Invoke(newInvocation) + } + return invoker.Invoke(invocation) +} + +func (ef *GenericFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + return result +} + +func GetGenericFilter() filter.Filter { + return &GenericFilter{} +} +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++ { + if v.Field(i).Kind() == reflect.Struct { + if v.Field(i).CanInterface() { + setInMap(result, t.Field(i), struct2MapAll(v.Field(i).Interface())) + } + } else if v.Field(i).Kind() == reflect.Slice { + if v.Field(i).CanInterface() { + setInMap(result, t.Field(i), struct2MapAll(v.Field(i).Interface())) + } + } else { + if v.Field(i).CanInterface() { + setInMap(result, t.Field(i), v.Field(i).Interface()) + } + } + } + return result + } else if t.Kind() == reflect.Slice { + value := reflect.ValueOf(obj) + var 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 { + return obj + } +} +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/impl/generic_filter_test.go b/filter/impl/generic_filter_test.go new file mode 100644 index 0000000000..a71a9db957 --- /dev/null +++ b/filter/impl/generic_filter_test.go @@ -0,0 +1,89 @@ +/* + * 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 impl + +import ( + "reflect" + "testing" +) +import ( + "github.com/stretchr/testify/assert" +) + +func Test_struct2MapAll(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"` + } + testData.AaAa = "1" + testData.BaBa = "1" + testData.CaCa.BaBa = "2" + testData.CaCa.AaAa = "2" + testData.CaCa.XxYy.xxXx = "3" + testData.CaCa.XxYy.Xx = "3" + 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)) + + assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"]).Kind()) + assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].(map[string]interface{})["xxYy"]).Kind()) +} + +type testStruct struct { + AaAa string + BaBa string `m:"baBa"` + XxYy struct { + xxXx string `m:"xxXx"` + Xx string `m:"xx"` + } `m:"xxYy"` +} + +func Test_struct2MapAll_Slice(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{}) + + 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()) +}