Support function in aggregation #281

This commit is contained in:
Francis Dong
2021-08-20 17:06:22 +08:00
committed by dxfeng10
parent dd70f9b118
commit 562badc796
15 changed files with 2032 additions and 16 deletions

View File

@@ -278,6 +278,11 @@
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
</dependencies>
<repositories>

View File

@@ -0,0 +1,184 @@
/*
* 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.function;
import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import we.util.DigestUtils;
/**
* Codec Functions
*
* @author Francis Dong
*
*/
public class CodecFunc implements IFunc {
private static final Logger LOGGER = LoggerFactory.getLogger(CodecFunc.class);
private static final String CHARSET_UTF8 = "UTF-8";
private static final String IV = "12345678";
private static CodecFunc singleton;
public static CodecFunc getInstance() {
if (singleton == null) {
synchronized (CodecFunc.class) {
if (singleton == null) {
CodecFunc instance = new CodecFunc();
instance.init();
singleton = instance;
}
}
}
return singleton;
}
private CodecFunc() {
}
public void init() {
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.md5", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha1", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha256", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha384", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha512", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.base64Encode", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.base64Decode", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.aesEncrypt", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.aesDecrypt", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.desEncrypt", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "codec.desDecrypt", this);
}
public String md5(String data) {
return DigestUtils.md5Hex(data);
}
public String sha1(String data) {
return DigestUtils.sha1Hex(data);
}
public String sha256(String data) {
return DigestUtils.sha256Hex(data);
}
public String sha384(String data) {
return DigestUtils.sha384Hex(data);
}
public String sha512(String data) {
return DigestUtils.sha512Hex(data);
}
public String base64Encode(String data) throws Exception {
try {
return Base64.getEncoder().encodeToString(data.getBytes(CHARSET_UTF8));
} catch (UnsupportedEncodingException e) {
LOGGER.error("Base64 encode error, data={}", data, e);
throw e;
}
}
public String base64Decode(String data) throws Exception {
return new String(Base64.getDecoder().decode(data));
}
public String aesEncrypt(String data, String key) throws Exception {
if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) {
return null;
}
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(CHARSET_UTF8), "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] result = cipher.doFinal(data.getBytes(CHARSET_UTF8));
return Base64.getEncoder().encodeToString(result);
} catch (Exception e) {
LOGGER.error("AES encrypt error, data={}", data, e);
throw e;
}
}
public String aesDecrypt(String data, String key) throws Exception {
if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) {
return null;
}
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(CHARSET_UTF8), "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] result = Base64.getDecoder().decode(data);
return new String(cipher.doFinal(result));
} catch (Exception e) {
LOGGER.error("AES decrypt error, data={}", data, e);
throw e;
}
}
public String desEncrypt(String data, String key) throws Exception {
if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) {
return null;
}
try {
DESKeySpec dks = new DESKeySpec(key.getBytes(CHARSET_UTF8));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
Key secretKey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(IV.getBytes(CHARSET_UTF8));
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
byte[] bytes = cipher.doFinal(data.getBytes(CHARSET_UTF8));
return new String(Base64.getEncoder().encode(bytes));
} catch (Exception e) {
LOGGER.error("DES eecrypt error, data={}", data, e);
throw e;
}
}
public String desDecrypt(String data, String key) throws Exception {
if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) {
return null;
}
try {
DESKeySpec dks = new DESKeySpec(key.getBytes(CHARSET_UTF8));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
Key secretKey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(IV.getBytes(CHARSET_UTF8));
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
return new String(cipher.doFinal(Base64.getDecoder().decode(data.getBytes(CHARSET_UTF8))), CHARSET_UTF8);
} catch (Exception e) {
LOGGER.error("DES decrypt error, data={}", data, e);
throw e;
}
}
}

View File

@@ -0,0 +1,246 @@
/*
* 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.function;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import we.fizz.exception.FizzRuntimeException;
/**
* Date Functions
*
* @author Francis Dong
*
*/
public class DateFunc implements IFunc {
private static final Logger LOGGER = LoggerFactory.getLogger(DateFunc.class);
private static DateFunc singleton;
public static DateFunc getInstance() {
if (singleton == null) {
synchronized (DateFunc.class) {
if (singleton == null) {
DateFunc instance = new DateFunc();
instance.init();
singleton = instance;
}
}
}
return singleton;
}
private DateFunc() {
}
public void init() {
FuncExecutor.register(NAME_SPACE_PREFIX + "date.timestamp", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "date.now", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "date.add", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "date.formatTs", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "date.changePattern", this);
}
/**
* Date pattern<br>
* yyyy-MM-dd
*/
public final static String DATE_FORMAT = "yyyy-MM-dd";
/**
* Time pattren<br>
* HH:mm:ss
*/
public final static String TIME_FORMAT = "HH:mm:ss";
/**
* Short time pattren<br>
* HH:mm
*/
public final static String SHORT_TIME_FORMAT = "HH:mm";
/**
* Date time pattern<br>
* yyyy-MM-dd HH:mm:ss
*/
public final static String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* Returns current timestamp (Milliseconds)
*
* @return
*/
public long timestamp() {
return System.currentTimeMillis();
}
/**
* Returns current time with the given pattern<br>
* Frequently-used pattern:<br>
* yyyy-MM-dd HH:mm:ss<br>
* yyyy-MM-dd<br>
* HH:mm:ss<br>
* HH:mm<br>
* yyyy-MM-dd HH:mm:ss Z<br>
*
* @param pattern [optional] the pattern describing the date and time format,
* dafault yyyy-MM-dd HH:mm:ss
* @return
*/
public String now(String pattern) {
return formatDate(new Date(), pattern);
}
/**
* Adds or subtracts the specified amount of time to the given calendar field,
* based on the calendar's rules. For example, to subtract 5 hours from the
* current time of the calendar, you can achieve it by calling:
* <p>
* <code>add("2021-08-04 14:23:12", "yyyy-MM-dd HH:mm:ss", 4, -5)</code>.
*
* @param date date string
* @param pattern date pattern of the given date string
* @param field the calendar field, <br>
* 1 for millisecond<br>
* 2 for second<br>
* 3 for minute<br>
* 4 for hour<br>
* 5 for date<br>
* 6 for month<br>
* 7 for year<br>
* @param amount the amount of date or time to be added to the field
* @return
*/
public String add(String date, String pattern, int field, int amount) {
Date d = parse(date, pattern);
if (d != null) {
// convert to calendar field
int calField = 0;
switch (field) {
case 1:
calField = Calendar.MILLISECOND;
break;
case 2:
calField = Calendar.SECOND;
break;
case 3:
calField = Calendar.MINUTE;
break;
case 4:
calField = Calendar.HOUR;
break;
case 5:
calField = Calendar.DATE;
break;
case 6:
calField = Calendar.MONTH;
break;
case 7:
calField = Calendar.YEAR;
break;
default:
LOGGER.error("invalid field, date={} pattern={} filed={}", date, pattern, field);
throw new FizzRuntimeException(
"invalid field, date=" + date + "pattern=" + pattern + " filed=" + field);
}
return formatDate(addToFiled(d, calField, amount), pattern);
}
return null;
}
/**
* Format the a timestamp to the given pattern
*
* @param timestamp
* @param pattern
* @return
*/
public String formatTs(long timestamp, String pattern) {
return formatDate(new Date(timestamp), pattern);
}
/**
* Format the a time with source pattern to the target pattern
*
* @param dateStr date
* @param sourcePattern source pattern
* @param targetPattern target pattern
* @return
*/
public String changePattern(String dateStr, String sourcePattern, String targetPattern) {
return formatDate(parse(dateStr, sourcePattern), targetPattern);
}
/**
* Adds or subtracts the specified amount of time to the given calendar field
*
* @param date a Date
* @param field field that the times to be add to, such as: Calendar.SECOND,
* Calendar.YEAR
* @param amount the amount of date or time to be added to the field
* @return
*/
private Date addToFiled(Date date, int field, int amount) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(field, amount);
return cal.getTime();
}
/**
* Parse string to Date
*
* @param dateStr String to be parsed
* @param pattern pattern of dateStr
* @return
*/
private Date parse(String dateStr, String pattern) {
SimpleDateFormat df = new SimpleDateFormat(pattern == null ? DATE_TIME_FORMAT : pattern);
try {
return df.parse(dateStr);
} catch (ParseException e) {
LOGGER.error("Parse date error, dateStr={} pattern={}", dateStr, pattern, e);
throw new FizzRuntimeException("Parse date error, dateStr=" + dateStr + " pattern=" + pattern, e);
}
}
/**
* Format date with the given pattern<br>
* Frequently-used pattern:<br>
* yyyy-MM-dd HH:mm:ss<br>
* yyyy-MM-dd<br>
* HH:mm:ss<br>
* HH:mm<br>
* yyyy-MM-dd HH:mm:ss Z<br>
*
* @param pattern [optional] the pattern describing the date and time format,
* dafault yyyy-MM-dd HH:mm:ss
* @return
*/
private String formatDate(Date date, String pattern) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern == null ? DATE_TIME_FORMAT : pattern);
return sdf.format(date);
}
}

View File

@@ -0,0 +1,474 @@
/*
* 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.function;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.lang3.StringUtils;
import org.noear.snack.ONode;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import we.fizz.exception.FizzRuntimeException;
import we.fizz.input.Input;
import we.fizz.input.PathMapping;
/**
* Function Register
*
* @author Francis Dong
*
*/
public class FuncExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(FuncExecutor.class);
private static final Map<String, IFunc> funcMap = new HashMap<>();
private static Pattern NUMBER_PATTERN = Pattern
.compile("^[-\\+]?[\\d]+\\s*[,\\)]{1}|^[-\\+]?[\\d]+\\.[\\d]+\\s*[,\\)]{1}");
private static FuncExecutor singleton;
public static FuncExecutor getInstance() {
if (singleton == null) {
synchronized (FuncExecutor.class) {
if (singleton == null) {
singleton = new FuncExecutor();
init();
}
}
}
return singleton;
}
private FuncExecutor() {
}
public static void init() {
try {
Reflections reflections = new Reflections("we.fizz.function");
Set<Class<? extends IFunc>> types = reflections.getSubTypesOf(IFunc.class);
for (Class<? extends IFunc> fnType : types) {
Method method = fnType.getMethod("getInstance");
method.invoke(fnType);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Register a function instance
*
* @param namespace a name to identify the given function instance
* @param func
*/
public static void register(String namespace, IFunc funcInstance) {
if (StringUtils.isBlank(namespace)) {
LOGGER.warn("namespace is required");
return;
}
if (!namespace.startsWith(IFunc.NAME_SPACE_PREFIX)) {
LOGGER.warn("namespace must start with fn.");
return;
}
if (funcInstance == null) {
LOGGER.warn("function instance is required");
return;
}
funcMap.put(namespace, funcInstance);
}
/**
* Execute function
*
* @param funcExpression
* @return
*/
public Object exec(ONode ctxNode, String funcExpression) {
RecursionContext ctx = new RecursionContext();
ctx.setFuncExpression(funcExpression);
return doExec(ctxNode, ctx);
}
private Object doExec(ONode ctxNode, RecursionContext ctx) {
String funcExpression = ctx.funcExpression;
if (StringUtils.isBlank(funcExpression)) {
return null;
}
funcExpression = StringUtils.trim(funcExpression);
int pos1 = funcExpression.indexOf("(");
if (pos1 == -1) {
LOGGER.warn("func expression is invalid, expression: {}", funcExpression);
return null;
}
if (!funcExpression.endsWith(")")) {
LOGGER.warn("func expression is invalid, expression: {}", funcExpression);
return null;
}
String path = funcExpression.substring(0, pos1);
int lastDotPos = path.lastIndexOf(".");
if (pos1 == -1) {
LOGGER.warn("func expression is invalid, expression: {}", funcExpression);
return null;
}
String namespace = path.substring(0, lastDotPos);
String methodName = path.substring(lastDotPos + 1);
Object funcInstance = funcMap.get(path);
if (funcInstance == null) {
String msg = String.format("function not found: %s, expression: %s", path, funcExpression);
LOGGER.warn(msg);
throw new FizzRuntimeException(msg);
}
try {
Method method = findMethod(funcInstance.getClass(), methodName);
Class[] paramTypes = method.getParameterTypes();
ctx.funcExpression = funcExpression;
Object[] args = parseArgs(ctxNode, ctx, funcExpression, paramTypes, method.isVarArgs());
if (args == null) {
return method.invoke(funcInstance);
}
return method.invoke(funcInstance, args);
} catch (FizzRuntimeException e) {
throw e;
} catch (InvocationTargetException e) {
Throwable targetEx = e.getTargetException();
if (targetEx instanceof FizzRuntimeException) {
throw (FizzRuntimeException) targetEx;
}
String msg = targetEx.getMessage();
if (msg == null) {
msg = String.format("execute function error: %s", funcExpression);
}
LOGGER.error(msg, targetEx);
throw new FizzRuntimeException(msg, targetEx);
} catch (Exception e) {
String msg = String.format("execute function error: %s", funcExpression);
LOGGER.error(msg, e);
throw new FizzRuntimeException(msg, e);
}
}
private Method findMethod(Class funcClass, String methodName) {
Method[] methods = funcClass.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
return method;
}
}
String msg = String.format("method not found: %s, class: %s", methodName, funcClass);
LOGGER.warn(msg);
throw new FizzRuntimeException(msg);
}
/**
* funcExpression sample:<br>
* fn.date.add({step1.request1.response.body.date}, "yyyy-MM-dd HH:mm:ss", 1,
* 1000)<br>
* fn.date.add(true, fn.date.add({step1.request1.response.body.date},
* "yyyy-MM-dd HH:mm:ss", 1, 1000), "yyyy-MM-dd HH:mm:ss\"))}}", 1, 1000)<br>
*
* @param funcExpression
* @param paramTypes
* @return
*/
private Object[] parseArgs(ONode ctxNode, RecursionContext ctx, String funcExpression, Class[] paramTypes,
boolean isVarArgs) {
int pos1 = funcExpression.indexOf("(");
// int pos2 = funcExpression.lastIndexOf(")");
String argsStr = funcExpression.substring(pos1 + 1);
argsStr = StringUtils.trim(argsStr);
// check if there is any argument
if (StringUtils.isBlank(argsStr)) {
if (paramTypes == null || paramTypes.length == 0) {
return null;
} else if (paramTypes.length == 1 && isVarArgs) {
// check if variable arguments
return null;
} else {
throw new FizzRuntimeException(
String.format("missing argument, Function Expression: %s", funcExpression));
}
}
Object[] args = new Object[paramTypes.length];
List<Object> varArgs = new ArrayList<>();
for (int i = 0; i < paramTypes.length; i++) {
Class clazz = paramTypes[i];
if (StringUtils.isBlank(argsStr)) {
if (isVarArgs && i == paramTypes.length - 1 && args[i] == null) {
args[i] = Array.newInstance(clazz.getComponentType(), 0);
}
break;
}
ArgsStrContainer argsStrContainer = new ArgsStrContainer(argsStr, i);
if (argsStr.startsWith("\"")) { // string
int pos = findStringEngPos(argsStr);
if (pos != -1) {
String arg = argsStr.substring(1, pos);
if (isVarArgs && i == paramTypes.length - 1) {
varArgs.add(arg);
args[i] = varArgs.toArray(new String[varArgs.size()]);
} else {
args[i] = arg;
}
argsStrContainer = this.trimArgStr(argsStrContainer, pos + 1, isVarArgs, paramTypes.length,
funcExpression);
argsStr = argsStrContainer.getArgsStr();
i = argsStrContainer.getIndex();
} else {
throw new FizzRuntimeException(
String.format("invalid argument: %s, Function Expression: %s", argsStr, funcExpression));
}
} else if (argsStr.matches("^true\\s*,")) { // boolean
if (isVarArgs && i == paramTypes.length - 1) {
varArgs.add(true);
args[i] = varArgs.toArray(new Boolean[varArgs.size()]);
} else {
args[i] = true;
}
argsStrContainer = this.trimArgStr(argsStrContainer, 4, isVarArgs, paramTypes.length, funcExpression);
argsStr = argsStrContainer.getArgsStr();
i = argsStrContainer.getIndex();
} else if (argsStr.matches("^false\\s*,")) { // boolean
if (isVarArgs && i == paramTypes.length - 1) {
varArgs.add(false);
args[i] = varArgs.toArray(new Boolean[varArgs.size()]);
} else {
args[i] = false;
}
argsStrContainer = this.trimArgStr(argsStrContainer, 5, isVarArgs, paramTypes.length, funcExpression);
argsStr = argsStrContainer.getArgsStr();
i = argsStrContainer.getIndex();
} else if (argsStr.startsWith("{")) { // reference value
int pos = argsStr.indexOf("}", 1);
if (pos != -1) {
String refKey = argsStr.substring(1, pos);
Object arg = PathMapping.getValueByPath(ctxNode, refKey);
arg = ConvertUtils.convert(arg, clazz);
if (isVarArgs && i == paramTypes.length - 1) {
varArgs.add(arg);
Object arr = Array.newInstance(clazz.getComponentType(), varArgs.size());
for (int j = 0; j < varArgs.size(); j++) {
Array.set(arr, j, varArgs.get(j));
}
args[i] = arr;
} else {
args[i] = arg;
}
argsStrContainer = this.trimArgStr(argsStrContainer, pos + 1, isVarArgs, paramTypes.length,
funcExpression);
argsStr = argsStrContainer.getArgsStr();
i = argsStrContainer.getIndex();
} else {
throw new FizzRuntimeException(
String.format("invalid argument: %s, Function Expression: %s", argsStr, funcExpression));
}
} else {
Matcher m = NUMBER_PATTERN.matcher(argsStr);
boolean isNumber = m.find();
if (isNumber) {
int pos = m.end();
String matchedStr = m.group();
// Number
String strNum = StringUtils.trim(matchedStr.substring(0, pos - 1));
if (isVarArgs && i == paramTypes.length - 1) {
Object arg = ConvertUtils.convert(strNum, clazz.getComponentType());
varArgs.add(arg);
Object arr = Array.newInstance(clazz.getComponentType(), varArgs.size());
for (int j = 0; j < varArgs.size(); j++) {
Array.set(arr, j, varArgs.get(j));
}
args[i] = arr;
} else {
Object arg = ConvertUtils.convert(strNum, clazz);
args[i] = arg;
}
argsStrContainer = this.trimArgStr(argsStrContainer, pos - 1, isVarArgs, paramTypes.length,
funcExpression);
argsStr = argsStrContainer.getArgsStr();
i = argsStrContainer.getIndex();
} else {
// function
ctx.funcExpression = argsStr;
Object rs = doExec(ctxNode, ctx);
if (isVarArgs && i == paramTypes.length - 1) {
Object arg = ConvertUtils.convert(rs, clazz.getComponentType());
varArgs.add(arg);
Object arr = Array.newInstance(clazz.getComponentType(), varArgs.size());
for (int j = 0; j < varArgs.size(); j++) {
Array.set(arr, j, varArgs.get(j));
}
args[i] = arr;
} else {
Object arg = ConvertUtils.convert(rs, clazz);
args[i] = arg;
}
argsStr = ctx.funcExpression;
argsStrContainer.setArgsStr(argsStr);
argsStrContainer = this.trimArgStr(argsStrContainer, 0, isVarArgs, paramTypes.length,
funcExpression);
argsStr = argsStrContainer.getArgsStr();
i = argsStrContainer.getIndex();
}
}
ctx.funcExpression = argsStr;
}
return args;
}
private ArgsStrContainer trimArgStr(ArgsStrContainer argsStrContainer, int fromIndex, boolean isVarArgs,
int paramTypesLen, String funcExpression) {
int i = argsStrContainer.getIndex();
String argsStr = argsStrContainer.getArgsStr();
if (i == paramTypesLen - 1 || (isVarArgs && i == paramTypesLen - 2)) {
boolean hasMore = hasMoreArg(argsStr, fromIndex);
if (isVarArgs && hasMore) {
argsStr = removeComma(argsStr, fromIndex, funcExpression);
if (i == paramTypesLen - 1) {
i--;
}
} else {
if (hasCloseParenthesis(argsStr, fromIndex)) {
argsStr = removeCloseParenthesis(argsStr, fromIndex, funcExpression);
} else {
throw new FizzRuntimeException(String.format("invalid argument: %s, Function Expression: %s",
argsStr.substring(fromIndex), funcExpression));
}
}
} else {
argsStr = removeComma(argsStr, fromIndex, funcExpression);
}
argsStrContainer.setArgsStr(argsStr);
argsStrContainer.setIndex(i);
return argsStrContainer;
}
private boolean hasMoreArg(String argsStr, int fromIndex) {
final int strLen = argsStr.length();
if (strLen == 0) {
return false;
}
for (int i = fromIndex; i < strLen; i++) {
if (!Character.isWhitespace(argsStr.charAt(i))) {
if (",".equals(String.valueOf(argsStr.charAt(i)))) {
return true;
} else {
return false;
}
}
}
return false;
}
private boolean hasCloseParenthesis(String argsStr, int fromIndex) {
final int strLen = argsStr.length();
if (strLen == 0) {
return false;
}
for (int i = fromIndex; i < strLen; i++) {
if (!Character.isWhitespace(argsStr.charAt(i))) {
if (")".equals(String.valueOf(argsStr.charAt(i)))) {
return true;
} else {
return false;
}
}
}
return false;
}
private String removeComma(String argsStr, int fromIndex, String funcExpression) {
final int strLen = argsStr.length();
if (strLen == 0) {
return argsStr;
}
for (int i = fromIndex; i < strLen; i++) {
if (!Character.isWhitespace(argsStr.charAt(i))) {
if (",".equals(String.valueOf(argsStr.charAt(i)))) {
return StringUtils.trim(argsStr.substring(i + 1));
}
}
}
throw new FizzRuntimeException(String.format("missing comma after argument: %s, Function Expression: %s",
argsStr.substring(fromIndex), funcExpression));
}
private String removeCloseParenthesis(String argsStr, int fromIndex, String funcExpression) {
final int strLen = argsStr.length();
if (strLen == 0 || strLen < fromIndex) {
return argsStr;
}
for (int i = fromIndex; i < strLen; i++) {
if (!Character.isWhitespace(argsStr.charAt(i))) {
if (")".equals(String.valueOf(argsStr.charAt(i)))) {
return StringUtils.trim(argsStr.substring(i + 1));
}
}
}
throw new FizzRuntimeException(
String.format("missing close parenthesis after argument: %s, Function Expression: %s",
argsStr.substring(fromIndex), funcExpression));
}
private int findStringEngPos(String ep) {
int pos = ep.indexOf("\"", 1);
while (pos != -1) {
String prevChar = ep.substring(pos - 1, pos);
if (!"\\".equals(prevChar)) {
return pos;
}
pos = ep.indexOf("\"", pos);
}
return -1;
}
}
@Data
@AllArgsConstructor
class ArgsStrContainer {
private String argsStr;
private int index;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class RecursionContext {
public String funcExpression;
public Object result;
}

View File

@@ -0,0 +1,39 @@
/*
* 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.function;
/**
* Function interface
*
* @author Francis Dong
*
*/
public interface IFunc {
public final static String NAME_SPACE_PREFIX = "fn.";
/**
* Init: Register functions to FuncExecutor in the initial stage <br>
* <br>
* Example: <br>
* FuncExecutor.register(NAME_SPACE_PREFIX + "date.timestamp", getInstance());<br>
* FuncExecutor.register(NAME_SPACE_PREFIX + "date.now", getInstance());<br>
*
*/
void init();
}

View File

@@ -0,0 +1,124 @@
/*
* 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.function;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* List Functions
*
* @author Francis Dong
*
*/
public class ListFunc implements IFunc {
private static final Logger LOGGER = LoggerFactory.getLogger(ListFunc.class);
private static ListFunc singleton;
public static ListFunc getInstance() {
if (singleton == null) {
synchronized (ListFunc.class) {
if (singleton == null) {
ListFunc instance = new ListFunc();
instance.init();
singleton = instance;
}
}
}
return singleton;
}
private ListFunc() {
}
public void init() {
FuncExecutor.register(NAME_SPACE_PREFIX + "list.expand", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "list.merge", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "list.extract", this);
}
/**
* Expand sublist item to the first level
*
* @param data
* @return
*/
public List<Object> expand(List<List<Object>> data) {
List<Object> result = new ArrayList<>();
if (data == null || data.size() == 0) {
return result;
}
for (List<Object> list : data) {
result.addAll(list);
}
return result;
}
/**
* Merge multiple list into one list
*
* @param data
* @return
*/
public List<Object> merge(List<Object>... data) {
List<Object> result = new ArrayList<>();
if (data == null || data.length == 0) {
return result;
}
for (List<Object> list : data) {
if (list == null || list.size() == 0) {
continue;
}
result.addAll(list);
}
return result;
}
/**
* Extract fields from list
*
* @param data
* @param fields
* @return
*/
public List<Map<String, Object>> extract(List<Map<String, Object>> data, String... fields) {
List<Map<String, Object>> result = new ArrayList<>();
if (data == null || data.size() == 0) {
return result;
}
if (fields.length == 0) {
return data;
}
for (Map<String, Object> m : data) {
Map<String, Object> r = new HashMap<>();
for (String field : fields) {
r.put(field, m.get(field));
}
result.add(r);
}
return result;
}
}

View File

@@ -0,0 +1,162 @@
/*
* 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.function;
import java.math.BigDecimal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Math Functions
*
* @author Francis Dong
*
*/
public class MathFunc implements IFunc {
private static final Logger LOGGER = LoggerFactory.getLogger(MathFunc.class);
private static MathFunc singleton;
public static MathFunc getInstance() {
if (singleton == null) {
synchronized (MathFunc.class) {
if (singleton == null) {
MathFunc instance = new MathFunc();
instance.init();
singleton = instance;
}
}
}
return singleton;
}
private MathFunc() {
}
public void init() {
FuncExecutor.register(NAME_SPACE_PREFIX + "math.absExact", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.negateExact", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.addExact", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.subtractExact", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.multiplyExact", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.maxExact", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.minExact", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.mod", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.pow", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.sqrt", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.random", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.absDecimal", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.negateDecimal", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.addDecimal", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.subtractDecimal", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.multiplyDecimal", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.divideDecimal", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.maxDecimal", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.minDecimal", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "math.scaleDecimal", this);
}
public long absExact(long a) {
return Math.abs(a);
}
public long negateExact(long a) {
return Math.negateExact(a);
}
public long addExact(long x, long y) {
return Math.addExact(x, y);
}
public long subtractExact(long x, long y) {
return Math.subtractExact(x, y);
}
public long multiplyExact(long x, long y) {
return Math.multiplyExact(x, y);
}
public long maxExact(long x, long y) {
return Math.max(x, y);
}
public long minExact(long x, long y) {
return Math.min(x, y);
}
public long mod(long x, long y) {
return Math.floorMod(x, y);
}
public double pow(double a, double b) {
return Math.pow(a, b);
}
public double sqrt(double a) {
return Math.sqrt(a);
}
/**
* Returns a {@code double} value with a positive sign, greater than or equal to
* {@code 0.0} and less than {@code 1.0}. Returned values are chosen
* pseudorandomly with (approximately) uniform distribution from that range.
*
* @return
*/
public double random() {
return Math.random();
}
public double absDecimal(double a) {
return BigDecimal.valueOf(a).abs().doubleValue();
}
public double negateDecimal(double a) {
return BigDecimal.valueOf(a).negate().doubleValue();
}
public double addDecimal(double x, double y) {
return BigDecimal.valueOf(x).add(BigDecimal.valueOf(y)).doubleValue();
}
public double subtractDecimal(double x, double y) {
return BigDecimal.valueOf(x).subtract(BigDecimal.valueOf(y)).doubleValue();
}
public double multiplyDecimal(double x, double y) {
return BigDecimal.valueOf(x).multiply(BigDecimal.valueOf(y)).doubleValue();
}
public double divideDecimal(double x, double y) {
return BigDecimal.valueOf(x).divide(BigDecimal.valueOf(y)).doubleValue();
}
public double maxDecimal(double x, double y) {
return BigDecimal.valueOf(x).max(BigDecimal.valueOf(y)).doubleValue();
}
public double minDecimal(double x, double y) {
return BigDecimal.valueOf(x).min(BigDecimal.valueOf(y)).doubleValue();
}
public double scaleDecimal(double a, int scale) {
return BigDecimal.valueOf(a).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}

View File

@@ -0,0 +1,148 @@
/*
* 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.function;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import we.fizz.exception.FizzRuntimeException;
/**
* String Functions
*
* @author Francis Dong
*
*/
public class StringFunc implements IFunc {
private static final Logger LOGGER = LoggerFactory.getLogger(StringFunc.class);
private static StringFunc singleton;
public static StringFunc getInstance() {
if (singleton == null) {
synchronized (StringFunc.class) {
if (singleton == null) {
StringFunc instance = new StringFunc();
instance.init();
singleton = instance;
}
}
}
return singleton;
}
private StringFunc() {
}
public void init() {
FuncExecutor.register(NAME_SPACE_PREFIX + "string.concat", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "string.concatws", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "string.substring", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "string.indexOf", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "string.startsWith", this);
FuncExecutor.register(NAME_SPACE_PREFIX + "string.endsWith", this);
}
/**
* Concat strings
*
* @param strs
* @return
*/
public String concat(String... strs) {
return StringUtils.join(strs);
}
/**
* Concat with separator
*
* @param strs
* @return
*/
public String concatws(String separator, String... strs) {
return StringUtils.join(strs, separator);
}
/**
* Returns a string that is a substring of this string. The substring begins at
* the specified {@code beginIndex} and extends to the character at index
* {@code endIndex - 1}. Thus the length of the substring is
* {@code endIndex-beginIndex}.
*
* @param str
* @param beginIndex
* @param endIndex
* @return
*/
public String substring(String str, int beginIndex, int... endIndex) {
if (StringUtils.isBlank(str)) {
return str;
}
if (endIndex != null && endIndex.length > 0) {
if (endIndex.length > 1) {
LOGGER.error("invalid argument: endIndex");
throw new FizzRuntimeException("invalid argument: endIndex");
}
return str.substring(beginIndex, endIndex[0]);
}
return str.substring(beginIndex);
}
/**
* Returns the index within this string of the first occurrence of the specified
* substring.
*
* @param str
* @param substr
* @return the index of the first occurrence of the specified substring, or
* {@code -1} if there is no such occurrence.
*/
public int indexOf(String str, String substr) {
return str.indexOf(substr);
}
/**
* Tests if this string starts with the specified prefix.
*
* @param prefix the prefix.
* @return {@code true} if the character sequence represented by the argument is
* a prefix of the character sequence represented by this string;
* {@code false} otherwise. Note also that {@code true} will be returned
* if the argument is an empty string or is equal to this {@code String}
* object as determined by the {@link #equals(Object)} method.
*/
public boolean startsWith(String str, String prefix) {
return str.startsWith(prefix);
}
/**
* Tests if this string starts with the specified prefix.
*
* @param prefix the prefix.
* @return {@code true} if the character sequence represented by the argument is
* a prefix of the character sequence represented by this string;
* {@code false} otherwise. Note also that {@code true} will be returned
* if the argument is an empty string or is equal to this {@code String}
* object as determined by the {@link #equals(Object)} method.
*/
public boolean endsWith(String str, String suffix) {
return str.endsWith(suffix);
}
}

View File

@@ -27,6 +27,8 @@ import org.noear.snack.ONode;
import we.constants.CommonConstants;
import we.fizz.StepContext;
import we.fizz.exception.FizzRuntimeException;
import we.fizz.function.FuncExecutor;
import we.fizz.function.IFunc;
import we.util.MapUtil;
/**
@@ -194,25 +196,33 @@ public class PathMapping {
private static Object getRefValue(ONode ctxNode, String type, String path) {
Object obj = null;
try {
String p = path;
String defaultValue = null;
if (path.indexOf("|") != -1) {
p = path.substring(0, path.indexOf("|"));
defaultValue = path.substring(path.indexOf("|") + 1);
}
ONode val = select(ctxNode, handlePath(p));
if (val != null && !val.isNull()) {
obj = val;
} else {
obj = defaultValue;
}
// check if it is a function
if (path.startsWith(IFunc.NAME_SPACE_PREFIX)) {
obj = FuncExecutor.getInstance().exec(ctxNode, path);
if (obj != null && type != null) {
obj = cast(obj, type);
}
} catch (Exception e) {
e.printStackTrace();
throw new FizzRuntimeException(String.format("path mapping errer: %s , path mapping data: %s %s", e.getMessage(), type, path), e);
} else {
try {
String p = path;
String defaultValue = null;
if (path.indexOf("|") != -1) {
p = path.substring(0, path.indexOf("|"));
defaultValue = path.substring(path.indexOf("|") + 1);
}
ONode val = select(ctxNode, handlePath(p));
if (val != null && !val.isNull()) {
obj = val;
} else {
obj = defaultValue;
}
if (obj != null && type != null) {
obj = cast(obj, type);
}
} catch (Exception e) {
e.printStackTrace();
throw new FizzRuntimeException(String.format("path mapping errer: %s , path mapping data: %s %s", e.getMessage(), type, path), e);
}
}
return obj;
}
@@ -259,6 +269,8 @@ public class PathMapping {
case "string": {
if (obj instanceof ONode) {
obj = ((ONode) obj).val().getString();
} else {
obj = String.valueOf(obj.toString());
}
break;
}

View File

@@ -0,0 +1,125 @@
/*
* 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.function;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.noear.snack.ONode;
import we.fizz.input.PathMapping;
import we.util.DigestUtils;
/**
*
* @author Francis Dong
*
*/
class CodecFuncTests {
@Test
void contextLoads() {
}
@Test
void testMd5() {
String funcExpression = "fn.codec.md5(\"abc\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("900150983cd24fb0d6963f7d28e17f72", result.toString());
}
@Test
void testMd5_2() {
String funcExpression = "fn.codec.md5(fn.date.add(fn.date.add(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", 1, fn.math.addExact(999,1)), \"yyyy-MM-dd HH:mm:ss\", fn.math.addExact(0,1), 1000))";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(DigestUtils.md5Hex("2021-07-09 22:44:57"), result.toString());
}
@Test
void testSha1() {
String funcExpression = "fn.codec.sha1(\"abc\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("a9993e364706816aba3e25717850c26c9cd0d89d", result.toString());
}
@Test
void testSha256() {
String funcExpression = "fn.codec.sha256(\"abc\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", result.toString());
}
@Test
void testSha384() {
String funcExpression = "fn.codec.sha384(\"abc\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7", result.toString());
}
@Test
void testSha512() {
String funcExpression = "fn.codec.sha512(\"abc\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", result.toString());
}
@Test
void testBase64Encode() {
String funcExpression = "fn.codec.base64Encode(\"Base64编码介绍\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("QmFzZTY057yW56CB5LuL57uN", result.toString());
}
@Test
void testBase64Decode() {
String funcExpression = "fn.codec.base64Decode(\"QmFzZTY057yW56CB5LuL57uN\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("Base64编码介绍", result.toString());
}
@Test
void testAesEncrypt() {
String funcExpression = "fn.codec.aesEncrypt(\"abc\", \"12345678123456781234567812345678\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("MQn0lgH5W6tS6Ii2c7UeSg==", result.toString());
}
@Test
void testAesDecrypt() {
String funcExpression = "fn.codec.aesDecrypt(\"MQn0lgH5W6tS6Ii2c7UeSg==\", \"12345678123456781234567812345678\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("abc", result.toString());
}
@Test
void testDesEncrypt() {
String funcExpression = "fn.codec.desEncrypt(\"abc\", \"12345678123456781234567812345678\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("9YR6ZPdZufM=", result.toString());
}
@Test
void testDesDecrypt() {
String funcExpression = "fn.codec.desDecrypt(\"9YR6ZPdZufM=\", \"12345678123456781234567812345678\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("abc", result.toString());
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.function;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
/**
*
* @author Francis Dong
*
*/
class DateFuncTests {
@Test
void contextLoads() {
}
@Test
void testExec() {
String funcExpression = "fn.date.timestamp()";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
System.out.println(result);
}
@Test
void testAdd() {
String funcExpression = "fn.date.add(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", 1, 1000)";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("2021-07-09 22:44:56", result.toString());
}
@Test
void testEmbeddedAdd() {
String funcExpression = "fn.date.add(fn.date.add(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", 1, 1000), \"yyyy-MM-dd HH:mm:ss\", 1, 1000)";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("2021-07-09 22:44:57", result.toString());
}
@Test
void testFormatTs() {
String funcExpression = "fn.date.formatTs(1628825352227, \"yyyy-MM-dd HH:mm:ss\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("2021-08-13 11:29:12", result.toString());
}
@Test
void testChangePattern() {
String funcExpression = "fn.date.changePattern(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", \"MM-dd HH:mm\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("07-09 22:44", result.toString());
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.function;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.noear.snack.ONode;
import we.fizz.input.PathMapping;
/**
*
* @author Francis Dong
*
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
class ListFuncTests {
@Test
void contextLoads() {
}
private Map<String, Object> createRecord(String key, Object value) {
Map<String, Object> m = new HashMap<>();
m.put(key, value);
return m;
}
private Map<String, Object> createRecord2(int index) {
Map<String, Object> m = new HashMap<>();
m.put("a", "a" + index);
m.put("b", "b" + index);
m.put("c", "c" + index);
m.put("d", "d" + index);
m.put("e", "e" + index);
return m;
}
@Test
void testExpand() {
List<List<Object>> data = new ArrayList<>();
List<Object> subList1 = new ArrayList<>();
subList1.add(createRecord("a", "a1"));
subList1.add(createRecord("a", "a2"));
subList1.add(createRecord("a", "a3"));
List<Object> subList2 = new ArrayList<>();
subList2.add(createRecord("a", "a4"));
subList2.add(createRecord("a", "a5"));
subList2.add(createRecord("a", "a6"));
data.add(subList1);
data.add(subList2);
ONode ctxNode = ONode.load(new HashMap());
PathMapping.setByPath(ctxNode, "test.data", data, true);
String funcExpression = "fn.list.expand({test.data})";
List<Object> result = (List<Object>) FuncExecutor.getInstance().exec(ctxNode, funcExpression);
assertEquals(6, result.size());
assertEquals("a2", ((Map<String, Object>) result.get(1)).get("a").toString());
assertEquals("a4", ((Map<String, Object>) result.get(3)).get("a").toString());
}
@Test
void testMerge() {
List<Object> subList1 = new ArrayList<>();
subList1.add(createRecord("a", "a1"));
subList1.add(createRecord("a", "a2"));
subList1.add(createRecord("a", "a3"));
List<Object> subList2 = new ArrayList<>();
subList2.add(createRecord("a", "a4"));
subList2.add(createRecord("a", "a5"));
subList2.add(createRecord("a", "a6"));
ONode ctxNode = ONode.load(new HashMap());
PathMapping.setByPath(ctxNode, "test.data1", subList1, true);
PathMapping.setByPath(ctxNode, "test.data2", subList2, true);
String funcExpression = "fn.list.merge({test.data1}, {test.data2})";
List<Object> result = (List<Object>) FuncExecutor.getInstance().exec(ctxNode, funcExpression);
assertEquals(6, result.size());
assertEquals("a2", ((Map<String, Object>) result.get(1)).get("a").toString());
assertEquals("a4", ((Map<String, Object>) result.get(3)).get("a").toString());
}
@Test
void testExtract() {
List<Object> subList1 = new ArrayList<>();
subList1.add(createRecord2(1));
subList1.add(createRecord2(2));
subList1.add(createRecord2(3));
subList1.add(createRecord2(4));
subList1.add(createRecord2(5));
ONode ctxNode = ONode.load(new HashMap());
PathMapping.setByPath(ctxNode, "test.data", subList1, true);
String funcExpression = "fn.list.extract({test.data}, \"c\",\"b\", \"e\")";
List<Object> result = (List<Object>) FuncExecutor.getInstance().exec(ctxNode, funcExpression);
assertEquals(5, result.size());
assertEquals("c2", ((Map<String, Object>) result.get(1)).get("c").toString());
assertEquals("e4", ((Map<String, Object>) result.get(3)).get("e").toString());
assertEquals(null, ((Map<String, Object>) result.get(3)).get("a"));
assertEquals(null, ((Map<String, Object>) result.get(3)).get("d"));
// System.out.println(result);
}
}

View File

@@ -0,0 +1,179 @@
/*
* 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.function;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.noear.snack.ONode;
import we.fizz.input.PathMapping;
/**
*
* @author Francis Dong
*
*/
class MathFuncTests {
@Test
void contextLoads() {
}
@Test
void testAbsExact() {
String funcExpression = "fn.math.absExact(-3)";
long result = (long) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(3, result);
}
@Test
void testNegateExact() {
String funcExpression = "fn.math.negateExact(4)";
long result = (long) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(-4, result);
}
@Test
void testNegateExact2() {
String funcExpression = "fn.math.negateExact(-4)";
long result = (long) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(4, result);
}
@Test
void testAddExact() {
String funcExpression = "fn.math.addExact(14,-1)";
long result = (long) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(13, result);
}
@Test
void testSubtractExact() {
String funcExpression = "fn.math.subtractExact(14,-1)";
long result = (long) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(15, result);
}
@Test
void testMultiplyExact() {
String funcExpression = "fn.math.multiplyExact(14,2)";
long result = (long) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(28, result);
}
@Test
void testMaxExact() {
String funcExpression = "fn.math.maxExact(14,2)";
long result = (long) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(14, result);
}
@Test
void testMinExact() {
String funcExpression = "fn.math.minExact(14,2)";
long result = (long) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(2, result);
}
@Test
void testMod() {
String funcExpression = "fn.math.mod(13,2)";
long result = (long) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(1, result);
}
@Test
void testPow() {
String funcExpression = "fn.math.pow(2,3)";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(8, result);
}
@Test
void testSqrt() {
String funcExpression = "fn.math.sqrt(4)";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(2, result);
}
@Test
void testAbsDecimal() {
String funcExpression = "fn.math.absDecimal(-4)";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(4, result);
}
@Test
void testNegateDecimal() {
String funcExpression = "fn.math.negateDecimal(4)";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(-4, result);
}
@Test
void testSubtractDecimal() {
String funcExpression = "fn.math.subtractDecimal(4,1.3)";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(2.7, result);
}
@Test
void testMultiplyDecimal() {
String funcExpression = "fn.math.multiplyDecimal(4,2.2)";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(8.8, result);
}
@Test
void testDivideDecimal() {
String funcExpression = "fn.math.divideDecimal(4.8,2)";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(2.4, result);
}
@Test
void testMaxDecimal() {
String funcExpression = "fn.math.maxDecimal(4.8,2)";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(4.8, result);
}
@Test
void testMinDecimal() {
String funcExpression = "fn.math.minDecimal(4.8,2)";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(2, result);
}
@Test
void testScaleDecimal() {
String funcExpression = "fn.math.scaleDecimal(4.8456,2)";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals(4.85, result);
}
@Test
void testRandom() {
String funcExpression = "fn.math.random()";
double result = (double) FuncExecutor.getInstance().exec(null, funcExpression);
// System.out.println(result);
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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.function;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.noear.snack.ONode;
import we.fizz.input.PathMapping;
/**
*
* @author Francis Dong
*
*/
class StringFuncTests {
@Test
void contextLoads() {
}
@Test
void testConcat() {
String funcExpression = "fn.string.concat(\"2021-07-09 22:44:55\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("2021-07-09 22:44:55", result.toString());
}
@Test
void testConcat2() {
String funcExpression = "fn.string.concat(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("2021-07-09 22:44:55yyyy-MM-dd HH:mm:ss", result.toString());
}
@Test
void testConcatws() {
String funcExpression = "fn.string.concatws(\",\" , \"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("2021-07-09 22:44:55,yyyy-MM-dd HH:mm:ss", result.toString());
}
@Test
void testSubstring() {
String funcExpression = "fn.string.substring(\"2021-07-09 22:44:55\", 1 , 4)";
Object result = FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("2021-07-09 22:44:55".substring(1, 4), result.toString());
}
@Test
void testSubstring2() {
ONode ctxNode = ONode.load(new HashMap());
Map<String, Object> m = new HashMap<>();
m.put("a", "1");
m.put("b", "1");
PathMapping.setByPath(ctxNode, "data.dateStr", "2021-07-09 22:44:55", true);
PathMapping.setByPath(ctxNode, "data.startIndex", 1, true);
PathMapping.setByPath(ctxNode, "data", m, false);
String funcExpression = "fn.string.substring({data.dateStr}, {data.startIndex})";
// String funcExpression = "fn.string.substring(\"2021-07-09 22:44:55\", 1)";
Object result = FuncExecutor.getInstance().exec(ctxNode, funcExpression);
assertEquals("2021-07-09 22:44:55".substring(1), result.toString());
}
@Test
void testIndexOf() {
String funcExpression = "fn.string.indexOf(\"2021-07-09 22:44:55\", \"07\")";
int result = (int)FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("2021-07-09 22:44:55".indexOf("07"), result);
}
@Test
void testStartsWith() {
String funcExpression = "fn.string.startsWith(\"2021-07-09 22:44:55\", \"2021\")";
boolean result = (boolean)FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("2021-07-09 22:44:55".startsWith("2021"), result);
}
@Test
void testEndsWith() {
String funcExpression = "fn.string.endsWith(\"2021-07-09 22:44:55\", \"44:55\")";
boolean result = (boolean)FuncExecutor.getInstance().exec(null, funcExpression);
assertEquals("2021-07-09 22:44:55".endsWith("44:55"), result);
}
}

View File

@@ -370,6 +370,12 @@
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
</dependencyManagement>