Repository: apache/dubbo (Java)
Date: 2020-05-06 05:42:12+00:00
Task type: Bug Fix

Issue:
telnet issue
### Environment

* Dubbo version: 2.7.4.1
* Operating System version: macOS Catalina Version 10.15.3
* Java version: 1.8.0_231

### Steps to reproduce this issue

1. 通过telnet调用接口如下
方法(Method)
`public String convert7(UserInfoCondition userInfoCondition) {  }`
参数(Parameter)
`public class UserInfoCondition {
    private List<Map<Integer, UserInfo>> userList;
}`
`public class UserInfo {
    private String accountMobile;
    private String userPassword;
}`

invoke UsesrInfoFacade.convert7({"userList":[{"1":{"accountMobile":"15052234567"}}]})
or
invoke UsesrInfoFacade.convert7({"userList":[{1:{"accountMobile":"15052234567"}}]})

提示如下错误(has a problem)
![image](https://user-images.githubusercontent.com/17045745/77892951-ebd50d00-72a5-11ea-95b0-dd59a68a83cc.png)

到底如何书写这个入参呢?(How to write the parameters)




Issue discussion:
尝试了很多种写法, 也指定了class, 还是失败, 不知道问题出在哪里.
和 #5748 应该是一个问题。 `({"userList":[{"1":{"accountMobile":"15052234567"},"class":"java.util.HashMap"}]}) `这样是可行的

Solution patch:
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java
@@ -417,7 +417,19 @@ private static Object realize0(Object pojo, Class<?> type, Type genericType, fin
             }
 
             if (Map.class.isAssignableFrom(type) || type == Object.class) {
-                final Map<Object, Object> result = createMap(map);
+                final Map<Object, Object> result;
+                // fix issue#5939
+                Type mapKeyType = getKeyTypeForMap(map.getClass());
+                Type typeKeyType = getGenericClassByIndex(genericType, 0);
+                boolean typeMismatch = mapKeyType instanceof Class
+                        && typeKeyType instanceof Class
+                        && !typeKeyType.getTypeName().equals(mapKeyType.getTypeName());
+                if (typeMismatch) {
+                    result = createMap(new HashMap(0));
+                } else {
+                    result = createMap(map);
+                }
+
                 history.put(pojo, result);
                 for (Map.Entry<Object, Object> entry : map.entrySet()) {
                     Type keyType = getGenericClassByIndex(genericType, 0);
@@ -503,6 +515,28 @@ private static Object realize0(Object pojo, Class<?> type, Type genericType, fin
         return pojo;
     }
 
+    /**
+     * Get key type for {@link Map} directly implemented by {@code clazz}.
+     * If {@code clazz} does not implement {@link Map} directly, return {@code null}.
+     *
+     * @param clazz {@link Class}
+     * @return Return String.class for {@link com.alibaba.fastjson.JSONObject}
+     */
+    private static Type getKeyTypeForMap(Class<?> clazz) {
+        Type[] interfaces = clazz.getGenericInterfaces();
+        if (!ArrayUtils.isEmpty(interfaces)) {
+            for (Type type : interfaces) {
+                if (type instanceof ParameterizedType) {
+                    ParameterizedType t = (ParameterizedType)type;
+                    if ("java.util.Map".equals(t.getRawType().getTypeName())) {
+                        return t.getActualTypeArguments()[0];
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
     /**
      * Get parameterized type
      *


Test patch:
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.common.utils;
 
+import com.alibaba.fastjson.JSONObject;
 import org.apache.dubbo.common.model.Person;
 import org.apache.dubbo.common.model.SerializablePerson;
 import org.apache.dubbo.common.model.User;
@@ -279,6 +280,41 @@ public void testMapToInterface() throws Exception {
         assertTrue(message.isUrgent());
     }
 
+    @Test
+    public void testJsonObjectToMap() throws Exception {
+        Method method = PojoUtilsTest.class.getMethod("setMap", Map.class);
+        assertNotNull(method);
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("1", "test");
+        @SuppressWarnings("unchecked")
+        Map<Integer, Object> value = (Map<Integer, Object>)PojoUtils.realize(jsonObject,
+                method.getParameterTypes()[0],
+                method.getGenericParameterTypes()[0]);
+        method.invoke(new PojoUtilsTest(), value);
+        assertEquals("test", value.get(1));
+    }
+
+    @Test
+    public void testListJsonObjectToListMap() throws Exception {
+        Method method = PojoUtilsTest.class.getMethod("setListMap", List.class);
+        assertNotNull(method);
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("1", "test");
+        List<JSONObject> list = new ArrayList<>(1);
+        list.add(jsonObject);
+        @SuppressWarnings("unchecked")
+        List<Map<Integer, Object>> result = (List<Map<Integer, Object>>)PojoUtils.realize(
+                list,
+                method.getParameterTypes()[0],
+                method.getGenericParameterTypes()[0]);
+        method.invoke(new PojoUtilsTest(), result);
+        assertEquals("test", result.get(0).get(1));
+    }
+
+    public void setMap(Map<Integer, Object> map) {}
+
+    public void setListMap(List<Map<Integer, Object>> list) {}
+
     @Test
     public void testException() throws Exception {
         Map map = new HashMap();
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/InvokerTelnetHandlerTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/InvokerTelnetHandlerTest.java
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/InvokerTelnetHandlerTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/InvokerTelnetHandlerTest.java
@@ -154,6 +154,20 @@ public void testInvokeOverriddenMethodBySelect() throws RemotingException {
         assertTrue(result.contains("result: 8") || result.contains("result: \"Dubbo\""));
     }
 
+    @Test
+    public void testInvokeMethodWithMapParameter() throws RemotingException {
+        mockChannel = mock(Channel.class);
+        given(mockChannel.getAttribute("telnet.service")).willReturn(DemoService.class.getName());
+        given(mockChannel.getLocalAddress()).willReturn(NetUtils.toAddress("127.0.0.1:5555"));
+        given(mockChannel.getRemoteAddress()).willReturn(NetUtils.toAddress("127.0.0.1:20886"));
+
+        registerProvider(DemoService.class.getName(), new DemoServiceImpl(), DemoService.class);
+
+        String param = "{1:\"Dubbo\",2:\"test\"}";
+        String result = invoke.telnet(mockChannel, "getMap(" + param + ")");
+        assertTrue(result.contains("result: {1:\"Dubbo\",2:\"test\"}"));
+    }
+
     @Test
     public void testInvokeMultiJsonParamMethod() throws RemotingException {
         mockChannel = mock(Channel.class);
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/service/DemoService.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/service/DemoService.java
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/service/DemoService.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/service/DemoService.java
@@ -66,4 +66,6 @@ public interface DemoService {
     String getPerson(Man man);
 
     String getRemoteApplicationName();
+
+    Map<Integer, Object> getMap(Map<Integer, Object> map);
 }
\ No newline at end of file
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/service/DemoServiceImpl.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/service/DemoServiceImpl.java
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/service/DemoServiceImpl.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/service/DemoServiceImpl.java
@@ -126,4 +126,9 @@ public String getPerson(Man man) {
     public String getRemoteApplicationName() {
         return RpcContext.getContext().getRemoteApplicationName();
     }
+
+    @Override
+    public Map<Integer, Object> getMap(Map<Integer, Object> map) {
+        return map;
+    }
 }
\ No newline at end of file