Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issues in synapse expressions #2285

Merged
merged 1 commit into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,15 @@ public static OMElement serializePath(SynapsePath path, String expression,
elem.addAttribute(elem.getOMFactory().createOMAttribute(
attribName, nullNS, expression));
} else if (path.getPathType() == SynapsePath.SYNAPSE_EXPRESSIONS_PATH) {
if (expression.startsWith("{") && expression.endsWith("}")) {
if (expression.startsWith("{${") && expression.endsWith("}}")) {
elem.addAttribute(elem.getOMFactory().createOMAttribute(
attribName, nullNS, expression));
} else if (expression.startsWith("{") && expression.endsWith("}")) {
elem.addAttribute(elem.getOMFactory().createOMAttribute(
attribName, nullNS, "{${" + expression.substring(1, expression.length() - 1) + "}}"));
} else {
elem.addAttribute(elem.getOMFactory().createOMAttribute(
attribName, nullNS, "{" + expression + "}"));
attribName, nullNS, "{${" + expression + "}"));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.apache.synapse.util.synapse.expression.context.EvaluationContext;
import org.apache.synapse.util.synapse.expression.exception.EvaluationException;

import java.math.BigDecimal;
import java.util.function.BiFunction;

/**
Expand Down Expand Up @@ -149,10 +150,23 @@ private ExpressionResult handleLogical(ExpressionResult leftValue, ExpressionRes
}

private ExpressionResult handleAddition(ExpressionResult leftValue, ExpressionResult rightValue) {
if (leftValue.isDouble() || rightValue.isDouble()) {
return new ExpressionResult(leftValue.asDouble() + rightValue.asDouble());
} else if (leftValue.isInteger() && rightValue.isInteger()) {
return new ExpressionResult(leftValue.asInt() + rightValue.asInt());
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());
} else if (leftValue.isLong() || rightValue.isLong()) {
return new ExpressionResult(leftValue.asLong() + rightValue.asLong());
} else {
try {
int result = Math.addExact(leftValue.asInt(), rightValue.asInt());
return new ExpressionResult(result);
} catch (ArithmeticException e) {
// handle overflow
return new ExpressionResult(leftValue.asLong() + rightValue.asLong());
}
}
} else if (leftValue.isString() && rightValue.isString()) {
return new ExpressionResult(leftValue.asString().concat(rightValue.asString()));
}
Expand All @@ -166,23 +180,109 @@ private ExpressionResult handleArithmetic(ExpressionResult leftValue, Expression
throw new EvaluationException("Arithmetic operation: " + operator + " between non-numeric values: "
+ leftValue.asString() + " and " + rightValue.asString());
}
boolean isInteger = leftValue.isInteger() && rightValue.isInteger();
boolean leftIsDouble = leftValue.isDouble();
boolean leftIsLong = leftValue.isLong();
boolean rightIsDouble = rightValue.isDouble();
boolean rightIsLong = rightValue.isLong();

// Promote to the highest precision type
if (leftIsDouble || rightIsDouble) {
double left = leftValue.asDouble();
double right = rightValue.asDouble();
return performDoubleOperation(left, right, operator);
} else if (leftIsLong || rightIsLong) {
long left = leftValue.asLong();
long right = rightValue.asLong();
return performLongOperation(left, right, operator);
} else {
// Default to int
int left = leftValue.asInt();
int right = rightValue.asInt();
return performIntOperation(left, right, operator);
}
}

private ExpressionResult performDoubleOperation(double left, double right, Operator operator) {
BigDecimal left1 = new BigDecimal(String.valueOf(left));
BigDecimal right1 = new BigDecimal(String.valueOf(right));
switch (operator) {
case SUBTRACT:
return isInteger ? new ExpressionResult(leftValue.asInt() - rightValue.asInt()) :
new ExpressionResult(leftValue.asDouble() - rightValue.asDouble());
BigDecimal sum = left1.subtract(right1);
return new ExpressionResult(sum.doubleValue());
case MULTIPLY:
return isInteger ? new ExpressionResult(leftValue.asInt() * rightValue.asInt()) :
new ExpressionResult(leftValue.asDouble() * rightValue.asDouble());
BigDecimal product = left1.multiply(right1);
return new ExpressionResult(product.doubleValue());
case DIVIDE:
return isInteger ? new ExpressionResult(leftValue.asInt() / rightValue.asInt()) :
new ExpressionResult(leftValue.asDouble() / rightValue.asDouble());
if (right == 0) {
throw new EvaluationException("Division by zero");
}
BigDecimal quotient = left1.divide(right1, BigDecimal.ROUND_HALF_UP);
return new ExpressionResult(quotient.doubleValue());
case MODULO:
return isInteger ? new ExpressionResult(leftValue.asInt() % rightValue.asInt()) :
new ExpressionResult(leftValue.asDouble() % rightValue.asDouble());
if (right == 0) {
throw new EvaluationException("Modulo by zero");
}
BigDecimal remainder = left1.remainder(right1);
return new ExpressionResult(remainder.doubleValue());
default:
throw new EvaluationException("Unsupported operator: " + operator + " between "
+ leftValue.asString() + " and " + rightValue.asString());
+ left + " and " + right);
}
}

private ExpressionResult performLongOperation(long left, long right, Operator operator) {
switch (operator) {
case SUBTRACT:
return new ExpressionResult(left - right);
case MULTIPLY:
return new ExpressionResult(left * right);
case DIVIDE:
if (right == 0L) {
throw new EvaluationException("Division by zero");
}
return new ExpressionResult((double) left / right);
case MODULO:
if (right == 0L) {
throw new EvaluationException("Modulo by zero");
}
return new ExpressionResult(left % right);
default:
throw new EvaluationException("Unsupported operator: " + operator + " between "
+ left + " and " + right);
}
}

private ExpressionResult performIntOperation(int left, int right, Operator operator) {
switch (operator) {
case SUBTRACT:
return new ExpressionResult(left - right);
case MULTIPLY:
try {
int result = Math.multiplyExact(left, right);
return new ExpressionResult(result);
} catch (ArithmeticException e) {
// handle overflow
return new ExpressionResult((long) left * (long) right);
}
case DIVIDE:
if (right == 0) {
throw new EvaluationException("Division by zero");
}
if (left % right == 0) {
// Exact division, return as integer
return new ExpressionResult(left / right);
} else {
// Division has a fractional part, return as double
return new ExpressionResult((double) left / right);
}
case MODULO:
if (right == 0) {
throw new EvaluationException("Modulo by zero");
}
return new ExpressionResult(left % right);
default:
throw new EvaluationException("Unsupported operator: " + operator + " between "
+ left + " and " + right);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNode;
import org.apache.synapse.util.synapse.expression.exception.EvaluationException;

import java.util.List;

/**
Expand Down Expand Up @@ -67,7 +66,7 @@ public ExpressionResult(int value) {
this.value = value;
}

public ExpressionResult(Long value) {
public ExpressionResult(long value) {
this.value = value;
}

Expand Down Expand Up @@ -180,13 +179,17 @@ public Class<?> getType() {
}

public boolean isNumeric() {
return isInteger() || isDouble();
return isInteger() || isDouble() || isLong();
}

public boolean isInteger() {
return value instanceof Integer || (value instanceof JsonPrimitive && isInteger((JsonPrimitive) value));
}

public boolean isLong() {
return value instanceof Long || (value instanceof JsonPrimitive && isLong((JsonPrimitive) value));
}

public boolean isDouble() {
return value instanceof Double || (value instanceof JsonPrimitive && isDouble((JsonPrimitive) value));
}
Expand Down Expand Up @@ -235,8 +238,10 @@ private boolean isInteger(JsonPrimitive jsonPrimitive) {
if (jsonPrimitive.isNumber()) {
Number number = jsonPrimitive.getAsNumber();
// Check if the number is an instance of integer types (int, long, short)
boolean initialCheck = number instanceof Integer || number instanceof Long || number instanceof Short;
if (!initialCheck && number instanceof LazilyParsedNumber) {
if (number instanceof Long && number.longValue() <= Integer.MAX_VALUE) {
return true;
}
if (number instanceof LazilyParsedNumber) {
// Check if the number is an instance of integer types (int, long, short)
String numberString = number.toString();
try {
Expand All @@ -246,17 +251,36 @@ private boolean isInteger(JsonPrimitive jsonPrimitive) {
return false;
}
}
return initialCheck;
}
return false; // Not a number, so it's not an integer
return false;
}

private boolean isLong(JsonPrimitive jsonPrimitive) {
if (jsonPrimitive.isNumber()) {
Number number = jsonPrimitive.getAsNumber();
// Check if the number is an instance of integer types (int, long, short)
if (number instanceof Long && number.longValue() > Integer.MAX_VALUE) {
return true;
}
if (number instanceof LazilyParsedNumber) {
// Check if the number is an instance of integer types (int, long, short)
String numberString = number.toString();
try {
Long.parseLong(numberString);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}
return false;
}

private boolean isDouble(JsonPrimitive jsonPrimitive) {
if (jsonPrimitive.isNumber()) {
Number number = jsonPrimitive.getAsNumber();
// Check if the number is an instance of floating-point types (float, double)
boolean initialCheck = number instanceof Float || number instanceof Double;
if (initialCheck) {
if (number instanceof Float || number instanceof Double) {
return true;
}
if (number instanceof LazilyParsedNumber) {
Expand All @@ -270,7 +294,7 @@ private boolean isDouble(JsonPrimitive jsonPrimitive) {
}
}
}
return false; // Not a number, so it's not a double
return false;
}

public boolean isOMElement() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private ExpressionResult parseNumber(String value) {
return new ExpressionResult(Integer.parseInt(value));
} catch (NumberFormatException e1) {
try {
return new ExpressionResult(Float.parseFloat(value));
return new ExpressionResult(Long.parseLong(value));
} catch (NumberFormatException e2) {
try {
return new ExpressionResult(Double.parseDouble(value));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ private ExpressionResult handleDoubleArgumentFunctions(EvaluationContext context
return handleCharAtFunction(source, argument1);
case ExpressionConstants.XPATH:
return evaluateXPATHExpression(context, source, argument1.asString(), isObjectValue);
case ExpressionConstants.ROUND:
return handleRoundFunction(source, argument1);
default:
throw new EvaluationException("Invalid function: " + functionName + " with two arguments");
}
Expand Down Expand Up @@ -279,6 +281,15 @@ private ExpressionResult handleRoundFunction(ExpressionResult result) {
throw new EvaluationException("Invalid argument provided for round function");
}

private ExpressionResult handleRoundFunction(ExpressionResult result, ExpressionResult decimalPlaces) {
if (result.isDouble() && decimalPlaces.isInteger() && decimalPlaces.asInt() > 0) {
return new ExpressionResult(ExpressionUtils.round(result.asDouble(), decimalPlaces.asInt()));
} else if (result.isInteger() || result.isLong()) {
return result;
}
throw new EvaluationException("Invalid argument provided for round function");
}

private ExpressionResult handleSqrtFunction(ExpressionResult result) {
if (result.isInteger()) {
return new ExpressionResult(Math.sqrt(result.asInt()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
package org.apache.synapse.util.synapse.expression.utils;

import org.apache.axiom.om.OMNode;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.util.List;

Expand Down Expand Up @@ -112,4 +115,16 @@ public static boolean isXMLVariable(Object variable) {
}
return isXML;
}

/**
* Rounds the given value to the specified number of decimal places.
* @param value The value to be rounded.
* @param decimalPlaces The number of decimal places to round to.
* @return The rounded value.
*/
public static Double round (double value, int decimalPlaces) {
BigDecimal bd = new BigDecimal(Double.toString(value));
bd = bd.setScale(decimalPlaces, RoundingMode.HALF_UP);
return bd.doubleValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand Down Expand Up @@ -63,6 +64,11 @@ public SynapseExpression(String synapseExpression) throws JaxenException {
parser.addErrorListener(errorListener);

ParseTree tree = parser.expression();
// if there are any tokens left after parsing the expression, throw an exception
if (tokens.LA(1) != Token.EOF) {
throw new JaxenException("Parse error: leftover input after parsing expression in " + synapseExpression);
}

ExpressionVisitor visitor = new ExpressionVisitor();
expressionNode = visitor.visit(tree);
if (errorListener.hasErrors()) {
Expand All @@ -73,6 +79,7 @@ public SynapseExpression(String synapseExpression) throws JaxenException {
throw new JaxenException(errorMessage.toString());
}
isContentAware = SynapseExpressionUtils.isSynapseExpressionContentAware(synapseExpression);
this.setPathType(SynapsePath.SYNAPSE_EXPRESSIONS_PATH);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void testSerializeThrowErrorMediator() throws JaxenException {
OMElement element = throwErrorMediatorSerializer.serializeSpecificMediator(mediator);
Assert.assertEquals("invalid type", type, element.getAttributeValue(new QName("type")));
Assert.assertEquals("invalid error message", err, element.getAttributeValue(new QName("errorMessage")));
mediator.setErrorMsg(new Value(new SynapseExpression(exp)));
mediator.setErrorMsg(new Value(new SynapseExpression("payload.err")));
element = throwErrorMediatorSerializer.serializeSpecificMediator(mediator);
Assert.assertEquals("invalid error message", "{" + exp + "}", element.getAttributeValue(new QName("errorMessage")));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void testWithXpath() throws JaxenException {
Assert.assertTrue(synapsePath.isContentAware());
synapsePath = new SynapseExpression("xpath(\"/student\")");
Assert.assertTrue(synapsePath.isContentAware());
synapsePath = new SynapseExpression("xpath(\"//*\") + vars.a$bc");
synapsePath = new SynapseExpression("xpath(\"//*\") + vars.abc");
Assert.assertTrue(synapsePath.isContentAware());
synapsePath = new SynapseExpression("xpath(\"$ctx:bla\") + $.age");
Assert.assertTrue(synapsePath.isContentAware());
Expand All @@ -81,9 +81,9 @@ public void testNegativeCases() throws JaxenException {
Assert.assertFalse(synapsePath.isContentAware());
synapsePath = new SynapseExpression("vars[\"payload\"]");
Assert.assertFalse(synapsePath.isContentAware());
synapsePath = new SynapseExpression("5 + var[\"payload\"].age");
synapsePath = new SynapseExpression("5 + vars[\"payload\"].age");
Assert.assertFalse(synapsePath.isContentAware());
synapsePath = new SynapseExpression("vars.a$.bc");
synapsePath = new SynapseExpression("vars.a.bc");
Assert.assertFalse(synapsePath.isContentAware());
synapsePath = new SynapseExpression("vars.books[?(@.category==\"payload.category\")]");
Assert.assertFalse(synapsePath.isContentAware());
Expand Down
Loading
Loading