Skip to content

Commit

Permalink
Merge pull request #2302 from GDLMadushanka/exptest
Browse files Browse the repository at this point in the history
Fix concurrency issue in expressions
  • Loading branch information
GDLMadushanka authored Jan 26, 2025
2 parents 5a8229e + f506d9f commit 0a64265
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.apache.synapse.core.axis2.Axis2MessageContext;

import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import javax.xml.namespace.QName;

public class Utils {
Expand Down Expand Up @@ -184,6 +185,7 @@ private static Object convertExpressionResult(Object evaluatedValue, String type
if (!(evaluatedValue instanceof Double)) {
handleDataTypeException("DOUBLE", expression, log);
}
evaluatedValue = BigDecimal.valueOf((Double) evaluatedValue);
break;
case INTEGER:
if (!(evaluatedValue instanceof Integer)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ public ExpressionResult evaluate(EvaluationContext context, boolean isObjectValu
case NOT_EQUALS:
return handleNotEquality(leftValue, rightValue);
case LESS_THAN:
return handleComparison(leftValue, rightValue, (a, b) -> a < b);
return handleComparison(leftValue, rightValue, (a, b) -> a.compareTo(b)<0);
case LESS_THAN_OR_EQUAL:
return handleComparison(leftValue, rightValue, (a, b) -> a <= b);
return handleComparison(leftValue, rightValue, (a, b) -> a.compareTo(b)<=0);
case GREATER_THAN:
return handleComparison(leftValue, rightValue, (a, b) -> a > b);
return handleComparison(leftValue, rightValue, (a, b) -> a.compareTo(b)>0);
case GREATER_THAN_OR_EQUAL:
return handleComparison(leftValue, rightValue, (a, b) -> a >= b);
return handleComparison(leftValue, rightValue, (a, b) -> a.compareTo(b)>=0);
case AND:
case AND_SYMBOL:
case OR:
Expand All @@ -113,7 +113,7 @@ public ExpressionResult evaluate(EvaluationContext context, boolean isObjectValu
}

private ExpressionResult handleComparison(ExpressionResult leftValue, ExpressionResult rightValue,
BiFunction<Double, Double, Boolean> comparison) {
BiFunction<BigDecimal, BigDecimal, Boolean> comparison) {
if ((leftValue.isDouble() || leftValue.isInteger()) && (rightValue.isDouble() || rightValue.isInteger())) {
return new ExpressionResult(comparison.apply(leftValue.asDouble(), rightValue.asDouble()));
}
Expand Down Expand Up @@ -152,10 +152,7 @@ private ExpressionResult handleLogical(ExpressionResult leftValue, ExpressionRes
private ExpressionResult handleAddition(ExpressionResult leftValue, ExpressionResult rightValue) {
if (leftValue.isNumeric() && rightValue.isNumeric()) {
if (leftValue.isDouble() || rightValue.isDouble()) {
BigDecimal left = new BigDecimal(leftValue.asString());
BigDecimal right = new BigDecimal(rightValue.asString());
BigDecimal sum = left.add(right);
return new ExpressionResult(sum.doubleValue());
return new ExpressionResult(leftValue.asDouble().add(rightValue.asDouble()));
} else if (leftValue.isLong() || rightValue.isLong()) {
return new ExpressionResult(leftValue.asLong() + rightValue.asLong());
} else {
Expand Down Expand Up @@ -187,8 +184,8 @@ private ExpressionResult handleArithmetic(ExpressionResult leftValue, Expression

// Promote to the highest precision type
if (leftIsDouble || rightIsDouble) {
double left = leftValue.asDouble();
double right = rightValue.asDouble();
BigDecimal left = leftValue.asDouble();
BigDecimal right = rightValue.asDouble();
return performDoubleOperation(left, right, operator);
} else if (leftIsLong || rightIsLong) {
long left = leftValue.asLong();
Expand All @@ -202,27 +199,25 @@ private ExpressionResult handleArithmetic(ExpressionResult leftValue, Expression
}
}

private ExpressionResult performDoubleOperation(double left, double right, Operator operator) {
BigDecimal left1 = new BigDecimal(String.valueOf(left));
BigDecimal right1 = new BigDecimal(String.valueOf(right));
private ExpressionResult performDoubleOperation(BigDecimal left, BigDecimal right, Operator operator) {
switch (operator) {
case SUBTRACT:
BigDecimal sum = left1.subtract(right1);
BigDecimal sum = left.subtract(right);
return new ExpressionResult(sum.doubleValue());
case MULTIPLY:
BigDecimal product = left1.multiply(right1);
return new ExpressionResult(product.doubleValue());
BigDecimal product = left.multiply(right);
return new ExpressionResult(product);
case DIVIDE:
if (right == 0) {
if (right.compareTo(BigDecimal.ZERO) == 0) {
throw new EvaluationException("Division by zero");
}
BigDecimal quotient = left1.divide(right1, BigDecimal.ROUND_HALF_UP);
BigDecimal quotient = left.divide(right, BigDecimal.ROUND_HALF_UP);
return new ExpressionResult(quotient.doubleValue());
case MODULO:
if (right == 0) {
if (right.compareTo(BigDecimal.ZERO) == 0) {
throw new EvaluationException("Modulo by zero");
}
BigDecimal remainder = left1.remainder(right1);
BigDecimal remainder = left.remainder(right);
return new ExpressionResult(remainder.doubleValue());
default:
throw new EvaluationException("Unsupported operator: " + operator + " between "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
import com.google.gson.internal.LazilyParsedNumber;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNode;
import org.apache.axis2.databinding.types.xsd._double;
import org.apache.synapse.util.synapse.expression.exception.EvaluationException;

import java.math.BigDecimal;
import java.util.List;

/**
Expand Down Expand Up @@ -70,7 +73,7 @@ public ExpressionResult(long value) {
this.value = value;
}

public ExpressionResult(double value) {
public ExpressionResult(BigDecimal value) {
this.value = value;
}

Expand Down Expand Up @@ -106,7 +109,9 @@ public String asString() {

// Method to get value as int
public int asInt() {
if (value instanceof Number) {
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof Number) {
return ((Number) value).intValue();
} else if (value instanceof JsonPrimitive && ((JsonPrimitive) value).isNumber()) {
return ((JsonPrimitive) value).getAsInt();
Expand All @@ -115,11 +120,13 @@ public int asInt() {
}

// Method to get value as double
public double asDouble() {
if (value instanceof Number) {
return ((Number) value).doubleValue();
public BigDecimal asDouble() {
if (value instanceof BigDecimal) {
return (BigDecimal) value;
} else if (value instanceof Number) {
return new BigDecimal(value.toString());
} else if (value instanceof JsonPrimitive && ((JsonPrimitive) value).isNumber()) {
return ((JsonPrimitive) value).getAsDouble();
return new BigDecimal(((JsonPrimitive) value).getAsString());
}
throw new EvaluationException("Value : " + value + " cannot be converted to double");
}
Expand Down Expand Up @@ -191,7 +198,8 @@ public boolean isLong() {
}

public boolean isDouble() {
return value instanceof Double || (value instanceof JsonPrimitive && isDouble((JsonPrimitive) value));
return value instanceof BigDecimal || value instanceof Double ||
(value instanceof JsonPrimitive && isDouble((JsonPrimitive) value));
}

public boolean isBoolean() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.google.gson.JsonElement;
import org.apache.synapse.util.synapse.expression.context.EvaluationContext;

import java.math.BigDecimal;

/**
* Represents a leaf node in the AST that holds a literal value.
*/
Expand Down Expand Up @@ -75,7 +77,9 @@ private ExpressionResult parseNumber(String value) {
return new ExpressionResult(Long.parseLong(value));
} catch (NumberFormatException e2) {
try {
return new ExpressionResult(Double.parseDouble(value));
Double.parseDouble(value);
// if double stored as big decimal, for accurate calculations
return new ExpressionResult(new BigDecimal(value));
} catch (NumberFormatException e3) {
throw new IllegalArgumentException("Value " + value + " is not a number");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -52,7 +53,6 @@
*/
public class PayloadAccessNode implements ExpressionNode {

private String expression;
private final String unProcessedExpression;
private final Map<String, ExpressionNode> arguments;

Expand All @@ -69,42 +69,39 @@ public enum Type {

public PayloadAccessNode(String expression, Map<String, ExpressionNode> arguments, Type type,
ExpressionNode predefinedFunctionNode) {
this.expression = expression;
this.unProcessedExpression = expression;
this.arguments = arguments;
this.type = type;
this.predefinedFunctionNode = predefinedFunctionNode;
Configuration.setDefaults(new Configuration.Defaults() {
private final JsonProvider jsonProvider = new GsonJsonProvider(new GsonBuilder().serializeNulls().create());
private final MappingProvider mappingProvider = new GsonMappingProvider();

public JsonProvider jsonProvider() {
return jsonProvider;
}

public MappingProvider mappingProvider() {
return mappingProvider;
}

public Set<Option> options() {
return EnumSet.noneOf(Option.class);
}
});
}

@Override
public ExpressionResult evaluate(EvaluationContext context, boolean isObjectValue) throws EvaluationException {
// Take a copy of the expression to avoid modifying the original expression
expression = unProcessedExpression;
String expression = unProcessedExpression;

if (expression.startsWith(ExpressionConstants.PAYLOAD)) {
expression = ExpressionConstants.PAYLOAD_$ + expression.substring(ExpressionConstants.PAYLOAD.length());
}
AtomicReference<String> expressionRef = new AtomicReference<>(expression);

for (Map.Entry<String, ExpressionNode> entry : arguments.entrySet()) {
Optional.ofNullable(entry.getValue())
.map(value -> value.evaluate(context, isObjectValue))
.ifPresent(result -> processResult(entry, result));
.ifPresent(result -> {
String regex = ExpressionUtils.escapeSpecialCharacters(entry.getKey());
String resultString = result.asString();
if (result.isString() && !entry.getValue().getClass().equals(FilterExpressionNode.class)) {
resultString = "\"" + resultString + "\"";
}
if (entry.getValue().getClass().equals(ArrayIndexNode.class)) {
resultString = resultString.replace("\"", "");
}
expressionRef.set(expressionRef.get().replaceFirst(regex, resultString));
});
}
expression = expressionRef.get();

Object result;
switch (type) {
Expand Down Expand Up @@ -139,24 +136,24 @@ public ExpressionResult evaluate(EvaluationContext context, boolean isObjectValu
}
String[] keyAndExpression = ExpressionUtils.extractVariableAndJsonPath(expressionToEvaluate);
String key = keyAndExpression[0];
String expression = keyAndExpression[1];
if (StringUtils.isNotEmpty(expression)) {
expression = expression.startsWith(".") ? "$" + expression : "$." + expression;
String newExpression = keyAndExpression[1];
if (StringUtils.isNotEmpty(newExpression)) {
newExpression = newExpression.startsWith(".") ? "$" + newExpression : "$." + newExpression;
}
Object keyValue = ((Map) variable).get(key);
if (keyValue == null) {
throw new EvaluationException("Could not find key: " + key + " in the variable: " + variable);
} else if (StringUtils.isEmpty(expression)) {
} else if (StringUtils.isEmpty(newExpression)) {
result = keyValue;
} else if (keyValue instanceof JsonElement) {
try {
result = JsonPath.parse(keyValue.toString()).read(expression);
result = JsonPath.parse(keyValue.toString()).read(newExpression);
} catch (PathNotFoundException e) {
// convert jsonPath error to native one
throw new EvaluationException(e.getMessage());
}
} else {
throw new EvaluationException("Could not evaluate JSONPath expression: " + expression
throw new EvaluationException("Could not evaluate JSONPath expression: " + newExpression
+ " on non-JSON object");
}
} else {
Expand Down Expand Up @@ -232,16 +229,4 @@ public ExpressionResult evaluate(EvaluationContext context, boolean isObjectValu
}
return null;
}

private void processResult(Map.Entry<String, ExpressionNode> entry, ExpressionResult result) {
String regex = ExpressionUtils.escapeSpecialCharacters(entry.getKey());
String resultString = result.asString();
if (result.isString() && !entry.getValue().getClass().equals(FilterExpressionNode.class)) {
resultString = "\"" + resultString + "\"";
}
if (entry.getValue().getClass().equals(ArrayIndexNode.class)) {
resultString = resultString.replace("\"", "");
}
expression = expression.replaceFirst(regex, resultString);
}
}
Loading

0 comments on commit 0a64265

Please sign in to comment.