【平台化引擎-3】组件工厂—配置化的能力

整体方案

【平台化引擎-1】根据maven坐标—获取到jar的Class文件(URLClassLoader)
【平台化引擎-2】— 参数扁平与结构化操作
在这两篇文章中,我们解决了硬编码后注册maven坐标,可以被JVM运行时感知,并执行相应逻辑。而参数在页面上扁平化展示+配置。JVM可以在运行的时候获取到对应的参数值。

而本篇文章重点描述下如何实现平台化的能力。

平台化的组件工厂.png

运行加载流程:


整体方案.png

实现

2.1 引入QlExpress脚本语言

引入依赖:

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>QLExpress</artifactId>
  <version>3.2.0</version>
</dependency>

QLExpress介绍

和java语法相比,要避免的一些ql写法错误
不支持try{}catch{}
注释目前只支持 /** **/,不支持单行注释 //
不支持java8的lambda表达式
不支持for循环集合操作for (Item item : list)
弱类型语言,请不要定义类型声明,更不要用Template(Map<String, List>之类的)
array的声明不一样
min,max,round,print,println,like,in 都是系统默认函数的关键字,请不要作为变量名

实现demo:用户自定义了函数方法。会传入到表达式引擎中,以便运行自定义方法。

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import org.apache.commons.collections4.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class TestExpress {
    //调试组件
    public static void main(String[] args) throws Exception {
        //方法名
        String methodName = "stringEquals";
        //传入的参数
        List<String> methodArgs = Lists.newArrayList("str1", "str2");
        //传入的脚本语言
        String methodCode = "if(str1==null && str2==null){\n    return true;\n}\nif(str1==null || str2==null) {\n    return false;\n}\nif(str1.equals(str2)) {\n    return true;\n}\nreturn false;";
        //待执行表达式(自定义)
        String userExpress = "stringEquals(a,b)";
        //获取方法表达式
        String functionExpress = getFunctionExpress(methodName, methodCode, methodArgs);

        //获取表达式
        StringBuilder express = new StringBuilder();
        express.append(functionExpress);
        express.append(userExpress);

        //上下文
        Map<String, Object> map = Maps.newHashMap();
        map.put("a", 1);
        map.put("b", 1);
        DefaultContext<String, Object> defaultContext = new DefaultContext<>();
        defaultContext.putAll(map);

        ExpressRunner runner = new ExpressRunner();
        //打印表达式
        System.out.println(express.toString());
        //执行结构
        System.out.println(runner.execute(express.toString(), defaultContext, null, true, false));

    }


    private static String getFunctionExpress(String name, String udfCode, List<String> udfCodeArgs) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("function ");
        stringBuilder.append(name + " ( ");
        if (CollectionUtils.isNotEmpty(udfCodeArgs)) {
            String args = udfCodeArgs.stream().map(arg -> "Object " + arg).collect(Collectors.joining(","));
            stringBuilder.append(args);
        }
        stringBuilder.append(" ){ ");
        stringBuilder.append(udfCode);
        stringBuilder.append(" }; ");
        return stringBuilder.toString();
    }
}

执行结果:

function stringEquals ( Object str1,Object str2 ){ if(str1==null && str2==null){
    return true;
}
if(str1==null || str2==null) {
    return false;
}
if(str1.equals(str2)) {
    return true;
}
return false; }; stringEquals(a,b)
true

2.2 通过jar执行函数

(1)定义UDF函数

用户可以自定义实现UDF函数。

public abstract class UdfFunction {
    protected ClassLoader classLoader;
    public UdfFunction(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }
}

public class ArrayLengthUdfFunction extends UdfFunction {
    public ArrayLengthUdfFunction() {
        super(ClassLoader.getSystemClassLoader());
    }
    public ArrayLengthUdfFunction(ClassLoader classLoader) {
        super(classLoader);
    }
    public static Integer eval(Object input) {
        if (input == null) {
            return 0;
        }
        if (input instanceof Collection) {
            return ((Collection<?>) input).size();
        }
        if (input instanceof CharSequence) {
            return ((CharSequence) input).length();
        }
        if (input.getClass().isArray()) {
            return ((Object[]) input).length;
        }
        if (input instanceof Map) {
            return ((Map<?, ?>) input).size();
        }
        return 1;
    }
}

(2)加载函数的工具类

import com.google.common.collect.Maps;
import com.tellme.platform.exception.FrameworkException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;

public class URLClassLoaderUtil {

    //将加载的类加载器缓存起来
    private static final Map<String, URLClassLoader> loaderMap = Maps.newConcurrentMap();

    public static Class<?> getUDFClass(String jarUrl, String className) throws Exception {
        URLClassLoader classLoader = getClassLoader(jarUrl);
        return Class.forName(className, true, classLoader);
    }

    /**
     * 判断方法是否存在
     */
    public static Method getMethod(Class<?> clazz, String functionName) {
        Method[] methods = clazz.getMethods();
        Optional<Method> methodOptional =
                Arrays.stream(methods).filter(me -> me.getName().equals(functionName)).findFirst();
        if (methodOptional.isEmpty()) {
            throw new FrameworkException("函数:" + functionName + " 不存在");
        }
        return methodOptional.get();
    }

    public static Object execute(Class<?> clazz, String methodName, Object... args) {
        try {
            Class<?>[] argClass = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
            Method method = clazz.getMethod(methodName, argClass);
            return method.invoke(clazz.newInstance(), args);
        } catch (Exception e) {
            throw new FrameworkException("函数执行错误", e);
        }
    }

    public static <T> T getInstanceWithClassloader(Class<T> clazz) throws Exception {
        try {
            Constructor<T> constructor = clazz.getConstructor(ClassLoader.class);
            return constructor.newInstance(clazz.getClassLoader());
        } catch (Exception e) {
            return clazz.newInstance();
        }
    }

    private static synchronized URLClassLoader getClassLoader(String jarUrl) {
        try {
            URL[] urls = new URL[]{new URL(jarUrl)};
            return loaderMap.computeIfAbsent(jarUrl, jar -> new URLClassLoader(urls));
        } catch (Exception e) {
            throw new FrameworkException("类库加载失败", e);
        }
    }
}

2.3 代码实现

(1)获取到平台上的配置

两类配置:

  1. lib:jar私服地址+类全名;
  2. code:参数列表、脚本函数、方法名;
public interface IUdfForQuery {
    UDFConfig getFunctionConfig(String functionName);
}

public class UdfForQueryForMock implements IUdfForQuery {

    private static final Integer EXPIRE_SECONDS = 3600;
    private static final Integer MAXIMUM_SIZE = 10000;
    private static final UdfForQueryForMock INSTANCE = new UdfForQueryForMock();

    private static final LoadingCache<String, UDFConfig> UDF_CACHE =
            CacheBuilder.newBuilder()
                    .concurrencyLevel(5)
                    .expireAfterWrite(Duration.ofSeconds(EXPIRE_SECONDS))
                    .maximumSize(MAXIMUM_SIZE)
                    .build(new CacheLoader<String, UDFConfig>() {
                        @Override
                        public UDFConfig load(String name) throws Exception {
                            //此处获取配置,mock实现,可rpc调用页面配置
                            UDFConfig result = new UDFConfig();
                            //脚本配置
                            if ("stringEquals".equals(name)) {
                                result.setUdfType(2);
                                result.setName("stringEquals");
                                result.setUdfCodeArgs(Lists.newArrayList("str1", "str2"));
                                result.setUdfCode("if(str1==null && str2==null){\n    return true;\n}\nif(str1==null || str2==null) {\n    return false;\n}\nif(str1.equals(str2)) {\n    return true;\n}\nreturn false;");
                            } else if ("convertToList".equals(name)) {
                                result.setUdfType(1);
                                result.setName("convertToList");
                                result.setClassName("com.tellme.ArrayLengthUdfFunction");
                                result.setJarUrl("http://私服地址/xxx.jar");
                            } else if ("len".equals(name)) {
                                result.setUdfType(1);
                                result.setName("len");
                                result.setClassName("com.tellme.ConvertToList");
                                result.setJarUrl("http://私服地址/xxx.jar");
                            } else {
                                throw new FrameworkException("未找到配置");
                            }
                            return result;
                        }
                    });

    private UdfForQueryForMock() {
    }

    public static UdfForQueryForMock getInstance() {
        return INSTANCE;
    }

    @Override
    public UDFConfig getFunctionConfig(String functionName) {
        try {
            return UDF_CACHE.get(functionName);
        } catch (Exception e) {
            throw new FrameworkException("函数:" + functionName + "获取配置失败,", e);
        }
    }
}

model对象:

public class UDFConfig {
    /**
     * udf的配置
     */
    private String name;
    /**
     * udf的类型
     */
    private Integer udfType;
    /**
     * jar的class类名
     */
    private String className;
    /**
     * jar的url地址
     */
    private String jarUrl;
    /**
     * 在线编辑的参数列表
     * eg:"str1", "str2"
     */
    private List<String> udfCodeArgs;
    /**
     * 脚本语言
     */
    private String udfCode;
}

枚举:

public enum UDFTypeEnum {
    LIB(1, "代码仓库"),
    CODE(2, "在线编辑"),
    INNNER(3, "内置函数");
}

(2)表达式组装处理器

convertExpress方法是核心方法:

  1. 完成表达式字符替换(.替换为_);
  2. 完成参数的获取(结构化参数替换为扁平化参数)
  3. 完成脚本函数的构建;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.tellme.platform.exception.FrameworkException;
import com.tellme.platform.express.UDFConfig;
import com.tellme.platform.express.UDFTypeEnum;
import com.tellme.platform.express.rpc.IUdfForQuery;
import com.tellme.platform.express.rpc.UdfForQueryForRpc;
import com.tellme.platform.paramhandler.FlowData;
import lombok.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

public class ExpressBuildHandler {
    private static final Set<String> DEFAULT_KEYWORD = Sets.newHashSet("null", "true", "false");
    public static final int EXPRESS_PARAM = 1;
    public static final int EXPRESS_METHOD = 2;

    public static ExpressInfo convertExpress(String express, FlowData dataInfo) throws Exception {
        ExpressInfo expressInfo = new ExpressInfo();
        //根据字符位置,解析总的表达式,获取到方法名、参数名
        List<ExpressVar> expressVarList = getVarFromExpress(express);
        //重写表达式(替换表达式中的.为_)
        expressInfo.setExpress(replaceSeparatorForExpress(express, expressVarList));
        //构建表达式入参(将需要的入参,从流参数中解析出来)
        expressInfo.setParamMap(getParamMap(expressVarList, dataInfo));
        //构建脚本函数
        expressInfo.setFunctionMap(getFunctionMap(expressVarList));
        return expressInfo;
    }

    public static Map<String, ExpressFunction> getFunctionMap(List<ExpressVar> expressVarList) throws Exception {
        Map<String, ExpressFunction> functionMap = Maps.newHashMap();
        //处理表达式
        for (ExpressVar entry : expressVarList) {
            if (entry.getType() == EXPRESS_METHOD) {
                String functionName = entry.getContext();

                ExpressFunction expressFunction = getExpressFunction(functionName);
                if (Objects.nonNull(expressFunction)) {
                    functionMap.put(functionName, expressFunction);
                }
            }
        }
        return functionMap;
    }

    public static Map<String, Object> getParamMap(List<ExpressVar> expressVarList, FlowData dataInfo) {
        Map<String, Object> paramMap = Maps.newHashMap();
        for (ExpressVar entry : expressVarList) {
            if (entry.getType() == EXPRESS_PARAM) {
                String valKey = replaceSeparatorForKey(entry.getContext());
                if (!DEFAULT_KEYWORD.contains(valKey)) {
                    paramMap.put(valKey, getExpressReplaceValue(entry.getContext(), dataInfo));
                }
            }
        }
        return paramMap;
    }

    /**
     * 重新表达式,替换分隔符
     */
    private static String replaceSeparatorForExpress(String express, List<ExpressVar> expressVarList) {
        int appendStart = 0;
        StringBuilder resultBuilder = new StringBuilder();
        //处理表达式
        for (ExpressVar entry : expressVarList) {
            //替换
            if (entry.getType() == EXPRESS_PARAM) {
                String valKey = replaceSeparatorForKey(entry.getContext());
                Integer start = entry.getStart();
                resultBuilder.append(express, appendStart, start);
                resultBuilder.append(valKey);
                appendStart = entry.getEnd();
            }
        }
        if (appendStart < express.length()) {
            resultBuilder.append(express, appendStart, express.length());
        }
        return resultBuilder.toString();
    }

    private static String replaceSeparatorForKey(String paramFieldName) {
        return paramFieldName.replace(".", "_");
    }


    private static ExpressFunction getExpressFunction(String functionName) throws Exception {
        IUdfForQuery udfForQuery = UdfForQueryForRpc.getInstance();
        //根据方法名,获取到方法配置
        UDFConfig udfConfig = udfForQuery.getFunctionConfig(functionName);
        UDFTypeEnum udfTypeEnum = UDFTypeEnum.of(udfConfig.getUdfType());
        switch (udfTypeEnum) {
            case LIB:
                return getExpressFunctionForLib(functionName, udfConfig);
            case CODE:
                return getExpressFunctionForCode(functionName, udfConfig);
            case INNNER:
                return getExpressFunctionForInner(functionName);
            default:
                return null;
        }
    }
    //内置函数,什么也不做。
    public static ExpressFunction getExpressFunctionForInner(String functionName) {
        return ExpressFunction.builder().function(functionName).build();
    }

    private static Object getExpressReplaceValue(String field, FlowData dataInfo) {
        String stringField = field.trim();
        if (DEFAULT_KEYWORD.contains(stringField)) {
            return stringField;
        }
        Object result = dataInfo.getData(stringField);
        if (Objects.isNull(result)) {
            return null;
        }
        return result;
    }

    private static List<ExpressVar> getVarFromExpress(String express) {
        List<ExpressVar> result = Lists.newArrayList();
        if (StringUtils.isBlank(express)) {
            return result;
        }
        boolean strScope = false;
        boolean varScope = false;
        int varStart = 0;
        int varEnd = 0;
        char currentChar;
        char preChar = 0;
        char[] charList = express.toCharArray();
        for (int i = 0; i < charList.length; i++) {
            currentChar = charList[i];
            if (i - 1 >= 0) {
                preChar = charList[i - 1];
            }
            //遇到引号变换字符串scope
            if (currentChar == '"' && preChar != '\\') {
                strScope = !strScope;
            }
            //非字符串且非变量scope,遇到字母进入变量scope
            if (!strScope && !varScope && Character.isLetter(currentChar)) {
                varScope = true;
                varStart = i;
                varEnd = i + 1;
                continue;
            }
            //当前处于字符串scope,进行累加或结束
            if (varScope) {
                if (Character.isLetterOrDigit(currentChar) || currentChar == '_' || currentChar == '.') {
                    varEnd++;
                    continue;
                }
                varScope = false;
                if (currentChar == '(') {
                    result.add(new ExpressVar(varStart, varEnd, 2, express.substring(varStart, varEnd)));
                } else {
                    result.add(new ExpressVar(varStart, varEnd, 1, express.substring(varStart, varEnd)));
                }
            }
        }
        if (strScope) {
            throw new FrameworkException("字符串不完整");
        }
        if (varScope) {
            result.add(new ExpressVar(varStart, varEnd, 1, express.substring(varStart, varEnd)));
        }
        return result;
    }

    /**
     * 表达式方法解析器 reflection
     */
    public static ExpressFunction getExpressFunctionForLib(String functionName, UDFConfig udfConfig) throws Exception {
        Class<?> clazz;
        try {
            clazz = URLClassLoaderUtil.getUDFClass(udfConfig.getJarUrl(), udfConfig.getClassName());
        } catch (Exception e) {
            throw new FrameworkException(String.format("组件:%s加载失败", udfConfig.getName()), e);
        }
        Method method = URLClassLoaderUtil.getMethod(clazz, "eval");
        Object instance = URLClassLoaderUtil.getInstanceWithClassloader(clazz);
        //返回类配置
        return ExpressFunction.builder()
                .function(functionName)
                .udfTypeEnum(UDFTypeEnum.of(udfConfig.getUdfType()))
                .classInstance(instance)
                .methodName(method.getName())
                .paramTypes(method.getParameterTypes())
                .build();
    }


    public static ExpressFunction getExpressFunctionForCode(String functionName, UDFConfig udfConfig) {
        String udfCode = udfConfig.getUdfCode();
        List<String> udfCodeArgs = udfConfig.getUdfCodeArgs();
        return ExpressFunction.builder()
                .function(functionName)
                .udfTypeEnum(UDFTypeEnum.of(udfConfig.getUdfType()))
                .udfFunctionImpl(getFunctionExpress(functionName, udfCode, udfCodeArgs))
                .build();
    }
    private static String getFunctionExpress(String name, String udfCode, List<String> udfCodeArgs) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("function ");
        stringBuilder.append(name + " ( ");
        if (CollectionUtils.isNotEmpty(udfCodeArgs)) {
            String args = udfCodeArgs.stream().map(arg -> "Object " + arg).collect(Collectors.joining(","));
            stringBuilder.append(args);
        }
        stringBuilder.append(" ){ ");
        stringBuilder.append(udfCode);
        stringBuilder.append(" }; ");
        return stringBuilder.toString();
    }


    @Getter
    @Setter
    @AllArgsConstructor
    public static class ExpressVar {
        private Integer start;
        private Integer end;
        private Integer type;
        private String context;
    }

    @Getter
    @Builder
    @ToString
    public static class ExpressFunction {
        private String function;
        private UDFTypeEnum udfTypeEnum;
        private Object classInstance;
        private String methodName;
        private Class<?>[] paramTypes;
        private String udfFunctionImpl;
    }
    @Getter
    @Setter
    public static class ExpressInfo {
        private String express;
        private Map<String, Object> paramMap;
        private Map<String, ExpressFunction> functionMap;
    }
}

(3)表达式执行处理器

import com.google.common.collect.Maps;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.tellme.platform.express.UDFTypeEnum;
import org.apache.commons.collections4.MapUtils;

import java.util.Map;
import java.util.Objects;

public class ExpressExecuteHandler {

    private static final ExpressExecuteHandler INSTANCE = new ExpressExecuteHandler();
    private final ExpressRunner runner;
    private final Map<String, ExpressBuildHandler.ExpressFunction> expressFunctionMap;

    private ExpressExecuteHandler() {
        this.runner = new ExpressRunner();
        this.expressFunctionMap = Maps.newConcurrentMap();
        //填充基本函数
        try {
            runner.addFunctionOfServiceMethod("toJSON", new InnerQlExpress(), "toJSON", new Class<?>[]{Object.class}, null);
            runner.addFunctionOfServiceMethod("fromJson", new InnerQlExpress(), "fromJson", new Class<?>[]{String.class}, null);
            runner.addFunctionOfServiceMethod("isNotEmpty", new InnerQlExpress(), "isNotEmpty", new Class<?>[]{String.class}, null);
            runner.addFunctionOfServiceMethod("isNotZero", new InnerQlExpress(), "isNotZero", new Class<?>[]{String.class}, null);
        } catch (Exception e) {
            throw new FrameworkException("初始化express函数", e);
        }
    }

    public static ExpressExecuteHandler getInstance() {
        return INSTANCE;
    }

    /**
     * 将外部jar的方法注册到ExpressRunner中,以便在表达式中使用
     */
    public synchronized void setFunction(ExpressBuildHandler.ExpressFunction expressFunction) throws Exception {
        String functionName = expressFunction.getFunction();
        //加载服务中的Function方法(使用Map防止多次注册)
        if (Objects.isNull(expressFunctionMap.get(functionName))) {
            expressFunctionMap.put(functionName, expressFunction);
            runner.addFunctionOfServiceMethod(expressFunction.getFunction(),
                    expressFunction.getClassInstance(),
                    expressFunction.getMethodName(),
                    expressFunction.getParamTypes(), null);
        }
    }

    /**
     * 执行脚本语言
     *
     * @param express        脚本表达式
     * @param defaultContext 环境变量(参数)
     */
    private Object execute(String express, DefaultContext<String, Object> defaultContext) throws Exception {
        return runner.execute(express, defaultContext, null, true, false);
    }

    /**
     * 执行函数
     */
    public Object calculateForResult(ExpressBuildHandler.ExpressInfo expressInfo) throws Exception {
        StringBuilder finalExpress = new StringBuilder();
        if (MapUtils.isNotEmpty(expressInfo.getFunctionMap())) {
            for (ExpressBuildHandler.ExpressFunction expressFunction : expressInfo.getFunctionMap().values()) {
                UDFTypeEnum udfTypeEnum = expressFunction.getUdfTypeEnum();
                if (UDFTypeEnum.LIB.equals(udfTypeEnum)) {
                    this.setFunction(expressFunction);
                }
                if (UDFTypeEnum.CODE.equals(udfTypeEnum)) {
                    finalExpress.append(expressFunction.getUdfFunctionImpl());
                }
            }
        }
        finalExpress.append(expressInfo.getExpress());
        DefaultContext<String, Object> defaultContext = new DefaultContext<>();
        defaultContext.putAll(expressInfo.getParamMap());
        return this.execute(finalExpress.toString(), defaultContext);
    }
}

内置函数:

public class InnerQlExpress {

    public boolean isNotEmpty(String value) {
        return StringUtils.isNotBlank(value);
    }

    public boolean isNotZero(String value) {
        return StringUtils.isNotEmpty(value) && Long.parseLong(value) != 0;
    }

    public Map<String, Object> fromJson(String val) {
        return ObjectMapperUtils.fromJson(val);
    }

    public String toJSON(Object obj) {
        return ObjectMapperUtils.toJSON(obj);
    }
}

2.4 上下文流转对象

主要是处理扁平化对象。ParamHelper工具类详见【平台化引擎-2】— 参数扁平与结构化操作


import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.tellme.platform.maven.helper.ParamHelper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

/**
 * 数据流对象。
 * 1. 取参扁平化;
 * 2. 存参结构化;
 */
public class FlowData implements Serializable {
    private static final long serialVersionUID = -8677073254096521327L;

    private String dataId;
    private final Map<String, Object> data = Maps.newHashMap();

    public FlowData() {
        this.dataId = null;
    }

    public FlowData(String dataId) {
        this.dataId = dataId;
    }
    public FlowData(FlowData flowData) {
        this.dataId = flowData.dataId;
        this.data.putAll(flowData.data);
    }
    public FlowData(String dataId, FlowData flowData) {
        this.dataId = dataId;
        this.data.putAll(flowData.data);
    }
    public String getDataId() {
        return dataId;
    }
    public void setDataId(String dataId) {
        this.dataId = dataId;
    }

    /**
     * 按照路径找到相应值
     */
    public Object getData(String fieldPath) {
        if (StringUtils.isEmpty(fieldPath)) {
            return null;
        }
        String[] splitFields = fieldPath.split("\\.");

        Object curData = data;
        for (String splitField : splitFields) {
            if (Objects.isNull(curData)) {
                return null;
            }
            if (curData instanceof Map) {
                curData = ((Map<?, ?>) curData).get(splitField);
                continue;
            }
            if (curData instanceof List && StringUtils.isNumeric(splitField)
                    && Integer.parseInt(splitField) < ((List<?>) curData).size()) {
                curData = ((List<?>) curData).get(Integer.parseInt(splitField));
                continue;
            }
            return null;
        }
        return curData;
    }

    public Map<String, Object> getData() {
        return data;
    }

    public void putData(String fieldPath, Object data) {
        Map<String, Object> updateMap = Maps.newHashMap();
        updateMap.put(fieldPath, data);
        putData(updateMap);
    }

    public void putData(Map<String, Object> updateMap) {
        updateMap(data, ParamHelper.foldData(updateMap));
    }

    public void putData(FlowData updateData) {
        updateMap(data, updateData.data);
    }

    public synchronized void removeData(String fieldPath) {
        if (StringUtils.isEmpty(fieldPath)) {
            return;
        }
        String[] splitFields = fieldPath.split("\\.");

        Object curData = data;
        for (int i = 0; i < splitFields.length - 1; i++) {
            if (Objects.isNull(curData)) {
                return;
            }
            String splitField = splitFields[i];
            if (curData instanceof Map) {
                curData = ((Map<?, ?>) curData).get(splitField);
                continue;
            }
            if (curData instanceof List && StringUtils.isNumeric(splitField)
                    && Integer.parseInt(splitField) < ((List<?>) curData).size()) {
                curData = ((List<?>) curData).get(Integer.parseInt(splitField));
                continue;
            }
            return;
        }

        String lastSplitField = splitFields[splitFields.length - 1];
        if (curData instanceof Map) {
            ((Map<?, ?>) curData).remove(lastSplitField);

        }
        if (curData instanceof List && StringUtils.isNumeric(lastSplitField)
                && Integer.parseInt(lastSplitField) < ((List<?>) curData).size()) {
            ((List<?>) curData).remove(Integer.parseInt(lastSplitField));
        }

    }

    private synchronized void updateMap(Map<String, Object> mainMap, Map<String, Object> updateMap) {
        if (Objects.isNull(mainMap)) {
            return;
        }
        for (Entry<String, Object> entry : updateMap.entrySet()) {
            String key = entry.getKey();
            Object updateValue = entry.getValue();
            if (mainMap.containsKey(key)) {
                Object mainValue = mainMap.get(key);
                if (mainValue instanceof Map && updateValue instanceof Map) {
                    updateMap((Map<String, Object>) mainValue, (Map<String, Object>) updateValue);
                    continue;
                }
                if (mainValue instanceof List && updateValue instanceof List) {
                    mainMap.put(key, updateValue);
                    continue;
                }
            }
            mainMap.put(key, updateValue);
        }
    }


    /**
     * 找到最后一个有效路径
     */
    private String getValidPath(String fieldPath) {
        if (Objects.isNull(data)) {
            return null;
        }

        List<String> result = Lists.newArrayList();
        String[] splitFields = fieldPath.split("\\.");
        Object curData = data;

        for (String field : splitFields) {
            Object nextData = null;
            if (curData instanceof Map) {
                nextData = ((Map<?, ?>) curData).get(field);
            }
            if (curData instanceof List && StringUtils.isNumeric(field)) {
                nextData = ((List<?>) curData).get(Integer.parseInt(field));
            }
            if (Objects.isNull(nextData)) {
                break;
            }
            result.add(field);
            curData = nextData;
        }
        if (CollectionUtils.isEmpty(result)) {
            return null;
        }
        return StringUtils.join(result, ".");
    }

    @Override
    public String toString() {
        return JSON.toJSONString(data);
    }
}

3. 测试方法

public class Test {
    public static void main(String[] args) throws Exception {

        FlowData param = new FlowData();
        param.putData("vendor_id", 1L);
        param.putData("xxx.yyy", "1");
        param.putData("shards", "123,11");
        System.out.println(JSON.toJSON(param));
        ExpressExecuteHandler executeHandler = ExpressExecuteHandler.getInstance();
        String ep = "!stringEquals(xxx.yyy,\"-124\") && len(convertToList(shards))>1;";
        ExpressBuildHandler.ExpressInfo expressInfo = ExpressBuildHandler.convertExpress(ep, param);
        Object result = executeHandler.calculateForResult(expressInfo);
        System.out.println(result);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,277评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,777评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,946评论 0 245
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,271评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,636评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,767评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,989评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,733评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,457评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,674评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,155评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,518评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,160评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,114评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,898评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,822评论 2 280
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,705评论 2 273

推荐阅读更多精彩内容