io.github.linuxforhealth.core.data.JexlEngineUtil Maven / Gradle / Ivy
/*
* (C) Copyright IBM Corp. 2020
*
* SPDX-License-Identifier: Apache-2.0
*/
package io.github.linuxforhealth.core.data;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.text.StringTokenizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import io.github.linuxforhealth.core.exception.DataExtractionException;
public final class JexlEngineUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(JexlEngineUtil.class);
private static final List OPERATORS =
Lists.newArrayList(">", "<", "==", "!=", ">=", "<=");
private JexlEngine jexl;
private Map functions = new HashMap<>();
private Map exprCache = new HashMap();
public JexlEngineUtil() {
jexl = new JexlBuilder().silent(false).debug(true).strict(true).create();
LOGGER.info("silent:{} , strict :{} ", jexl.isSilent(), jexl.isStrict());
functions.put(StringUtils.class.getSimpleName(), StringUtils.class);
functions.put(NumberUtils.class.getSimpleName(), NumberUtils.class);
functions.put(String.class.getSimpleName(), String.class);
}
public JexlEngineUtil(Map functions) {
this();
functions.putAll(functions);
}
public JexlEngineUtil(String name, Object function) {
this();
functions.put(name, function);
}
public Object evaluate(String jexlExp, Map context) {
Preconditions.checkArgument(StringUtils.isNotBlank(jexlExp), "jexlExp cannot be blank");
Preconditions.checkArgument(context != null, "context cannot be null");
String trimedJexlExp = StringUtils.trim(jexlExp);
// ensure that expression
validateExpression(trimedJexlExp);
Map localContext = new HashMap<>(functions);
localContext.putAll(context);
JexlExpression exp = exprCache.get(trimedJexlExp);
if(exp == null) {
exp = jexl.createExpression(trimedJexlExp);
exprCache.put(trimedJexlExp, exp);
}
JexlContext jc = new MapContext();
localContext.entrySet().forEach(e -> jc.set(e.getKey(), e.getValue()));
// Now evaluate the expression, getting the result
try {
Object obj = exp.evaluate(jc);
return obj;
} catch (JexlException e) {
throw new DataExtractionException("Exception encountered during JEXL expression evaluation",
e);
}
}
public boolean evaluateCondition(String jexlExp, Map context) {
Preconditions.checkArgument(StringUtils.isNotBlank(jexlExp), "jexlExp cannot be blank");
Preconditions.checkArgument(context != null, "context cannot be null");
String trimedJexlExp = StringUtils.trim(jexlExp);
// ensure that expression
validateCondition(trimedJexlExp);
Map localContext = new HashMap<>(functions);
localContext.putAll(context);
JexlExpression exp = jexl.createExpression(trimedJexlExp);
JexlContext jc = new MapContext();
localContext.entrySet().forEach(e -> jc.set(e.getKey(), e.getValue()));
// Now evaluate the expression, getting the result
boolean obj = (boolean) exp.evaluate(jc);
return obj;
}
static void validateCondition(String input) {
boolean isValid = false;
StringTokenizer strtoken = new StringTokenizer(input, " ").setIgnoreEmptyTokens(true);
if (strtoken.getTokenList().size() == 3) {
String var1 = strtoken.nextToken();
String operator = strtoken.nextToken();
String var2 = strtoken.nextToken();
if (StringUtils.isAlphanumeric(var1) && StringUtils.isAlphanumeric(var2)
&& OPERATORS.contains(operator)) {
isValid = true;
}
}
if (!isValid) {
throw new IllegalArgumentException(
"Condition not supported, only the following format is supported, value1 value2, input:"
+ input);
}
}
private void validateExpression(String jexlExp) {
StringTokenizer stk = new StringTokenizer(jexlExp, ".").setIgnoreEmptyTokens(true);
String tok = stk.nextToken();
// format of expressions should be Function.method
// if only one part is specified like Function then this is not a valid expression.
if (stk.getTokenList().size() < 2 || functions.get(tok) == null) {
throw new IllegalArgumentException("Expression has unsupported function: " + tok);
}
if (jexlExp.contains(";")) {
throw new IllegalArgumentException(
"Expression cannot contain character ; Expression: " + jexlExp);
}
}
}