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

liujun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/master by this push:
     new 041f8bb  Use safeConstructor instead of constructor when parsing yaml 
(#7437)
041f8bb is described below

commit 041f8bb0a98a912bd363560d5a2339198dcf79a5
Author: Wu Zhiguo <[email protected]>
AuthorDate: Fri Mar 26 18:58:37 2021 +0800

    Use safeConstructor instead of constructor when parsing yaml (#7437)
---
 .../cluster/configurator/parser/ConfigParser.java  | 24 +++---
 .../config/model/ConditionRuleParser.java          | 14 ++--
 .../cluster/router/tag/model/TagRuleParser.java    | 19 ++---
 .../configurator/parser/ConfigParserTest.java      | 12 +--
 .../dubbo/rpc/cluster/router/TagRouterTest.java    |  2 +-
 .../org/apache/dubbo/common/utils/PojoUtils.java   | 97 ++++++++++++++++++++++
 .../apache/dubbo/common/utils/PojoUtilsTest.java   | 50 +++++++++++
 7 files changed, 181 insertions(+), 37 deletions(-)

diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParser.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParser.java
index 0fc5fa9..8058fb5 100644
--- 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParser.java
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParser.java
@@ -16,33 +16,33 @@
  */
 package org.apache.dubbo.rpc.cluster.configurator.parser;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONValidator;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.PojoUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfigItem;
 import 
org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig;
 
-import org.yaml.snakeyaml.TypeDescription;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONValidator;
 import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-import static org.apache.dubbo.rpc.cluster.Constants.OVERRIDE_PROVIDERS_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
 import static 
org.apache.dubbo.common.constants.RegistryConstants.APP_DYNAMIC_CONFIGURATORS_CATEGORY;
 import static 
org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_CONFIGURATORS_CATEGORY;
+import static org.apache.dubbo.rpc.cluster.Constants.OVERRIDE_PROVIDERS_KEY;
 
 /**
  * Config parser
  */
 public class ConfigParser {
 
-    public static List<URL> parseConfigurators(String rawConfig) {
+    public static List<URL> parseConfigurators(String rawConfig) throws 
Exception {
         // compatible url JsonArray, such as [ "override://xxx", 
"override://xxx" ]
         if (isJsonArray(rawConfig)) {
             return parseJsonArray(rawConfig);
@@ -72,14 +72,10 @@ public class ConfigParser {
         return urls;
     }
 
-    private static <T> T parseObject(String rawConfig) {
-        Constructor constructor = new Constructor(ConfiguratorConfig.class);
-        TypeDescription itemDescription = new 
TypeDescription(ConfiguratorConfig.class);
-        itemDescription.addPropertyParameters("items", ConfigItem.class);
-        constructor.addTypeDescription(itemDescription);
-
-        Yaml yaml = new Yaml(constructor);
-        return yaml.load(rawConfig);
+    private static <T> T parseObject(String rawConfig) throws Exception {
+        Yaml yaml = new Yaml(new SafeConstructor());
+        Map<String, Object> map = yaml.load(rawConfig);
+        return (T) PojoUtils.mapToPojo(map, ConfiguratorConfig.class);
     }
 
     private static List<URL> serviceItemToUrls(ConfigItem item, 
ConfiguratorConfig config) {
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
index 07cc7ed..f64ae00 100644
--- 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
@@ -17,9 +17,12 @@
 package org.apache.dubbo.rpc.cluster.router.condition.config.model;
 
 import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.PojoUtils;
 
 import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
+
+import java.util.Map;
 
 /**
  * %YAML1.2
@@ -37,11 +40,10 @@ import org.yaml.snakeyaml.constructor.Constructor;
  */
 public class ConditionRuleParser {
 
-    public static ConditionRouterRule parse(String rawRule) {
-        Constructor constructor = new Constructor(ConditionRouterRule.class);
-
-        Yaml yaml = new Yaml(constructor);
-        ConditionRouterRule rule = yaml.load(rawRule);
+    public static ConditionRouterRule parse(String rawRule) throws Exception {
+        Yaml yaml = new Yaml(new SafeConstructor());
+        Map<String, Object> map = yaml.load(rawRule);
+        ConditionRouterRule rule = PojoUtils.mapToPojo(map, 
ConditionRouterRule.class);
         rule.setRawRule(rawRule);
         if (CollectionUtils.isEmpty(rule.getConditions())) {
             rule.setValid(false);
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/TagRuleParser.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/TagRuleParser.java
index 4f6669f..d25f3dd 100644
--- 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/TagRuleParser.java
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/TagRuleParser.java
@@ -17,23 +17,22 @@
 package org.apache.dubbo.rpc.cluster.router.tag.model;
 
 import org.apache.dubbo.common.utils.CollectionUtils;
-import org.yaml.snakeyaml.TypeDescription;
+import org.apache.dubbo.common.utils.PojoUtils;
+
 import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
+
+import java.util.Map;
 
 /**
  *
  */
 public class TagRuleParser {
 
-    public static TagRouterRule parse(String rawRule) {
-        Constructor constructor = new Constructor(TagRouterRule.class);
-        TypeDescription tagDescription = new 
TypeDescription(TagRouterRule.class);
-        tagDescription.addPropertyParameters("tags", Tag.class);
-        constructor.addTypeDescription(tagDescription);
-
-        Yaml yaml = new Yaml(constructor);
-        TagRouterRule rule = yaml.load(rawRule);
+    public static TagRouterRule parse(String rawRule) throws Exception {
+        Yaml yaml = new Yaml(new SafeConstructor());
+        Map<String, Object> map = yaml.load(rawRule);
+        TagRouterRule rule = PojoUtils.mapToPojo(map, TagRouterRule.class);
         rule.setRawRule(rawRule);
         if (CollectionUtils.isEmpty(rule.getTags())) {
             rule.setValid(false);
diff --git 
a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParserTest.java
 
b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParserTest.java
index d8d616f..6dcd953 100644
--- 
a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParserTest.java
+++ 
b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParserTest.java
@@ -89,7 +89,7 @@ public class ConfigParserTest {
     }
 
     @Test
-    public void parseConfiguratorsServiceMultiAppsTest() throws IOException {
+    public void parseConfiguratorsServiceMultiAppsTest() throws Exception {
         try (InputStream yamlStream = 
this.getClass().getResourceAsStream("/ServiceMultiApps.yml")) {
             List<URL> urls = 
ConfigParser.parseConfigurators(streamToString(yamlStream));
             Assertions.assertNotNull(urls);
@@ -112,7 +112,7 @@ public class ConfigParserTest {
     }
 
     @Test
-    public void parseConfiguratorsAppMultiServicesTest() throws IOException {
+    public void parseConfiguratorsAppMultiServicesTest() throws Exception {
         try (InputStream yamlStream = 
this.getClass().getResourceAsStream("/AppMultiServices.yml")) {
             String yamlFile = streamToString(yamlStream);
             List<URL> urls = ConfigParser.parseConfigurators(yamlFile);
@@ -129,7 +129,7 @@ public class ConfigParserTest {
 
 
     @Test
-    public void parseConfiguratorsAppAnyServicesTest() throws IOException {
+    public void parseConfiguratorsAppAnyServicesTest() throws Exception {
         try (InputStream yamlStream = 
this.getClass().getResourceAsStream("/AppAnyServices.yml")) {
             List<URL> urls = 
ConfigParser.parseConfigurators(streamToString(yamlStream));
             Assertions.assertNotNull(urls);
@@ -144,7 +144,7 @@ public class ConfigParserTest {
     }
 
     @Test
-    public void parseConfiguratorsAppNoServiceTest() throws IOException {
+    public void parseConfiguratorsAppNoServiceTest() throws Exception {
         try (InputStream yamlStream = 
this.getClass().getResourceAsStream("/AppNoService.yml")) {
             List<URL> urls = 
ConfigParser.parseConfigurators(streamToString(yamlStream));
             Assertions.assertNotNull(urls);
@@ -159,7 +159,7 @@ public class ConfigParserTest {
     }
 
     @Test
-    public void parseConsumerSpecificProvidersTest() throws IOException {
+    public void parseConsumerSpecificProvidersTest() throws Exception {
         try (InputStream yamlStream = 
this.getClass().getResourceAsStream("/ConsumerSpecificProviders.yml")) {
             List<URL> urls = 
ConfigParser.parseConfigurators(streamToString(yamlStream));
             Assertions.assertNotNull(urls);
@@ -175,7 +175,7 @@ public class ConfigParserTest {
     }
 
     @Test
-    public void parseURLJsonArrayCompatible() {
+    public void parseURLJsonArrayCompatible() throws Exception {
 
         String configData = 
"[\"override://0.0.0.0/com.xx.Service?category=configurators&timeout=6666&disabled=true&dynamic=false&enabled=true&group=dubbo&priority=1&version=1.0\"
 ]";
 
diff --git 
a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/TagRouterTest.java
 
b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/TagRouterTest.java
index 0981719..4215575 100644
--- 
a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/TagRouterTest.java
+++ 
b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/TagRouterTest.java
@@ -76,7 +76,7 @@ public class TagRouterTest {
      * </pre>
      */
     @Test
-    public void tagRouterRuleParseTest(){
+    public void tagRouterRuleParseTest() throws Exception {
         String tagRouterRuleConfig = "---\n" +
                 "force: false\n" +
                 "runtime: true\n" +
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
index 29328b2..d3fbe8d 100644
--- 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
@@ -23,6 +23,7 @@ import org.apache.dubbo.common.logger.LoggerFactory;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -30,6 +31,8 @@ import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Proxy;
 import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -41,6 +44,7 @@ import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Properties;
 import java.util.TreeMap;
 import java.util.WeakHashMap;
@@ -50,6 +54,8 @@ import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
+import static org.apache.dubbo.common.utils.ClassUtils.isAssignableFrom;
+
 /**
  * PojoUtils. Travel object deeply, and convert complex type to simple type.
  * <p/>
@@ -68,6 +74,8 @@ public class PojoUtils {
     private static final ConcurrentMap<String, Method> NAME_METHODS_CACHE = 
new ConcurrentHashMap<String, Method>();
     private static final ConcurrentMap<Class<?>, ConcurrentMap<String, Field>> 
CLASS_FIELD_CACHE = new ConcurrentHashMap<Class<?>, ConcurrentMap<String, 
Field>>();
     private static final boolean GENERIC_WITH_CLZ = 
Boolean.parseBoolean(ConfigUtils.getProperty(CommonConstants.GENERIC_WITH_CLZ_KEY,
 "true"));
+    private static final List<Class<?>> CLASS_CAN_BE_STRING = 
Arrays.asList(Byte.class, Short.class, Integer.class,
+            Long.class, Float.class, Double.class, Boolean.class, 
Character.class);
 
     public static Object[] generalize(Object[] objs) {
         Object[] dests = new Object[objs.length];
@@ -678,4 +686,93 @@ public class PojoUtils {
         }
     }
 
+    /**
+     * convert map to a specific class instance
+     *
+     * @param map map wait for convert
+     * @param cls the specified class
+     * @param <T> the type of {@code cls}
+     * @return class instance declare in param {@code cls}
+     * @throws ReflectiveOperationException if the instance creation is failed
+     * @since 2.7.10
+     */
+    public static <T> T mapToPojo(Map<String, Object> map, Class<T> cls) 
throws ReflectiveOperationException {
+        T instance = cls.getDeclaredConstructor().newInstance();
+        Map<String, Field> beanPropertyFields = 
ReflectUtils.getBeanPropertyFields(cls);
+        for (Map.Entry<String, Field> entry : beanPropertyFields.entrySet()) {
+            String name = entry.getKey();
+            Field field = entry.getValue();
+            Object mapObject = map.get(name);
+            if (mapObject == null) {
+                continue;
+            }
+
+            Type type = field.getGenericType();
+            Object fieldObject = getFieldObject(mapObject, type);
+            field.set(instance, fieldObject);
+        }
+
+        return instance;
+    }
+
+    private static Object getFieldObject(Object mapObject, Type fieldType) 
throws ReflectiveOperationException {
+        if (fieldType instanceof Class<?>) {
+            return convertClassType(mapObject, (Class<?>) fieldType);
+        } else if (fieldType instanceof ParameterizedType) {
+            return convertParameterizedType(mapObject, (ParameterizedType) 
fieldType);
+        } else if (fieldType instanceof GenericArrayType || fieldType 
instanceof TypeVariable<?> || fieldType instanceof WildcardType) {
+            // ignore these type currently
+            return null;
+        } else {
+            throw new IllegalArgumentException("Unrecognized Type: " + 
fieldType.toString());
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object convertClassType(Object mapObject, Class<?> type) 
throws ReflectiveOperationException {
+        if (type.isPrimitive() || isAssignableFrom(type, 
mapObject.getClass())) {
+            return mapObject;
+        } else if (Objects.equals(type, String.class) && 
CLASS_CAN_BE_STRING.contains(mapObject.getClass())) {
+            // auto convert specified type to string
+            return mapObject.toString();
+        } else if (mapObject instanceof Map) {
+            return mapToPojo((Map<String, Object>) mapObject, type);
+        } else {
+            // type didn't match and mapObject is not another Map struct.
+            // we just ignore this situation.
+            return null;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object convertParameterizedType(Object mapObject, 
ParameterizedType type) throws ReflectiveOperationException {
+        Type rawType = type.getRawType();
+        if (!isAssignableFrom((Class<?>) rawType, mapObject.getClass())) {
+            return null;
+        }
+
+        Type[] actualTypeArguments = type.getActualTypeArguments();
+        if (isAssignableFrom(Map.class, (Class<?>) rawType)) {
+            Map<Object, Object> map = (Map<Object, Object>) 
mapObject.getClass().getDeclaredConstructor().newInstance();
+            for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) 
mapObject).entrySet()) {
+                Object key = getFieldObject(entry.getKey(), 
actualTypeArguments[0]);
+                Object value = getFieldObject(entry.getValue(), 
actualTypeArguments[1]);
+                map.put(key, value);
+            }
+
+            return map;
+        } else if (isAssignableFrom(Collection.class, (Class<?>) rawType)) {
+            Collection<Object> collection = (Collection<Object>) 
mapObject.getClass().getDeclaredConstructor().newInstance();
+            for (Object m : (Iterable<?>) mapObject) {
+                Object ele = getFieldObject(m, actualTypeArguments[0]);
+                collection.add(ele);
+            }
+
+            return collection;
+        } else {
+            // ignore other type currently
+            return null;
+        }
+    }
+
 }
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
index e615dd9..b093bb2 100644
--- 
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
@@ -760,6 +760,38 @@ public class PojoUtilsTest {
         assertEquals(setResult, setStr);
     }
 
+    @Test
+    public void testMapToPojo() throws Exception {
+        Map<String, Object> map = new HashMap<>();
+        map.put("gender", "male");
+        map.put("age", 40);
+
+        List<Map<String, Object>> children = new ArrayList<>();
+        Map<String, Object> child = new HashMap<>();
+        child.put("gender", "male");
+        child.put("age", 15);
+        children.add(child);
+        map.put("children", children);
+
+        Map<String, Object> features = new HashMap<>();
+        features.put("divorce", false);
+        features.put("money", 0);
+        features.put("height", "177cm");
+        map.put("features", features);
+
+        Parent parent = PojoUtils.mapToPojo(map, Parent.class);
+
+        assertEquals(parent.gender, "male");;
+        assertEquals(parent.getAge(), 40);
+        assertEquals(parent.getChildren().size(), 1);
+        assertEquals(parent.getChildren().get(0).gender, "male");
+        assertEquals(parent.getChildren().get(0).age, 15);
+        assertNotNull(parent.getFeatures());
+        assertEquals(parent.getFeatures().get("divorce"), "false");
+        assertEquals(parent.getFeatures().get("money"), "0");
+        assertEquals(parent.getFeatures().get("height"), "177cm");
+    }
+
     public enum Day {
         SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
     }
@@ -842,7 +874,9 @@ public class PojoUtilsTest {
         String name;
         int age;
         Child child;
+        private List<Child> children;
         private String securityEmail;
+        private Map<String, String> features;
 
         public static Parent getNewParent() {
             return new Parent();
@@ -879,6 +913,22 @@ public class PojoUtilsTest {
         public void setChild(Child child) {
             this.child = child;
         }
+
+        public List<Child> getChildren() {
+            return children;
+        }
+
+        public void setChildren(List<Child> children) {
+            this.children = children;
+        }
+
+        public Map<String, String> getFeatures() {
+            return features;
+        }
+
+        public void setFeatures(Map<String, String> features) {
+            this.features = features;
+        }
     }
 
     public static class Child {

Reply via email to