Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use safeConstructor instead of constructor when parsing yaml #7437

Merged
merged 7 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -72,14 +72,10 @@ private static List<URL> parseJsonArray(String rawConfig) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,11 +40,10 @@
*/
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public void parseConfiguratorsServiceGroupVersionTest() throws Exception {
}

@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);
Expand All @@ -112,7 +112,7 @@ public void parseConfiguratorsServiceNoRuleTest() {
}

@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);
Expand All @@ -129,7 +129,7 @@ public void parseConfiguratorsAppMultiServicesTest() throws IOException {


@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);
Expand All @@ -144,7 +144,7 @@ public void parseConfiguratorsAppAnyServicesTest() throws IOException {
}

@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);
Expand All @@ -159,7 +159,7 @@ public void parseConfiguratorsAppNoServiceTest() throws IOException {
}

@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);
Expand All @@ -175,7 +175,7 @@ public void parseConsumerSpecificProvidersTest() throws IOException {
}

@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\" ]";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ private void setData(String path, String data) throws Exception {
* </pre>
*/
@Test
public void tagRouterRuleParseTest(){
public void tagRouterRuleParseTest() throws Exception {
String tagRouterRuleConfig = "---\n" +
"force: false\n" +
"runtime: true\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@
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;
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;
Expand All @@ -41,6 +44,7 @@
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;
Expand All @@ -50,6 +54,8 @@
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/>
Expand All @@ -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];
Expand Down Expand Up @@ -677,4 +685,93 @@ public static <T> void updatePropertyIfAbsent(Supplier<T> getterMethod, Consumer
}
}

/**
* 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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,38 @@ public void testRealizeCollectionWithNullElement() {
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
}
Expand Down Expand Up @@ -842,7 +874,9 @@ public static class Parent {
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();
Expand Down Expand Up @@ -879,6 +913,22 @@ public Child getChild() {
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 {
Expand Down