Support process control in aggregation #215

This commit is contained in:
Francis Dong
2021-07-05 15:30:18 +08:00
committed by linwaiwai
parent 520e06f6a3
commit c32c206872
26 changed files with 1900 additions and 17 deletions

View File

@@ -43,6 +43,9 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import we.constants.CommonConstants;
import we.exception.ExecuteScriptException;
import we.fizz.component.ComponentHelper;
import we.fizz.component.IComponent;
import we.fizz.component.StepContextPosition;
import we.fizz.input.ClientInputConfig;
import we.fizz.input.Input;
import we.fizz.input.InputConfig;
@@ -116,14 +119,12 @@ public class Pipeline {
}else {
LinkedList<Step> opSteps = (LinkedList<Step>) steps.clone();
Step step1 = opSteps.removeFirst();
step1.beforeRun(stepContext, null);
Mono<List<StepResponse>> result = createStep(step1).expand(response -> {
Mono<List<StepResponse>> result = runStep(step1, null).expand(response -> {
if (opSteps.isEmpty() || response.isStop()) {
return Mono.empty();
}
Step step = opSteps.pop();
step.beforeRun(stepContext, response);
return createStep(step);
return runStep(step, response);
}).flatMap(response -> Flux.just(response)).collectList();
return result.flatMap(clientResponse -> {
return handleOutput(input);
@@ -131,6 +132,23 @@ public class Pipeline {
}
}
private Mono<StepResponse> runStep(Step step, StepResponse response){
List<IComponent> components = step.getComponents();
if (components != null && components.size() > 0) {
StepContextPosition stepCtxPos = new StepContextPosition(step.getName());
return ComponentHelper.run(components, stepContext, stepCtxPos, (ctx, pos) -> {
step.beforeRun(stepContext, null);
return createStep(step).flatMap(r -> {
ctx.addStepCircleResult(pos.getStepName());
return Mono.just(r);
});
}).flatMap(sr -> Mono.just((StepResponse)sr));
} else {
step.beforeRun(stepContext, null);
return createStep(step);
}
}
private Mono<AggregateResult> handleOutput(Input input){
// 数据转换
long t3 = System.currentTimeMillis();

View File

@@ -22,7 +22,11 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.noear.snack.ONode;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@@ -34,6 +38,12 @@ import org.springframework.web.reactive.function.client.WebClient;
import com.alibaba.fastjson.JSON;
import reactor.core.publisher.Mono;
import we.fizz.component.ComponentHelper;
import we.fizz.component.ComponentTypeEnum;
import we.fizz.component.IComponent;
import we.fizz.component.StepContextPosition;
import we.fizz.component.circle.Circle;
import we.fizz.component.condition.Condition;
import we.fizz.input.Input;
import we.fizz.input.InputConfig;
import we.fizz.input.InputContext;
@@ -57,6 +67,16 @@ public class Step {
private Map<String, InputConfig> requestConfigs = new HashMap<String, InputConfig>();
private List<IComponent> components;
public List<IComponent> getComponents() {
return components;
}
public void setComponents(List<IComponent> components) {
this.components = components;
}
public SoftReference<Pipeline> getWeakPipeline() {
return weakPipeline;
}
@@ -78,6 +98,7 @@ public class Step {
InputConfig inputConfig = InputFactory.createInputConfig(requestConfig);
step.addRequestConfig((String)requestConfig.get("name"), inputConfig);
}
step.setComponents(ComponentHelper.buildComponents((List<Map<String, Object>>) config.get("components")));
return step;
}
}
@@ -112,16 +133,33 @@ public class Step {
public List<Mono> run() {
List<Mono> monos = new ArrayList<Mono>();
for(String name :inputs.keySet()) {
Input input = inputs.get(name);
for(String requestName :inputs.keySet()) {
Input input = inputs.get(requestName);
List<IComponent> components = input.getConfig().getComponents();
if (components != null && components.size() > 0) {
StepContextPosition stepCtxPos = new StepContextPosition(name, requestName);
Mono<Object> result = ComponentHelper.run(components, stepContext, stepCtxPos, (ctx, pos) -> {
if (input.needRun(ctx)) {
return input.run().flatMap(r -> {
ctx.addRequestCircleResult(pos.getStepName(), pos.getRequestName());
return Mono.just(r);
});
}
return Mono.just(new HashMap());
});
monos.add(result);
} else {
if (input.needRun(stepContext)) {
Mono<Map> singleMono = input.run();
monos.add(singleMono);
}
}
}
return monos;
}
public void afeterRun() {
}

View File

@@ -26,6 +26,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.codec.multipart.FilePart;
import com.alibaba.fastjson.JSON;
import we.constants.CommonConstants;
/**
@@ -146,6 +148,8 @@ public class StepContext<K, V> extends ConcurrentHashMap<K, V> {
requests = new HashMap<>();
stepResponse.setRequests(requests);
requests.put(requestName, new HashMap<String, Object>());
}else if(!requests.containsKey(requestName)) {
requests.put(requestName, new HashMap<String, Object>());
}
return (Map<String, Object>) requests.get(requestName);
}
@@ -682,6 +686,161 @@ public class StepContext<K, V> extends ConcurrentHashMap<K, V> {
return request.get(key);
}
/**
* 设置Step的循环对象 <br>
* Set the current circle item of step<br>
* <br>
*
* @param stepName
* @param item
* @param index
*/
public void setStepCircleItem(String stepName, Object item, Integer index) {
StepResponse stepResponse = (StepResponse) this.get(stepName);
if (stepResponse == null) {
return;
}
stepResponse.setItem(item);
stepResponse.setIndex(index);
}
/**
* 添加Step的循环结果<br>
* Add the result of current circle item <br>
* <br>
*
* @param stepName
* @param key
* @param value
*/
public void addStepCircleResult(String stepName) {
StepResponse stepResponse = (StepResponse) this.get(stepName);
if (stepResponse == null) {
return;
}
List<Map<String, Object>> circle = (List<Map<String, Object>>) stepResponse.getCircle();
if (circle == null) {
circle = new ArrayList<>();
stepResponse.setCircle(circle);
}
Map<String, Object> circleResult = new HashMap<>();
circleResult.put("requests", deepCopy(stepResponse.getRequests()));
circleResult.put("result", deepCopy(stepResponse.getResult()));
circleResult.put("item", deepCopy(stepResponse.getItem()));
circleResult.put("index", stepResponse.getIndex());
circle.add(circleResult);
}
/**
* 获取Step的循环对象<br>
* Returns current circle item<br>
*
* @param stepName
*/
public Object getStepItem(String stepName) {
StepResponse stepResponse = (StepResponse) this.get(stepName);
if (stepResponse == null) {
return null;
}
return stepResponse.getItem();
}
/**
* 获取Step的循环结果<br>
* Returns circle result list of step <br>
*
* @param stepName
*/
public List<Map<String, Object>> getStepCircle(String stepName) {
StepResponse stepResponse = (StepResponse) this.get(stepName);
if (stepResponse == null) {
return null;
}
return stepResponse.getCircle();
}
/**
* 设置请求的循环对象<br>
* Set current circle item of request <br>
*
* @param stepName
* @param requestName
* @param item
*/
public void setRequestCircleItem(String stepName, String requestName, Object item, Integer index) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return;
}
request.put("item", item);
request.put("index", index);
}
/**
* 设置请求的循环结果<br>
* Set current circle result of request <br>
*
* @param stepName
* @param requestName
*/
public void addRequestCircleResult(String stepName, String requestName) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return;
}
List<Map<String, Object>> circle = (List<Map<String, Object>>) request.get("circle");
if (circle == null) {
circle = new ArrayList<>();
request.put("circle", circle);
}
Map<String, Object> circleResult = new HashMap<>();
circleResult.put("request", deepCopy(request.get("request")));
circleResult.put("response", deepCopy(request.get("response")));
circleResult.put("item", deepCopy(request.get("item")));
circleResult.put("index", request.get("index"));
circle.add(circleResult);
}
/**
* 获取请求的循环对象<br>
* Returns the current circle item of request<br>
*
* @param stepName
* @param requestName
*/
public List<Map<String, Object>> getRequestCircleItem(String stepName, String requestName) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return null;
}
return (List<Map<String, Object>>) request.get("circle");
}
/**
* 获取请求的循环结果<br>
* Returns circle result list of request<br>
*
* @param stepName
* @param requestName
*/
public Object getRequestCircle(String stepName, String requestName) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return null;
}
return request.get("item");
}
private Object deepCopy(Object obj) {
if(obj == null) {
return obj;
}
if(obj.getClass().isPrimitive()) {
return obj;
}
return JSON.parse(JSON.toJSONString(obj));
}
public ConfigurableApplicationContext getApplicationContext(){
return this.applicationContext;
}

View File

@@ -18,6 +18,7 @@
package we.fizz;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -28,6 +29,12 @@ public class StepResponse {
private Map<String, Map<String, Object>> requests;
private Map result;
private boolean stop;
// circle item
private Object item;
// index of circle item
private Integer index;
// circle results
private List<Map<String,Object>> circle;
public StepResponse(Step aStep, HashMap item, Map<String, Map<String, Object>> requests) {
setStepName(aStep.getName());
@@ -39,6 +46,14 @@ public class StepResponse {
setResult(item);
}
public void addRequest(String requestName, Map<String, Object> requestObj) {
if (this.requests.containsKey(requestName)) {
this.requests.get(requestName).putAll(requestObj);
} else {
this.requests.put(requestName, requestObj);
}
}
public boolean isStop() {
return stop;
}
@@ -63,5 +78,23 @@ public class StepResponse {
public void setResult(Map result) {
this.result = result;
}
public Object getItem() {
return item;
}
public void setItem(Object item) {
this.item = item;
}
public List<Map<String, Object>> getCircle() {
return circle;
}
public void setCircle(List<Map<String, Object>> circle) {
this.circle = circle;
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import org.noear.snack.ONode;
import com.alibaba.fastjson.JSON;
import reactor.core.publisher.Mono;
import we.fizz.StepContext;
import we.fizz.component.circle.Circle;
import we.fizz.component.condition.Condition;
/**
* Condition component
*
* @author Francis Dong
*
*/
public class ComponentHelper {
/**
* Converts step context to ONode
*
* @param stepContext context
* @return
*/
public static ONode toONode(StepContext<String, Object> stepContext) {
ONode o = null;
synchronized (stepContext) {
o = ONode.loadObj(stepContext);
}
return o;
}
public static List<IComponent> buildComponents(List<Map<String, Object>> componentConfig) {
List<IComponent> components = new ArrayList<>();
if (componentConfig != null && componentConfig.size() > 0) {
for (Map<String, Object> m : componentConfig) {
// condition
if (ComponentTypeEnum.CONDITION.getCode().equals(m.get("type"))) {
Condition c = JSON.parseObject(JSON.toJSONString(m), Condition.class);
components.add(c);
}
// circle
if (ComponentTypeEnum.CIRCLE.getCode().equals(m.get("type"))) {
Circle c = JSON.parseObject(JSON.toJSONString(m), Circle.class);
components.add(c);
}
}
}
return components;
}
/**
*
* @param components
* @param stepContext
* @param f
*/
public static Mono<Object> run(List<IComponent> components, StepContext<String, Object> stepContext,
StepContextPosition stepCtxPos, BiFunction<StepContext, StepContextPosition, Mono> f) {
if (components != null && components.size() > 0) {
// conditions before circle component
List<Condition> conditions = new ArrayList<>();
Circle circle = null;
for (IComponent component : components) {
if (ComponentTypeEnum.CIRCLE == component.getType()) {
circle = (Circle) component;
}
if (circle == null && ComponentTypeEnum.CONDITION == component.getType()) {
conditions.add((Condition) component);
}
}
if (conditions != null && conditions.size() > 0) {
ONode ctxNode = toONode(stepContext);
for (Condition c : conditions) {
if (!c.exec(ctxNode)) {
return null;
}
}
}
if (circle != null) {
return circle.exec(stepContext, stepCtxPos, f);
}
// // conditions before circle component
// List<Condition> conditions1 = new ArrayList<>();
// // conditions after circle component
// List<Condition> conditions2 = new ArrayList<>();
// Circle circle = null;
// for (IComponent component : components) {
// if (ComponentTypeEnum.CIRCLE == component.getType()) {
// circle = (Circle) component;
// }
// if (circle == null && ComponentTypeEnum.CONDITION == component.getType()) {
// conditions1.add((Condition) component);
// }
// if (circle != null && ComponentTypeEnum.CONDITION == component.getType()) {
// conditions2.add((Condition) component);
// }
// }
//
// if (conditions1 != null && conditions1.size() > 0) {
// ONode ctxNode = toONode(stepContext);
// for (Condition c : conditions1) {
// if (!c.exec(ctxNode)) {
// return null;
// }
// }
// }
//
// if (circle != null) {
// return circle.exec(stepContext, (ctx) -> {
// boolean canRun = true;
// if (conditions2 != null && conditions2.size() > 0) {
// ONode ctxNode = toONode(ctx);
// for (Condition c : conditions2) {
// if (!c.exec(ctxNode)) {
// canRun = false;
// }
// }
// }
// if (canRun) {
// return f.apply(ctx);
// } else {
// return Mono.empty();
// }
//
// });
// }
}
return Mono.empty();
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component;
/**
* Component Type
*
* @author Francis Dong
*
*/
public enum ComponentTypeEnum {
CONDITION("condition"), CIRCLE("circle");
private String code;
private ComponentTypeEnum(String code) {
this.code = code;
}
public static ComponentTypeEnum getEnumByCode(String code) {
for (ComponentTypeEnum item : ComponentTypeEnum.values()) {
if (item.getCode().equals(code)) {
return item;
}
}
return null;
}
public String getCode() {
return code;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component;
/**
* Data type of fixed value
*
* @author Francis Dong
*
*/
public enum FixedDataTypeEnum{
NUMBER("number"), STRING("string"), BOOLEAN("boolean");
private String code;
private FixedDataTypeEnum(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
public void setCode(String code) {
this.code = code;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component;
/**
* Component interface
*
* @author Francis Dong
*
*/
public interface IComponent {
/**
* Returns component type
* @return
*/
public ComponentTypeEnum getType();
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component;
/**
* Operator
*
* @author Francis Dong
*
*/
public enum OperatorEnum {
EQ("eq"), NE("ne"), GT("gt"), GE("ge"), LT("lt"), LE("le"), CONTAINS("contains"), NOT_CONTAIN("notContain"), CONTAINS_ANY("containsAny"),
IS_NULL("isNull"), IS_NOT_NULL("isNotNull"), IS_BLANK("isBlank"), IS_NOT_BLANK("isNotBlank"), IS_EMPTY("isEmpty"),
IS_NOT_EMPTY("isNotEmpty");
private String code;
private OperatorEnum(String code) {
this.code = code;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component;
/**
* Data type of reference value
*
* @author Francis Dong
*
*/
public enum RefDataTypeEnum {
INT("int"), LONG("long"), FLOAT("float"), DOUBLE("double"), STRING("string"), BOOLEAN("boolean"), ARRAY("array");
private String code;
private RefDataTypeEnum(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
public void setCode(String code) {
this.code = code;
}
}

View File

@@ -0,0 +1,47 @@
/* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component;
import lombok.Data;
/**
*
* @author Francis Dong
*
*/
@Data
public class StepContextPosition {
private String stepName;
private String requestName;
public StepContextPosition(String stepName) {
this.stepName = stepName;
}
public StepContextPosition(String stepName, String requestName) {
this.stepName = stepName;
this.requestName = requestName;
}
public String getPath() {
if (this.requestName == null) {
return this.stepName;
}
return this.stepName + ".requests." + this.requestName;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component;
/**
* Value Type
*
* @author Francis Dong
*
*/
public enum ValueTypeEnum {
FIXED("fixed"), REF("ref");
private String code;
private ValueTypeEnum(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}

View File

@@ -0,0 +1,280 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component.circle;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.noear.snack.ONode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Data;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import we.fizz.StepContext;
import we.fizz.component.ComponentHelper;
import we.fizz.component.ComponentTypeEnum;
import we.fizz.component.IComponent;
import we.fizz.component.StepContextPosition;
import we.fizz.component.ValueTypeEnum;
import we.fizz.component.condition.Condition;
import we.fizz.exception.FizzRuntimeException;
import we.fizz.input.PathMapping;
import we.fizz.input.RPCInput;
/**
* Circle component
*
* @author Francis Dong
*
*/
public class Circle implements IComponent {
private static final String type = ComponentTypeEnum.CIRCLE.getCode();
private String desc;
private ValueTypeEnum dataSourceType;
private Object dataSource;
private List<Condition> execConditions;
private List<Condition> breakConditions;
@Override
public ComponentTypeEnum getType() {
return ComponentTypeEnum.getEnumByCode(type);
}
/**
*
* @param desc [optional] description
* @param dataSourceType [required] type of data source
* @param dataSource [required] data source
* @param execConditions [optional] conditions to execute current circle loop
* item
* @param breakConditions [optional] conditions to break circle
*/
public Circle(String desc, ValueTypeEnum dataSourceType, Object dataSource, List<Condition> execConditions,
List<Condition> breakConditions) {
this.desc = desc;
this.dataSourceType = dataSourceType;
this.dataSource = dataSource;
this.execConditions = execConditions;
this.breakConditions = breakConditions;
}
/**
* Current item
*/
private Object currentItem;
/**
* Index of current item
*/
private Integer index;
/**
* Fixed value of dataSource
*/
private Integer fixedValue;
/**
* Reference value of dataSource
*/
private List<Object> refValue;
private boolean refReadFlag;
private Integer getFixedValue(ONode ctxNode) {
if (fixedValue != null) {
return fixedValue;
}
if (dataSource == null) {
return fixedValue;
}
if (dataSource instanceof Integer || dataSource instanceof Long) {
fixedValue = Integer.valueOf(dataSource.toString());
if (fixedValue.intValue() < 1) {
throw new FizzRuntimeException("invalid data source, fixed data source must be a positive integer");
}
return fixedValue;
} else {
throw new FizzRuntimeException("invalid data source, fixed data source must be a positive integer");
}
}
@SuppressWarnings("unchecked")
private List<Object> getRefValue(ONode ctxNode) {
if (refReadFlag) {
return refValue;
}
Object value = PathMapping.getValueByPath(ctxNode, (String) dataSource);
if (value == null) {
return null;
}
if (value instanceof Collection) {
refValue = (List<Object>) value;
return refValue;
} else {
throw new FizzRuntimeException("invalid data source, referenced data source must be a array");
}
}
/**
* Returns next circle item, returns null if no item left or dataSource is null
*
* @return
*/
public CircleItem next(ONode ctxNode) {
if (ValueTypeEnum.FIXED.equals(dataSourceType)) {
Integer total = this.getFixedValue(ctxNode);
if (index == null) {
index = 0;
currentItem = index;
return new CircleItem(currentItem, index);
} else if (index.intValue() < total.intValue() - 1) {
index = index + 1;
currentItem = index;
return new CircleItem(currentItem, index);
} else {
return null;
}
} else if (ValueTypeEnum.REF.equals(dataSourceType)) {
List<Object> list = this.getRefValue(ctxNode);
if (index == null) {
index = 0;
currentItem = list.get(index);
return new CircleItem(currentItem, index);
} else if (index.intValue() < list.size() - 1) {
index = index + 1;
currentItem = list.get(index);
return new CircleItem(currentItem, index);
} else {
return null;
}
}
return null;
}
/**
* Returns true if execConditions are all true, false otherwise
*
* @param ctxNode
* @return
*/
public boolean canExec(ONode ctxNode) {
if (this.execConditions != null && this.execConditions.size() > 0) {
try {
for (Condition condition : execConditions) {
if (!condition.exec(ctxNode)) {
return false;
}
}
} catch (FizzRuntimeException e) {
throw new FizzRuntimeException(type + " " + e.getMessage(), e.getCause());
}
}
return true;
}
/**
* Returns true if breakConditions are all true, false otherwise
*
* @param ctxNode
* @return
*/
public boolean breakCircle(ONode ctxNode) {
if (this.breakConditions != null && this.breakConditions.size() > 0) {
try {
for (Condition condition : breakConditions) {
if (condition.exec(ctxNode)) {
return true;
}
}
} catch (FizzRuntimeException e) {
throw new FizzRuntimeException(type + " " + e.getMessage(), e.getCause());
}
}
return false;
}
@SuppressWarnings("unchecked")
public Mono<Object> exec(StepContext<String, Object> stepContext, StepContextPosition stepCtxPos,
BiFunction<StepContext, StepContextPosition, Mono> f) {
ONode ctxNode = ComponentHelper.toONode(stepContext);
CircleItem nextItem = this.next(ctxNode);
if (nextItem != null) {
return Mono.just(new CircleItemResult(nextItem, null)).expand(circleItemResult -> {
// put nextItem to step context and ctxNode for further JSON path mapping
CircleItem cItem = circleItemResult.nextItem;
if (stepCtxPos.getRequestName() != null) {
stepContext.setRequestCircleItem(stepCtxPos.getStepName(), stepCtxPos.getRequestName(),
cItem.getItem(), cItem.getIndex());
} else {
stepContext.setStepCircleItem(stepCtxPos.getStepName(), cItem.getItem(), cItem.getIndex());
}
PathMapping.setByPath(ctxNode, stepCtxPos.getPath() + ".item", cItem.getItem(), true);
PathMapping.setByPath(ctxNode, stepCtxPos.getPath() + ".index", cItem.getIndex(), true);
if (!this.canExec(ctxNode)) {
return Mono.just(new CircleItemResult(this.next(ctxNode), null));
}
if (this.breakCircle(ctxNode)) {
return Mono.empty();
}
return f.apply(stepContext, stepCtxPos).flatMap(r -> {
CircleItem nextItem2 = this.next(ctxNode);
if (nextItem2 == null) {
return Mono.empty();
}
return Mono.just(new CircleItemResult(nextItem2, r));
});
}).flatMap(circleItemResult -> Flux.just(circleItemResult)).collectList().flatMap(list -> {
if (list != null && list.size() > 0) {
Collections.reverse(list);
for (int i = 0; i < list.size(); i++) {
if (list.get(i).result != null) {
return Mono.just(list.get(i).result);
}
}
}
return Mono.empty();
});
} else {
return Mono.empty();
}
}
@Data
class CircleItemResult {
private CircleItem nextItem;
private Object result;
public CircleItemResult(CircleItem nextItem, Object result) {
this.nextItem = nextItem;
this.result = result;
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component.circle;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*
* @author Francis Dong
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CircleItem {
private Object item;
private Integer index;
}

View File

@@ -0,0 +1,262 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component.condition;
import java.util.Collection;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.noear.snack.ONode;
import org.springframework.util.CollectionUtils;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import we.fizz.component.ComponentTypeEnum;
import we.fizz.component.IComponent;
import we.fizz.component.OperatorEnum;
import we.fizz.component.RefDataTypeEnum;
import we.fizz.component.ValueTypeEnum;
import we.fizz.exception.FizzRuntimeException;
import we.fizz.input.PathMapping;
/**
* Condition component
*
* @author Francis Dong
*
*/
@Data
@AllArgsConstructor
public class Condition implements IComponent {
private static final String type = ComponentTypeEnum.CONDITION.getCode();
private String desc;
private ConditionValue value1;
private OperatorEnum operator;
private ConditionValue value2;
@Override
public ComponentTypeEnum getType() {
return ComponentTypeEnum.getEnumByCode(type);
}
/**
* Execute condition
*
* @return
*/
@SuppressWarnings({ "rawtypes" })
public boolean exec(ONode ctxNode) {
if (value1 == null || operator == null) {
return false;
}
boolean rs = false;
try {
Object v1 = null;
if (ValueTypeEnum.FIXED.equals(value1.getType())) {
v1 = value1.getValue();
} else {
v1 = PathMapping.getValueByPath(ctxNode, (String) value1.getValue());
v1 = this.cast(value1.getRefDataType(), v1);
}
Object v2 = null;
if (value2 != null && value2.getType() != null) {
if (ValueTypeEnum.FIXED.equals(value2.getType())) {
v2 = value2.getValue();
} else {
v2 = PathMapping.getValueByPath(ctxNode, (String) value2.getValue());
v2 = this.cast(value2.getRefDataType(), v2);
}
}
switch (operator) {
case EQ:
if (v1 == null && v2 == null) {
rs = true;
} else if (v1 != null && v2 != null) {
rs = this.compare(v1, v2) == 0;
}
break;
case NE:
if (v1 == null && v2 == null) {
rs = false;
} else if ((v1 == null && v2 != null) || (v1 != null && v2 == null)) {
rs = true;
} else if (v1 != null && v2 != null) {
rs = this.compare(v1, v2) != 0;
}
break;
case GT:
rs = this.compare(v1, v2) > 0;
break;
case GE:
rs = this.compare(v1, v2) >= 0;
break;
case LT:
rs = this.compare(v1, v2) < 0;
break;
case LE:
rs = this.compare(v1, v2) <= 0;
break;
case CONTAINS:
if (v1 == null) {
rs = false;
}
if (v1 instanceof Collection && !(v2 instanceof Collection)) {
Collection coll1 = (Collection) v1;
Object el = v2;
if (v2 instanceof Integer || v2 instanceof Long) {
el = Long.valueOf(v2.toString());
} else if (v2 instanceof Float || v2 instanceof Double) {
el = Double.valueOf(v2.toString());
}
rs = CollectionUtils.contains(coll1.iterator(), el);
} else if (!(v1 instanceof Collection)) {
throw new FizzRuntimeException("value1 must be a collection");
} else if (v2 instanceof Collection) {
throw new FizzRuntimeException("value2 can not be a collection");
}
break;
case NOT_CONTAIN:
if (v1 == null) {
rs = true;
}
if (v1 instanceof Collection && !(v2 instanceof Collection)) {
Collection coll1 = (Collection) v1;
Object el = v2;
if (v2 instanceof Integer || v2 instanceof Long) {
el = Long.valueOf(v2.toString());
} else if (v2 instanceof Float || v2 instanceof Double) {
el = Double.valueOf(v2.toString());
}
rs = !CollectionUtils.contains(coll1.iterator(), el);
} else if (!(v1 instanceof Collection)) {
throw new FizzRuntimeException("value1 must be a collection");
} else if (v2 instanceof Collection) {
throw new FizzRuntimeException("value2 can not be a collection");
}
break;
case CONTAINS_ANY:
if (v1 == null || v2 == null) {
rs = false;
}
if (v1 instanceof Collection && v2 instanceof Collection) {
Collection coll1 = (Collection) v1;
Collection coll2 = (Collection) v2;
rs = CollectionUtils.containsAny(coll1, coll2);
} else if (!(v1 instanceof Collection)) {
throw new FizzRuntimeException("value1 must be a collection");
} else if (!(v2 instanceof Collection)) {
throw new FizzRuntimeException("value2 must be a collection");
}
break;
case IS_NULL:
rs = v1 == null;
break;
case IS_NOT_NULL:
rs = v1 != null;
break;
case IS_BLANK:
rs = v1 == null || StringUtils.isBlank(v1.toString());
break;
case IS_NOT_BLANK:
rs = v1 != null && StringUtils.isNotBlank(v1.toString());
break;
case IS_EMPTY:
rs = v1 == null || (v1 instanceof Collection && ((Collection) v1).isEmpty())
|| (v1 instanceof Map && ((Map) v1).isEmpty());
break;
case IS_NOT_EMPTY:
if (v1 != null) {
if (v1 instanceof Collection) {
rs = !((Collection) v1).isEmpty();
} else if (v1 instanceof Map) {
rs = !((Map) v1).isEmpty();
}
}
break;
default:
break;
}
} catch (FizzRuntimeException e) {
String message = type + ": " + e.getMessage() + ", data=" + JSON.toJSONString(this);
throw new FizzRuntimeException(message, e.getCause());
}
return rs;
}
@SuppressWarnings("rawtypes")
private int compare(Object v1, Object v2) {
if (v1 == null || v2 == null) {
throw new FizzRuntimeException("value1 and value2 can not be null");
}
if (v1 instanceof Boolean && v2 instanceof Boolean) {
Boolean n1 = (Boolean) v1;
Boolean n2 = (Boolean) v2;
return n1.compareTo(n2);
} else if ((v1 instanceof Integer || v1 instanceof Long || v1 instanceof Float || v1 instanceof Double)
&& (v2 instanceof Integer || v2 instanceof Long || v2 instanceof Float || v2 instanceof Double)) {
// compare value if both are numbers
Double n1 = Double.valueOf(v1.toString());
Double n2 = Double.valueOf(v2.toString());
return n1.compareTo(n2);
} else if (v1 instanceof String && v2 instanceof String) {
String s1 = v1.toString();
String s2 = v2.toString();
return s1.compareTo(s2);
} else {
throw new FizzRuntimeException(
"types of value1 and value2 are not consistent or not supported for comparision");
}
}
private Object cast(RefDataTypeEnum type, Object val) {
if (type != null) {
switch (type) {
case INT:
val = Integer.valueOf(val.toString());
break;
case LONG:
val = Long.valueOf(val.toString());
break;
case FLOAT:
val = Float.valueOf(val.toString());
break;
case DOUBLE:
val = Double.valueOf(val.toString());
break;
case BOOLEAN:
val = Boolean.valueOf(val.toString());
break;
case STRING:
val = val.toString();
break;
}
}
return val;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component.condition;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import we.fizz.component.FixedDataTypeEnum;
import we.fizz.component.RefDataTypeEnum;
import we.fizz.component.ValueTypeEnum;
/**
* Condition value
*
* @author Francis Dong
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ConditionValue {
private ValueTypeEnum type;
private FixedDataTypeEnum fixedDataType;
private RefDataTypeEnum refDataType;
private Object value;
public ConditionValue(ValueTypeEnum type, FixedDataTypeEnum fixedDataType, Object value) {
this.type = type;
this.fixedDataType = fixedDataType;
this.value = value;
}
public ConditionValue(ValueTypeEnum type, RefDataTypeEnum refDataType, Object value) {
this.type = type;
this.refDataType = refDataType;
this.value = value;
}
}

View File

@@ -1,7 +1,11 @@
package we.fizz.exception;
public class FizzRuntimeException extends RuntimeException {
public FizzRuntimeException (String message){
public FizzRuntimeException(String message) {
super(message);
}
public FizzRuntimeException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -18,8 +18,11 @@
package we.fizz.input;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import we.fizz.component.IComponent;
/**
*
* @author linwaiwai
@@ -30,9 +33,8 @@ public class InputConfig {
private InputType type;
protected Map<String, Object> dataMapping;
protected Map<String, Object> configMap;
private Map<String, Object> condition;
private List<IComponent> components;
public Map<String, Object> getCondition() {
return condition;
@@ -72,6 +74,14 @@ public class InputConfig {
this.fallback = fallback;
}
public List<IComponent> getComponents() {
return components;
}
public void setComponents(List<IComponent> components) {
this.components = components;
}
public void parse(){
}

View File

@@ -17,11 +17,13 @@
package we.fizz.input;
import we.fizz.component.ComponentHelper;
import we.fizz.exception.FizzRuntimeException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -56,6 +58,7 @@ public class InputFactory {
}
inputConfig.setType(typeEnum);
inputConfig.setDataMapping((Map<String, Object>) config.get("dataMapping"));
inputConfig.setComponents(ComponentHelper.buildComponents((List<Map<String, Object>>) config.get("components")));
inputConfig.parse();
return inputConfig;
}

View File

@@ -309,6 +309,7 @@ public class PathMapping {
break;
}
}
// upper case header name
if (list.size() > 5 && "headers".equals(list.get(4))) {
String headerName = list.get(5).toUpperCase();

View File

@@ -97,7 +97,7 @@ public class DubboInput extends RPCInput {
Map<String, Object> group = new HashMap<>();
group.put("request", request);
group.put("response", response);
this.stepResponse.getRequests().put(name, group);
this.stepResponse.addRequest(name, group);
request.put("serviceName", config.getServiceName());
request.put("version", config.getVersion());

View File

@@ -91,7 +91,7 @@ public class GrpcInput extends RPCInput implements IInput {
Map<String, Object> group = new HashMap<>();
group.put("request", request);
group.put("response", response);
this.stepResponse.getRequests().put(name, group);
this.stepResponse.addRequest(name, group);
request.put("serviceName", config.getServiceName());
request.put("method", config.getMethod());

View File

@@ -121,7 +121,7 @@ public class RequestInput extends RPCInput implements IInput{
Map<String, Object> group = new HashMap<>();
group.put("request", request);
group.put("response", response);
this.stepResponse.getRequests().put(name, group);
this.stepResponse.addRequest(name, group);
HttpMethod method = HttpMethod.valueOf(config.getMethod().toUpperCase());
request.put("method", method);

View File

@@ -0,0 +1,170 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.noear.snack.ONode;
import we.fizz.component.circle.Circle;
import we.fizz.component.circle.CircleItem;
import we.fizz.component.condition.Condition;
import we.fizz.component.condition.ConditionValue;
/**
*
* @author Francis Dong
*
*/
import we.fizz.input.PathMapping;
class CircleTests {
@Test
void contextLoads() {
}
@SuppressWarnings("rawtypes")
@Test
void testNextFixedDataSource() {
ONode ctxNode = ONode.load(new HashMap());
// FIXED data source
Circle c = new Circle(null, ValueTypeEnum.FIXED, 3, null, null);
CircleItem circleItem = c.next(ctxNode);
assertEquals(0, (Integer) circleItem.getItem());
circleItem = c.next(ctxNode);
assertEquals(1, (Integer) circleItem.getItem());
circleItem = c.next(ctxNode);
assertEquals(2, (Integer) circleItem.getItem());
circleItem = c.next(ctxNode);
assertEquals(null, circleItem);
}
@Test
void testNextRefDataSource() {
ONode ctxNode = ONode.load(new HashMap());
List<String> list1 = new ArrayList<>();
list1.add("1");
list1.add("2");
list1.add("3");
PathMapping.setByPath(ctxNode, "data.list1", list1, true);
// REF data source
Circle c = new Circle(null, ValueTypeEnum.REF, "data.list1", null, null);
CircleItem circleItem = c.next(ctxNode);
assertEquals("1", (String) circleItem.getItem());
circleItem = c.next(ctxNode);
assertEquals("2", (String) circleItem.getItem());
circleItem = c.next(ctxNode);
assertEquals("3", (String) circleItem.getItem());
circleItem = c.next(ctxNode);
assertEquals(null, circleItem);
}
@Test
void testExecCondition() {
ONode ctxNode = ONode.load(new HashMap());
List<String> list1 = new ArrayList<>();
list1.add("0");
list1.add("1");
list1.add("2");
list1.add("3");
list1.add("4");
PathMapping.setByPath(ctxNode, "data.list1", list1, true);
ConditionValue bValue1 = new ConditionValue(ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "3");
ConditionValue bValue2 = new ConditionValue(ValueTypeEnum.REF, RefDataTypeEnum.STRING, "item");
Condition c1 = new Condition(null, bValue1, OperatorEnum.NE, bValue2);
List<Condition> execConditions = new ArrayList<>();
execConditions.add(c1);
Circle circle = new Circle(null, ValueTypeEnum.REF, "data.list1", execConditions, null);
for (int i = 0; i < 5; i++) {
CircleItem circleItem = circle.next(ctxNode);
PathMapping.setByPath(ctxNode, "item", circleItem.getItem(), true);
PathMapping.setByPath(ctxNode, "index", circleItem.getIndex(), true);
boolean rs = circle.canExec(ctxNode);
assertEquals(i, circleItem.getIndex());
if (i < 3) {
assertEquals(true, rs);
}
if (i == 3) {
assertEquals(false, rs);
break;
}
}
}
@Test
void testBreakCondition() {
ONode ctxNode = ONode.load(new HashMap());
List<String> list1 = new ArrayList<>();
list1.add("0");
list1.add("1");
list1.add("2");
list1.add("3");
list1.add("4");
PathMapping.setByPath(ctxNode, "data.list1", list1, true);
ConditionValue bValue1 = new ConditionValue(ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "3");
ConditionValue bValue2 = new ConditionValue(ValueTypeEnum.REF, RefDataTypeEnum.STRING, "item");
Condition c1 = new Condition(null, bValue1, OperatorEnum.EQ, bValue2);
List<Condition> breakConditions = new ArrayList<>();
breakConditions.add(c1);
Circle circle = new Circle(null, ValueTypeEnum.REF, "data.list1", null, breakConditions);
for (int i = 0; i < 5; i++) {
CircleItem circleItem = circle.next(ctxNode);
PathMapping.setByPath(ctxNode, "item", circleItem.getItem(), true);
PathMapping.setByPath(ctxNode, "index", circleItem.getIndex(), true);
boolean rs = circle.breakCircle(ctxNode);
assertEquals(i, circleItem.getIndex());
if (i < 3) {
assertEquals(false, rs);
}
if (i == 3) {
assertEquals(true, rs);
break;
}
}
}
}

View File

@@ -0,0 +1,327 @@
/*
* Copyright (C) 2021 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package we.fizz.component;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.validation.constraints.AssertTrue;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.noear.snack.ONode;
import we.fizz.component.condition.Condition;
import we.fizz.component.condition.ConditionValue;
import we.fizz.input.PathMapping;
/**
*
* @author Francis Dong
*
*/
class ConditionTests {
@Test
void contextLoads() {
}
private static final Boolean TRUE = true;
private static final Boolean FALSE = false;
@Test
void testExec() {
ONode ctxNode = ONode.load(new HashMap());
Map<String, Object> m = new HashMap<>();
m.put("int", 1);
m.put("long", 2);
m.put("float", 3.1);
m.put("double", 4.21);
m.put("string_abcd", "abcd");
m.put("string_1", "1");
m.put("string_8", "8");
m.put("string_blank", "");
m.put("bool_true", true);
m.put("bool_false", false);
List<String> list1 = new ArrayList<>();
list1.add("0");
list1.add("1");
list1.add("2");
list1.add("3");
list1.add("4");
List<String> list2 = new ArrayList<>();
list2.add("1");
list2.add("3");
list2.add("223");
List<Integer> intList = new ArrayList<>();
intList.add(0);
intList.add(1);
intList.add(2);
intList.add(3);
intList.add(4);
List<Float> floatList = new ArrayList<>();
floatList.add(0f);
floatList.add(1f);
floatList.add(2f);
floatList.add(3f);
floatList.add(4f);
PathMapping.setByPath(ctxNode, "data.m", m, true);
PathMapping.setByPath(ctxNode, "data.list1", list1, true);
PathMapping.setByPath(ctxNode, "data.list2", list2, true);
PathMapping.setByPath(ctxNode, "data.intList", intList, true);
PathMapping.setByPath(ctxNode, "data.floatList", floatList, true);
PathMapping.setByPath(ctxNode, "data.emptyList", new ArrayList<>(), true);
// boolean
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.FIXED,
FixedDataTypeEnum.BOOLEAN, TRUE, OperatorEnum.EQ, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.FIXED,
FixedDataTypeEnum.BOOLEAN, FALSE, OperatorEnum.EQ, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.FIXED,
FixedDataTypeEnum.BOOLEAN, FALSE, OperatorEnum.EQ, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.REF,
RefDataTypeEnum.BOOLEAN, "data.m.bool_true", OperatorEnum.EQ, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.REF,
RefDataTypeEnum.BOOLEAN, "data.m.bool_false", OperatorEnum.EQ, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.REF,
RefDataTypeEnum.BOOLEAN, "data.m.bool_true", OperatorEnum.EQ, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.REF, null,
"data.m.a", OperatorEnum.EQ, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.FIXED,
FixedDataTypeEnum.BOOLEAN, FALSE, OperatorEnum.GT, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.FIXED,
FixedDataTypeEnum.BOOLEAN, FALSE, OperatorEnum.GE, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.FIXED,
FixedDataTypeEnum.BOOLEAN, TRUE, OperatorEnum.GE, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.FIXED,
FixedDataTypeEnum.BOOLEAN, TRUE, OperatorEnum.LT, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.FIXED,
FixedDataTypeEnum.BOOLEAN, FALSE, OperatorEnum.LE, TRUE });
// number
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1, ValueTypeEnum.FIXED,
FixedDataTypeEnum.NUMBER, 1, OperatorEnum.EQ, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1, ValueTypeEnum.FIXED,
FixedDataTypeEnum.NUMBER, 2, OperatorEnum.EQ, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1, ValueTypeEnum.FIXED,
FixedDataTypeEnum.NUMBER, 1.000, OperatorEnum.EQ, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1, ValueTypeEnum.FIXED,
FixedDataTypeEnum.NUMBER, 2.1, OperatorEnum.EQ, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.0, ValueTypeEnum.FIXED,
FixedDataTypeEnum.NUMBER, 1.000, OperatorEnum.EQ, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.1, ValueTypeEnum.FIXED,
FixedDataTypeEnum.NUMBER, 2.1, OperatorEnum.EQ, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.1, ValueTypeEnum.FIXED,
FixedDataTypeEnum.NUMBER, 2.1, OperatorEnum.GT, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.1, ValueTypeEnum.FIXED,
FixedDataTypeEnum.NUMBER, 0.1, OperatorEnum.GE, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.1, ValueTypeEnum.FIXED,
FixedDataTypeEnum.NUMBER, 0.1, OperatorEnum.LT, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.1, ValueTypeEnum.FIXED,
FixedDataTypeEnum.NUMBER, 4, OperatorEnum.LT, TRUE });
// collection<String>
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "2", OperatorEnum.CONTAINS, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_1", OperatorEnum.CONTAINS, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.CONTAINS, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "2", OperatorEnum.NOT_CONTAIN, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_1", OperatorEnum.NOT_CONTAIN, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.NOT_CONTAIN, TRUE });
// collection contains any
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF,
RefDataTypeEnum.ARRAY, "data.list2", OperatorEnum.CONTAINS_ANY, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF,
RefDataTypeEnum.ARRAY, "data.intList", OperatorEnum.CONTAINS_ANY, FALSE });
// Collection<int>
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "2", OperatorEnum.CONTAINS, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.REF,
RefDataTypeEnum.INT, "data.m.int", OperatorEnum.CONTAINS, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED,
RefDataTypeEnum.INT, 2, OperatorEnum.CONTAINS, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED,
RefDataTypeEnum.INT, 9, OperatorEnum.CONTAINS, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "2", OperatorEnum.NOT_CONTAIN, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.REF,
RefDataTypeEnum.INT, "data.m.int", OperatorEnum.NOT_CONTAIN, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED,
RefDataTypeEnum.INT, 2, OperatorEnum.NOT_CONTAIN, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED,
RefDataTypeEnum.INT, 9, OperatorEnum.NOT_CONTAIN, TRUE });
// Collection<Float>
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.floatList",
ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "2", OperatorEnum.CONTAINS, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.floatList", ValueTypeEnum.REF,
RefDataTypeEnum.INT, "data.m.int", OperatorEnum.CONTAINS, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.floatList",
ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 2, OperatorEnum.CONTAINS, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.floatList",
ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 2.0, OperatorEnum.CONTAINS, TRUE });
// String
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, null, OperatorEnum.EQ, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, null, OperatorEnum.EQ, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "1", OperatorEnum.EQ, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "1", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "1", OperatorEnum.EQ, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "1", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "21", OperatorEnum.EQ, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, null, OperatorEnum.NE, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, null, OperatorEnum.NE, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "1", OperatorEnum.NE, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "1", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "1", OperatorEnum.NE, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "1", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "21", OperatorEnum.NE, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "1", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "21", OperatorEnum.GT, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "11", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "21", OperatorEnum.GT, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "11", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "21", OperatorEnum.GE, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "11", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "21", OperatorEnum.LT, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "11", ValueTypeEnum.FIXED,
FixedDataTypeEnum.STRING, "21", OperatorEnum.LE, TRUE });
// Is null
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_NULL, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8",
ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_NULL, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null,
OperatorEnum.IS_NULL, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", null, null, null,
OperatorEnum.IS_NULL, FALSE });
// Is not null
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_NOT_NULL, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8",
ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_NOT_NULL, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null,
OperatorEnum.IS_NOT_NULL, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", null, null, null,
OperatorEnum.IS_NOT_NULL, TRUE });
// Is Blank
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_BLANK, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8",
ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_BLANK, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null,
OperatorEnum.IS_BLANK, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", null, null, null,
OperatorEnum.IS_BLANK, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_blank", null, null,
null, OperatorEnum.IS_BLANK, TRUE });
// Is not Blank
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_NOT_BLANK, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8",
ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_NOT_BLANK, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null,
OperatorEnum.IS_NOT_BLANK, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", null, null, null,
OperatorEnum.IS_NOT_BLANK, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_blank", null, null,
null, OperatorEnum.IS_NOT_BLANK, FALSE });
// Is empty
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_EMPTY, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_EMPTY, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null,
OperatorEnum.IS_EMPTY, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", null, null, null,
OperatorEnum.IS_EMPTY, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.emptyList", null, null, null,
OperatorEnum.IS_EMPTY, TRUE });
// Is not empty
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_NOT_EMPTY, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF,
RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.IS_NOT_EMPTY, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null,
OperatorEnum.IS_NOT_EMPTY, FALSE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", null, null, null,
OperatorEnum.IS_NOT_EMPTY, TRUE });
this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.emptyList", null, null, null,
OperatorEnum.IS_NOT_EMPTY, FALSE });
}
private void run(ONode ctxNode, Object[] item) {
ConditionValue bValue1 = null;
if (item[1] instanceof FixedDataTypeEnum) {
bValue1 = new ConditionValue((ValueTypeEnum) item[0], (FixedDataTypeEnum) item[1], item[2]);
} else {
bValue1 = new ConditionValue((ValueTypeEnum) item[0], (RefDataTypeEnum) item[1], item[2]);
}
ConditionValue bValue2 = null;
if (item[3] != null) {
if (item[4] instanceof FixedDataTypeEnum) {
bValue2 = new ConditionValue((ValueTypeEnum) item[3], (FixedDataTypeEnum) item[4], item[5]);
} else {
bValue2 = new ConditionValue((ValueTypeEnum) item[3], (RefDataTypeEnum) item[4], item[5]);
}
}
Condition c = new Condition(null, bValue1, (OperatorEnum) item[6], bValue2);
boolean rs = c.exec(ctxNode);
boolean expected = (boolean) item[7];
assertEquals(expected, rs);
}
}

View File

@@ -163,4 +163,31 @@ class PathMappingTests {
}
@Test
void testArray() {
ONode ctxNode = ONode.load(new HashMap());
Map<String, Object> m = new HashMap<>();
m.put("a", "1");
m.put("b", "1");
List<String> list = new ArrayList<>();
list.add("0");
list.add("1");
list.add("2");
list.add("3");
list.add("4");
PathMapping.setByPath(ctxNode, "data.m", m, true);
PathMapping.setByPath(ctxNode, "data.arr", list, true);
Object abcVal1 = PathMapping.getValueByPath(ctxNode, "data.arr[0]");
assertEquals("0", (String)abcVal1);
Object abcVal2 = PathMapping.getValueByPath(ctxNode, "data.arr[-1]");
assertEquals("4", (String)abcVal2);
System.out.println(abcVal1);
System.out.println(abcVal2);
}
}