This is an automated email from the ASF dual-hosted git repository.

alexstocks pushed a commit to branch 1.5
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/1.5 by this push:
     new 18fa78b  feature: Add a new GenericService that returns provider's 
attachments (#1196)
18fa78b is described below

commit 18fa78b32727706cef9b52b6abb3f568776dacc0
Author: 陈哈哈 <[email protected]>
AuthorDate: Fri Jun 25 22:21:18 2021 +0800

    feature: Add a new GenericService that returns provider's attachments 
(#1196)
    
    * feature: 增加ResultGenericService,支持泛调用获取Provider返回的Attachments.
    
    * fix: split imports, add comments;
    
    * impr: 更新GenericService对Attachment支持的函数签名及对应实现
    
    * rename: Rename generic proxy factory
---
 common/proxy/proxy_factory/generic.go      | 160 +++++++++++++++++++++++++++++
 common/proxy/proxy_factory/generic_test.go |  55 ++++++++++
 config/generic_service_ext.go              |  41 ++++++++
 3 files changed, 256 insertions(+)

diff --git a/common/proxy/proxy_factory/generic.go 
b/common/proxy/proxy_factory/generic.go
new file mode 100644
index 0000000..8b55999
--- /dev/null
+++ b/common/proxy/proxy_factory/generic.go
@@ -0,0 +1,160 @@
+/*
+ * 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 proxy_factory
+
+import (
+       "context"
+       "reflect"
+)
+
+import (
+       "github.com/apache/dubbo-go/common"
+       "github.com/apache/dubbo-go/common/constant"
+       "github.com/apache/dubbo-go/common/logger"
+       "github.com/apache/dubbo-go/common/proxy"
+       "github.com/apache/dubbo-go/protocol"
+       "github.com/apache/dubbo-go/protocol/invocation"
+)
+
+var (
+       typeOfInterface = 
reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem()).Type()
+       typeOfStrMap    = 
reflect.Zero(reflect.TypeOf((*map[string]interface{})(nil)).Elem()).Type()
+       typeOfError     = 
reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()).Type()
+)
+
+// GenericProxyFactory proxy factory, return values with attachments
+type GenericProxyFactory struct {
+}
+
+// NewGenericProxyFactory returns a proxy factory instance
+func NewGenericProxyFactory(_ ...proxy.Option) proxy.ProxyFactory {
+       return &GenericProxyFactory{}
+}
+
+// GetProxy gets a proxy
+func (f *GenericProxyFactory) GetProxy(invoker protocol.Invoker, url 
*common.URL) *proxy.Proxy {
+       return f.GetAsyncProxy(invoker, nil, url)
+}
+
+// GetAsyncProxy gets a async proxy
+func (f *GenericProxyFactory) GetAsyncProxy(invoker protocol.Invoker, callBack 
interface{}, url *common.URL) *proxy.Proxy {
+       attrs := map[string]string{}
+       attrs[constant.ASYNC_KEY] = url.GetParam(constant.ASYNC_KEY, "false")
+       return proxy.NewProxyWithOptions(invoker, callBack, attrs,
+               proxy.WithProxyImplementFunc(NewGenericProxyImplFunc(attrs)))
+}
+
+// GetInvoker gets a invoker
+func (f *GenericProxyFactory) GetInvoker(url *common.URL) protocol.Invoker {
+       return &ProxyInvoker{
+               BaseInvoker: *protocol.NewBaseInvoker(url),
+       }
+}
+
+// NewGenericProxyImplFunc returns a new function with the given attributes,
+// supports only function sign: func Invoke(Context, []{Method, 
[]{Arguments}}) protocol.Result.
+func NewGenericProxyImplFunc(attr map[string]string) proxy.ImplementFunc {
+       return func(p *proxy.Proxy, rpc common.RPCService) {
+               serviceValue := reflect.ValueOf(rpc)
+               serviceElem := serviceValue.Elem()
+               serviceType := serviceElem.Type()
+               numField := serviceElem.NumField()
+               for i := 0; i < numField; i++ {
+                       f := serviceElem.Field(i)
+                       if !(f.Kind() == reflect.Func && f.IsValid() && 
f.CanSet()) {
+                               continue
+                       }
+                       funcField := serviceType.Field(i)
+                       funcName := funcField.Tag.Get("dubbo")
+                       if funcName == "" {
+                               funcName = funcField.Name
+                       }
+                       // Only generic method: Invoke/$invoke
+                       if funcName != "Invoke" && funcName != "$invoke" {
+                               continue
+                       }
+                       // Enforce param types:
+                       // Invoke(Context, []{Method, []{Arguments}}) (result, 
attachments, error)
+                       inNum := funcField.Type.NumIn()
+                       if inNum != 2 {
+                               logger.Errorf("Generic func requires 2 in-arg 
type, func: %s(%s), was: %d",
+                                       funcField.Name, 
funcField.Type.String(), inNum)
+                               continue
+                       }
+                       // Enforce return types
+                       // Invoke(Context, []{Method, []{Arguments}}) (result, 
attachments, error)
+                       outNum := funcField.Type.NumOut()
+                       if outNum != 3 ||
+                               funcField.Type.Out(0) != typeOfInterface ||
+                               funcField.Type.Out(1) != typeOfStrMap ||
+                               funcField.Type.Out(2) != typeOfError {
+                               logger.Errorf("Generic func requires 3 out 
types, func: %s(%s), "+
+                                       "require: (result interface{}, 
attachments map[string]interface{}, err error)",
+                                       funcField.Name, funcField.Type.String())
+                               continue
+                       }
+                       f.Set(reflect.MakeFunc(f.Type(), 
makeResultProxyFunc(funcName, p, attr)))
+               }
+       }
+}
+
+// make a function: func Invoke
+func makeResultProxyFunc(funcName string, proxy *proxy.Proxy, usrAttr 
map[string]string) func([]reflect.Value) []reflect.Value {
+       // Invoke(goctx, []interface{}{service.Method, types, values}) (result, 
attachments, error);
+       return func(funArgs []reflect.Value) []reflect.Value {
+               // Context
+               invCtx := funArgs[0].Interface().(context.Context)
+               invReply := reflect.New(typeOfInterface)
+               invArgs := funArgs[1].Interface().([]interface{})
+               invValues := funArgs[1]
+               inv := invocation.NewRPCInvocationWithOptions(
+                       invocation.WithMethodName(funcName),
+                       invocation.WithCallBack(proxy.GetCallback()),
+                       invocation.WithArguments(invArgs),
+                       
invocation.WithParameterValues([]reflect.Value{invValues}),
+                       invocation.WithReply(invReply.Interface()),
+               )
+               if invCtx == nil {
+                       invCtx = context.Background()
+               }
+               // Build-in attachments
+               for k, value := range usrAttr {
+                       inv.SetAttachments(k, value)
+               }
+               // User context attachments
+               attachments := invCtx.Value(constant.AttachmentKey)
+               if ssmap, ok := attachments.(map[string]string); ok {
+                       for k, v := range ssmap {
+                               inv.SetAttachments(k, v)
+                       }
+               } else if simap, ok := attachments.(map[string]interface{}); ok 
{
+                       for k, v := range simap {
+                               inv.SetAttachments(k, v)
+                       }
+               } else {
+                       logger.Errorf("Attachments requires map[string]string 
OR map[string]interface{}, was: %T", attachments)
+               }
+               // Invoke and unwrap reply value
+               result := proxy.GetInvoker().Invoke(invCtx, inv)
+               if nil == result.Error() {
+                       result.SetResult(invReply.Elem().Interface())
+               }
+               var ret, att, err = result.Result(), result.Attachments(), 
result.Error()
+               return []reflect.Value{reflect.ValueOf(&ret).Elem(), 
reflect.ValueOf(&att).Elem(), reflect.ValueOf(&err).Elem()}
+       }
+}
diff --git a/common/proxy/proxy_factory/generic_test.go 
b/common/proxy/proxy_factory/generic_test.go
new file mode 100644
index 0000000..de280bd
--- /dev/null
+++ b/common/proxy/proxy_factory/generic_test.go
@@ -0,0 +1,55 @@
+/*
+ * 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 proxy_factory
+
+import (
+       "fmt"
+       "testing"
+)
+
+import (
+       "github.com/stretchr/testify/assert"
+)
+
+import (
+       "github.com/apache/dubbo-go/common"
+       "github.com/apache/dubbo-go/protocol"
+)
+
+func TestGetResultProxy(t *testing.T) {
+       proxyFactory := NewGenericProxyFactory()
+       url := common.NewURLWithOptions()
+       proxy := proxyFactory.GetProxy(protocol.NewBaseInvoker(url), url)
+       assert.NotNil(t, proxy)
+}
+
+func TestGetAsyncResultProxy(t *testing.T) {
+       proxyFactory := NewGenericProxyFactory()
+       url := common.NewURLWithOptions()
+       proxy := proxyFactory.GetAsyncProxy(protocol.NewBaseInvoker(url), 
func(res common.CallbackResponse) {
+               fmt.Println("Test callback res:", res)
+       }, url)
+       assert.NotNil(t, proxy)
+}
+
+func TestGetResultInvoker(t *testing.T) {
+       proxyFactory := NewGenericProxyFactory()
+       url := common.NewURLWithOptions()
+       invoker := proxyFactory.GetInvoker(url)
+       assert.True(t, invoker.IsAvailable())
+}
diff --git a/config/generic_service_ext.go b/config/generic_service_ext.go
new file mode 100644
index 0000000..cda1d76
--- /dev/null
+++ b/config/generic_service_ext.go
@@ -0,0 +1,41 @@
+/*
+ * 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
+
+import (
+       "context"
+)
+
+// GenericService2 uses for generic invoke for service call,
+// Unlike GenericService, it returns a tuple (interface{}, 
map[string]interface{}, error) called by DubboRPC,
+// which contains the Attachments data returned by the DubboProvider interface
+type GenericService2 struct {
+       // Invoke this field will inject impl by proxy, returns (result 
interface{}, attachments map[string]interface{}, err error)
+       Invoke       func(ctx context.Context, args []interface{}) 
(interface{}, map[string]interface{}, error) `dubbo:"$invoke"`
+       referenceStr string
+}
+
+// NewGenericService2 returns a GenericService2 instance
+func NewGenericService2(referenceStr string) *GenericService2 {
+       return &GenericService2{referenceStr: referenceStr}
+}
+
+// Reference gets referenceStr from GenericService2
+func (u *GenericService2) Reference() string {
+       return u.referenceStr
+}

Reply via email to