From 0222047072760fa4095e76f22452c4adbaa8fbb9 Mon Sep 17 00:00:00 2001 From: Lahiru Madushanka Date: Tue, 29 Oct 2024 09:15:39 +0530 Subject: [PATCH 1/3] Introduce SIEL expression support Add new synapse path type called SIEL to evaluate complex expressions which are not supported in either JSONPath or XPATH. Fixes wso2/micro-integrator/issues/3749 --- modules/core/pom.xml | 42 ++ .../util/synapse_path/ExpressionLexer.g4 | 114 +++++ .../util/synapse_path/ExpressionParser.g4 | 182 ++++++++ .../org/apache/synapse/SynapseConstants.java | 56 +++ .../config/xml/PropertyMediatorFactory.java | 5 + .../synapse/config/xml/SynapsePath.java | 7 +- .../config/xml/SynapsePathFactory.java | 14 +- .../synapse_path/ast/ArgumentListNode.java | 47 ++ .../util/synapse_path/ast/ArrayIndexNode.java | 52 +++ .../synapse_path/ast/BinaryOperationNode.java | 188 ++++++++ .../ast/ConditionalExpressionNode.java | 52 +++ .../util/synapse_path/ast/ExpressionNode.java | 29 ++ .../synapse_path/ast/ExpressionResult.java | 200 ++++++++ .../ast/FilterExpressionNode.java | 62 +++ .../ast/HeadersAndPropertiesAccessNode.java | 67 +++ .../util/synapse_path/ast/LiteralNode.java | 102 +++++ .../synapse_path/ast/PayloadAccessNode.java | 170 +++++++ .../ast/PredefinedFunctionNode.java | 427 +++++++++++++++++ .../ast/SignedExpressionNode.java | 54 +++ .../context/EvaluationContext.java | 89 ++++ .../exception/EvaluationException.java | 27 ++ .../synapse_path/exception/SyntaxError.java | 70 +++ .../exception/SyntaxErrorListener.java | 90 ++++ .../synapse_path/utils/ExpressionUtils.java | 89 ++++ .../visitor/ExpressionVisitor.java | 428 ++++++++++++++++++ .../synapse/util/xpath/SynapsePath.java | 86 ++++ .../ConditionalExpressionTest.java | 51 +++ .../HeaderAndPropertyAccessTest.java | 196 ++++++++ .../PayloadAndVariableAccessTest.java | 133 ++++++ .../synapse_path/PreDefinedFunctionsTest.java | 353 +++++++++++++++ .../PrimitiveExpressionsTest.java | 173 +++++++ .../util/synapse_path/SyntaxErrorsTest.java | 55 +++ .../synapse/util/synapse_path/TestUtils.java | 183 ++++++++ pom.xml | 14 +- 34 files changed, 3899 insertions(+), 8 deletions(-) create mode 100644 modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/ExpressionLexer.g4 create mode 100644 modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/ExpressionParser.g4 create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ArgumentListNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ArrayIndexNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/BinaryOperationNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ConditionalExpressionNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ExpressionNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ExpressionResult.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/FilterExpressionNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/HeadersAndPropertiesAccessNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/LiteralNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/PayloadAccessNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/PredefinedFunctionNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/SignedExpressionNode.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/context/EvaluationContext.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/exception/EvaluationException.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/exception/SyntaxError.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/exception/SyntaxErrorListener.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/utils/ExpressionUtils.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/synapse_path/visitor/ExpressionVisitor.java create mode 100644 modules/core/src/main/java/org/apache/synapse/util/xpath/SynapsePath.java create mode 100644 modules/core/src/test/java/org/apache/synapse/util/synapse_path/ConditionalExpressionTest.java create mode 100644 modules/core/src/test/java/org/apache/synapse/util/synapse_path/HeaderAndPropertyAccessTest.java create mode 100644 modules/core/src/test/java/org/apache/synapse/util/synapse_path/PayloadAndVariableAccessTest.java create mode 100644 modules/core/src/test/java/org/apache/synapse/util/synapse_path/PreDefinedFunctionsTest.java create mode 100644 modules/core/src/test/java/org/apache/synapse/util/synapse_path/PrimitiveExpressionsTest.java create mode 100644 modules/core/src/test/java/org/apache/synapse/util/synapse_path/SyntaxErrorsTest.java create mode 100644 modules/core/src/test/java/org/apache/synapse/util/synapse_path/TestUtils.java diff --git a/modules/core/pom.xml b/modules/core/pom.xml index f9985835e5..e574dda528 100644 --- a/modules/core/pom.xml +++ b/modules/core/pom.xml @@ -145,6 +145,44 @@ + + org.antlr + antlr4-maven-plugin + + + antlr + generate-sources + + antlr4 + + + ${project.basedir}/src/main/antlr4 + + -visitor + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/antlr4 + + + + + @@ -506,5 +544,9 @@ org.apache.logging.log4j log4j-slf4j-impl + + org.antlr + antlr4-runtime + diff --git a/modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/ExpressionLexer.g4 b/modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/ExpressionLexer.g4 new file mode 100644 index 0000000000..072f463914 --- /dev/null +++ b/modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/ExpressionLexer.g4 @@ -0,0 +1,114 @@ +lexer grammar ExpressionLexer; + +JSONPATH_FUNCTIONS: 'contains ' | 'in' | 'nin' | 'subsetof' | 'size' | 'empty' | 'empty true' | 'empty false' | '=~'; + +// Tokens for identifiers, operators, and keywords +VAR: 'var'; +PAYLOAD: 'payload' | '$'; +HEADERS: 'headers'; +CONFIG: 'config'; +ATTRIBUTES: 'attributes'; +AXIS2: 'axis2'; +SYNAPSE: 'synapse'; +REGISTRY: 'registry'; +SECRET: 'secret'; +BASE64ENCODE: 'base64encode'; +BASE64DECODE: 'base64decode'; +URLENCODE: 'urlEncode'; +URLDECODE: 'urlDecode'; +NOW: 'now'; +TODAY: 'today'; +FORMATDATE: 'formatDate'; +ISNUMBER: 'isNumber'; +ISSTRING: 'isString'; +ISARRAY: 'isArray'; +ISOBJECT: 'isObject'; +ROUND: 'round'; +INTEGER: 'integer'; +FLOAT: 'float'; +STRING: 'string'; +BOOLEAN: 'boolean'; +ABS: 'abs'; +FLOOR: 'floor'; +CEIL: 'ceil'; +SQRT: 'sqrt'; +LOG: 'log'; +POW: 'pow'; +LENGTH: 'length'; +TOUPPER: 'toUpper'; +TOLOWER: 'toLower'; +SUBSTRING: 'subString'; +STARTSWITH: 'startsWith'; +ENDSWITH: 'endsWith'; +CONTAINS: 'contains'; +TRIM: 'trim'; +REPLACE: 'replace'; +SPLIT: 'split'; +AND: 'and' | '&&'; +OR: 'or' | '||'; +NOT: 'not' | '!'; + +DOUBLE_DOT : '..'; +ASTERISK : '*'; + +// Operators +PLUS: '+'; +MINUS: '-'; +DIV: '/'; +MODULO: '%'; +EQ: '=='; +NEQ: '!='; +GT: '>'; +LT: '<'; +GTE: '>='; +LTE: '<='; + +// Delimiters +LPAREN: '('; +RPAREN: ')'; +LBRACKET: '['; +RBRACKET: ']'; +DOT: '.'; +COMMA: ','; +COLON: ':'; +QUOTE: '"' | '\''; + +// Literals +BOOLEAN_LITERAL: 'true' | 'false'; +NUMBER: '-'? [0-9]+ ('.' [0-9]+)?; + + +STRING_LITERAL : ('"' (ESC | ~["\\])* '"' | '\'' (ESC | ~['\\])* '\''); + + +fragment ESC + : '\\' [btnfr"'\\/] // Basic escape sequences + | UNICODE_ESC + | OCTAL_ESC + ; + +fragment UNICODE_ESC + : '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT + ; + +fragment OCTAL_ESC + : '\\' [0-3]? [0-7] [0-7] + ; + +fragment HEX_DIGIT + : [0-9a-fA-F] + ; + +NULL_LITERAL + : 'null' // Define null as a recognized keyword + ; + +// Identifiers +ID: [a-zA-Z_][a-zA-Z_0-9]*; +GETPROPERTY: 'getProperty'; +// Special symbols for JSONPath filter expressions +QUESTION: '?'; +AT: '@'; + +// Whitespace +WS: [ \t\n\r]+ -> skip; diff --git a/modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/ExpressionParser.g4 b/modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/ExpressionParser.g4 new file mode 100644 index 0000000000..5f72f75261 --- /dev/null +++ b/modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/ExpressionParser.g4 @@ -0,0 +1,182 @@ +parser grammar ExpressionParser; + +options { + tokenVocab = ExpressionLexer; +} + +expression + : comparisonExpression + | conditionalExpression + | EOF + ; + +conditionalExpression + : comparisonExpression (QUESTION expression COLON expression)? + ; + +comparisonExpression + : logicalExpression ( (GT | LT | GTE | LTE | EQ | NEQ) logicalExpression )* + | logicalExpression (EQ | NEQ) NULL_LITERAL // Allow comparison to null + ; + +logicalExpression + : arithmeticExpression (AND logicalExpression | OR logicalExpression)? + ; + +arithmeticExpression + : term ( (PLUS | MINUS) term )* + ; + +term + : factor ( (ASTERISK | DIV | MODULO) factor )* + ; + +factor + : literal + | functionCall + | variableAccess + | payloadAccess + | headerAccess + | configAccess + | attributeAccess + | LPAREN expression RPAREN + ; + +configAccess + : CONFIG DOT headerName + ; + +headerAccess + : HEADERS DOT headerName + ; + +headerName + : ID // Standard identifier + | STRING_LITERAL // Quoted string literal to allow spaces + ; + +attributeAccess + : ATTRIBUTES (DOT AXIS2 DOT propertyName // Access syntax for attributes.axis2.property + | DOT SYNAPSE DOT propertyName) // Access syntax for attributes.synapse.property + ; + +propertyName + : ID // Standard identifier + | STRING_LITERAL // Quoted string literal to allow spaces + ; + +literal + : arrayLiteral + | BOOLEAN_LITERAL + | NUMBER + | STRING_LITERAL + | NULL_LITERAL + ; + +variableAccess + : VAR ( DOT ID // Dot notation: var.variableName + | (DOT)? LBRACKET STRING_LITERAL RBRACKET // Bracket notation: var["variableName"] + ) + ( (DOUBLE_DOT ASTERISK // Handles recursive descent and wildcard, like var.variableName..* + | DOT ASTERISK // Handles wildcard after dot notation, like var.variableName.* + | DOT ID + | LBRACKET arrayIndex RBRACKET // Handles array access, like var.variableName[...] + | DOT LBRACKET arrayIndex RBRACKET // Handles dot followed by bracket notation, like var.variableName.["property"] + )* + ) + ; + +arrayLiteral + : LBRACKET (expression (COMMA expression)*)? RBRACKET // Array with zero or more literals, separated by commas + ; + +payloadAccess + : PAYLOAD ( (DOUBLE_DOT ASTERISK + | DOUBLE_DOT ID + | DOT ID + | LBRACKET arrayIndex RBRACKET + | DOT LBRACKET arrayIndex RBRACKET + | DOT ASTERISK)* + | DOUBLE_DOT ID (LBRACKET arrayIndex RBRACKET)? ) + ; + +arrayIndex + : NUMBER + | STRING_LITERAL + | expression + | multipleArrayIndices + | sliceArrayIndex + | expression ( (PLUS | MINUS | MULT | DIV ) expression)* + | ASTERISK + | QUESTION? filterExpression + ; + +multipleArrayIndices + : expression (COMMA expression)+ + ; + +sliceArrayIndex + : signedExpressions? COLON signedExpressions? (COLON signedExpressions?)? + ; + +signedExpressions + : MINUS? expression + ; + +filterExpression + : (filterComponent)+ + ; + +filterComponent + : variableAccess + | payloadAccess + | stringOrOperator + | headerAccess + | configAccess + | attributeAccess + | functionCall + ; + +stringOrOperator + : QUESTION | AT | JSONPATH_FUNCTIONS| STRING_LITERAL |NUMBER | BOOLEAN_LITERAL | ID | GT | LT | GTE | LTE | EQ | NEQ + | PLUS | MINUS | MULT | DIV | LPAREN | RPAREN | DOT | COMMA | COLON | WS | AND | OR | NOT | ASTERISK + ; + + + +functionCall + : LENGTH LPAREN expression RPAREN + | TOUPPER LPAREN expression RPAREN + | TOLOWER LPAREN expression RPAREN + | SUBSTRING LPAREN expression COMMA expression (COMMA expression)? RPAREN + | STARTSWITH LPAREN expression COMMA expression RPAREN + | ENDSWITH LPAREN expression COMMA expression RPAREN + | CONTAINS LPAREN expression COMMA expression RPAREN + | TRIM LPAREN expression RPAREN + | REPLACE LPAREN expression COMMA expression COMMA expression RPAREN + | SPLIT LPAREN expression COMMA expression RPAREN + | ABS LPAREN expression RPAREN + | FLOOR LPAREN expression RPAREN + | CEIL LPAREN expression RPAREN + | SQRT LPAREN expression RPAREN + | LOG LPAREN expression RPAREN + | POW LPAREN expression COMMA expression RPAREN + | REGISTRY LPAREN expression RPAREN (DOT GETPROPERTY LPAREN expression RPAREN)? + | SECRET LPAREN expression RPAREN + | BASE64ENCODE LPAREN expression (COMMA expression)? RPAREN + | BASE64DECODE LPAREN expression RPAREN + | URLENCODE LPAREN expression (COMMA expression)? RPAREN + | URLDECODE LPAREN expression RPAREN + | ISNUMBER LPAREN expression RPAREN + | ISSTRING LPAREN expression RPAREN + | ISARRAY LPAREN expression RPAREN + | ISOBJECT LPAREN expression RPAREN + | NOW LPAREN RPAREN + | TODAY LPAREN STRING_LITERAL RPAREN + | FORMATDATE LPAREN expression COMMA STRING_LITERAL RPAREN + | ROUND LPAREN expression RPAREN + | INTEGER LPAREN expression RPAREN + | FLOAT LPAREN expression RPAREN + | STRING LPAREN expression RPAREN + | BOOLEAN LPAREN expression RPAREN + ; diff --git a/modules/core/src/main/java/org/apache/synapse/SynapseConstants.java b/modules/core/src/main/java/org/apache/synapse/SynapseConstants.java index 47647ee066..e4e987f8ef 100644 --- a/modules/core/src/main/java/org/apache/synapse/SynapseConstants.java +++ b/modules/core/src/main/java/org/apache/synapse/SynapseConstants.java @@ -625,4 +625,60 @@ public enum ENDPOINT_TIMEOUT_TYPE { ENDPOINT_TIMEOUT, GLOBAL_TIMEOUT, HTTP_CONNE public static final String JAEGER_SPAN_ID = "jaeger_span_id"; public static final String ANALYTICS_METADATA = "ANALYTICS_METADATA"; + + // Constants related to SIEL + public static final String AND = "and"; + public static final String OR = "or"; + public static final String NOT = "not"; + public static final String TO_LOWER = "toLower"; + public static final String TO_UPPER = "toUpper"; + public static final String LENGTH = "length"; + public static final String SUBSTRING = "subString"; + public static final String STARTS_WITH = "startsWith"; + public static final String ENDS_WITH = "endsWith"; + public static final String CONTAINS = "contains"; + public static final String TRIM = "trim"; + public static final String REPLACE = "replace"; + public static final String SPLIT = "split"; + public static final String NOW = "now"; + public static final String ABS = "abs"; + public static final String CEIL = "ceil"; + public static final String FLOOR = "floor"; + public static final String SQRT = "sqrt"; + public static final String LOG = "log"; + public static final String POW = "pow"; + public static final String B64ENCODE = "base64encode"; + public static final String B64DECODE = "base64decode"; + public static final String URL_ENCODE = "urlEncode"; + public static final String URL_DECODE = "urlDecode"; + public static final String IS_NUMBER = "isNumber"; + public static final String IS_STRING = "isString"; + public static final String IS_ARRAY = "isArray"; + public static final String IS_OBJECT = "isObject"; + + public static final String ROUND = "round"; + public static final String INTEGER = "integer"; + public static final String FLOAT = "float"; + public static final String STRING = "string"; + public static final String BOOLEAN = "boolean"; + + public static final String PAYLOAD = "payload"; + public static final String PAYLOAD_$ = "$"; + public static final String SIEL_IDENTIFIER_START = "#["; + public static final String SIEL_IDENTIFIER_END = "]"; + public static final String AXIS2 = "axis2"; + + public static final String UNKNOWN = "unknown"; + + public static final class SIELErrors { + public static final String IF_CONDITION_ERROR = "Condition is not a boolean value"; + private static final String INVALID_ARGUMENT = "Invalid argument type provided for %s function"; + private static final String INVALID_OPERATION = "Invalid argument type provided for %s operation"; + public static String getInvalidArgumentMessage(String functionName) { + return String.format(INVALID_ARGUMENT, functionName); + } + public static String getInvalidOperationMessage(String functionName) { + return String.format(INVALID_OPERATION, functionName); + } + } } diff --git a/modules/core/src/main/java/org/apache/synapse/config/xml/PropertyMediatorFactory.java b/modules/core/src/main/java/org/apache/synapse/config/xml/PropertyMediatorFactory.java index 5b7c45ca36..abea2741ae 100644 --- a/modules/core/src/main/java/org/apache/synapse/config/xml/PropertyMediatorFactory.java +++ b/modules/core/src/main/java/org/apache/synapse/config/xml/PropertyMediatorFactory.java @@ -22,11 +22,13 @@ import org.apache.axiom.om.OMAttribute; import org.apache.axiom.om.OMElement; import org.apache.synapse.Mediator; +import org.apache.synapse.SynapseConstants; import org.apache.synapse.SynapseException; import org.apache.synapse.mediators.Value; import org.apache.synapse.mediators.builtin.PropertyMediator; import org.apache.synapse.util.MediatorPropertyUtils; import org.apache.synapse.util.xpath.SynapseJsonPath; +import org.apache.synapse.util.xpath.SynapsePath; import org.apache.synapse.util.xpath.SynapseXPath; import org.jaxen.JaxenException; @@ -83,6 +85,9 @@ public Mediator createSpecificMediator(OMElement elem, Properties properties) { String nameExpression = nameAttributeValue.substring(1, nameAttributeValue.length() - 1); if(nameExpression.startsWith("json-eval(")) { new SynapseJsonPath(nameExpression.substring(10, nameExpression.length() - 1)); + } else if (nameExpression.startsWith(SynapseConstants.SIEL_IDENTIFIER_START) && + nameExpression.endsWith(SynapseConstants.SIEL_IDENTIFIER_END)) { + new SynapsePath(nameExpression.substring(2, nameExpression.length() - 1)); } else { new SynapseXPath(nameExpression); } diff --git a/modules/core/src/main/java/org/apache/synapse/config/xml/SynapsePath.java b/modules/core/src/main/java/org/apache/synapse/config/xml/SynapsePath.java index 133aaff73d..77b2c1a650 100644 --- a/modules/core/src/main/java/org/apache/synapse/config/xml/SynapsePath.java +++ b/modules/core/src/main/java/org/apache/synapse/config/xml/SynapsePath.java @@ -27,6 +27,7 @@ public abstract class SynapsePath extends AXIOMXPath { public static final String X_PATH = "X_PATH"; public static final String JSON_PATH = "JSON_PATH"; + public static final String SIEL_PATH = "SIEL_PATH"; private String pathType = null; public DOMSynapseXPathNamespaceMap domNamespaceMap = new DOMSynapseXPathNamespaceMap(); @@ -60,9 +61,11 @@ public SynapsePath(String path, String pathType, Log log) throws JaxenException private String inferPathType(String expression) { if (expression.startsWith("json-eval(")) { - return X_PATH; - } else { return JSON_PATH; + } else if(expression.startsWith("#[") && expression.endsWith("]")) { + return SIEL_PATH; + } else { + return X_PATH; } } diff --git a/modules/core/src/main/java/org/apache/synapse/config/xml/SynapsePathFactory.java b/modules/core/src/main/java/org/apache/synapse/config/xml/SynapsePathFactory.java index 791c7612e8..8303492bf9 100644 --- a/modules/core/src/main/java/org/apache/synapse/config/xml/SynapsePathFactory.java +++ b/modules/core/src/main/java/org/apache/synapse/config/xml/SynapsePathFactory.java @@ -27,6 +27,7 @@ import org.apache.synapse.SynapseException; import org.apache.synapse.config.SynapsePropertiesLoader; import org.apache.synapse.util.xpath.SynapseJsonPath; +import org.apache.synapse.util.xpath.SynapsePath; import org.apache.synapse.util.xpath.SynapseXPath; import org.jaxen.JaxenException; @@ -39,16 +40,19 @@ public class SynapsePathFactory { private static final Log log = LogFactory.getLog(SynapsePathFactory.class); - public static SynapsePath getSynapsePath(OMElement elem, QName attribName) + public static org.apache.synapse.config.xml.SynapsePath getSynapsePath(OMElement elem, QName attribName) throws JaxenException { - SynapsePath path = null; + org.apache.synapse.config.xml.SynapsePath path = null; OMAttribute pathAttrib = elem.getAttribute(attribName); if (pathAttrib != null && pathAttrib.getAttributeValue() != null) { if(pathAttrib.getAttributeValue().startsWith("json-eval(")) { path = new SynapseJsonPath(pathAttrib.getAttributeValue().substring(10, pathAttrib.getAttributeValue().length() - 1)); + } else if (pathAttrib.getAttributeValue().startsWith(SynapseConstants.SIEL_IDENTIFIER_START) && + pathAttrib.getAttributeValue().endsWith(SynapseConstants.SIEL_IDENTIFIER_END)) { + path = new SynapsePath(pathAttrib.getAttributeValue().substring(2, pathAttrib.getAttributeValue().length() - 1)); } else { try { path = new SynapseXPath(pathAttrib.getAttributeValue()); @@ -79,8 +83,8 @@ public static SynapsePath getSynapsePath(OMElement elem, QName attribName) return path; } - public static SynapsePath getSynapsePathfromExpression(OMElement elem, String expression) throws JaxenException { - SynapsePath path = null; + public static org.apache.synapse.config.xml.SynapsePath getSynapsePathfromExpression(OMElement elem, String expression) throws JaxenException { + org.apache.synapse.config.xml.SynapsePath path = null; if (expression != null) { if (expression.startsWith("json-eval(")) { path = new SynapseJsonPath(expression.substring(10, expression.length() - 1)); @@ -112,7 +116,7 @@ public static SynapsePath getSynapsePathfromExpression(OMElement elem, String ex return path; } - public static SynapsePath getSynapsePath(OMElement elem, String expression) + public static org.apache.synapse.config.xml.SynapsePath getSynapsePath(OMElement elem, String expression) throws JaxenException { if (expression == null) { diff --git a/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ArgumentListNode.java b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ArgumentListNode.java new file mode 100644 index 0000000000..e2f8f2bc61 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ArgumentListNode.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.synapse.util.synapse_path.ast; + +import org.apache.synapse.util.synapse_path.context.EvaluationContext; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class handles the list of arguments passed to a function. + */ +public class ArgumentListNode implements ExpressionNode { + private final List arguments = new ArrayList<>(); + + public ArgumentListNode() { + } + + public void addArgument(ExpressionNode argument) { + arguments.add(argument); + } + + public List getArguments() { + return arguments; + } + + @Override + public ExpressionResult evaluate(EvaluationContext context) { + return null; + } + +} diff --git a/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ArrayIndexNode.java b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ArrayIndexNode.java new file mode 100644 index 0000000000..72a42694f6 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ArrayIndexNode.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.synapse.util.synapse_path.ast; + +import org.apache.synapse.util.synapse_path.context.EvaluationContext; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class handles the index of an array in AST. + */ +public class ArrayIndexNode implements ExpressionNode { + private final List indexArray; + private final char separator; + + public ArrayIndexNode(ArgumentListNode arguments, char separator) { + this.indexArray = arguments.getArguments(); + this.separator = separator; + } + + @Override + public ExpressionResult evaluate(EvaluationContext context) { + List indexList = new ArrayList<>(); + for (ExpressionNode index : indexArray) { + if (index == null) { + indexList.add(""); + continue; + } + ExpressionResult result = index.evaluate(context); + if (result != null) { + indexList.add(result.asString()); + } + } + return new ExpressionResult(String.join(String.valueOf(separator), indexList)); + } +} diff --git a/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/BinaryOperationNode.java b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/BinaryOperationNode.java new file mode 100644 index 0000000000..05b85b8bbb --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/BinaryOperationNode.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.synapse.util.synapse_path.ast; + +import org.apache.synapse.util.synapse_path.context.EvaluationContext; +import org.apache.synapse.util.synapse_path.exception.EvaluationException; + +import java.util.function.BiFunction; + +/** + * Represents a binary operation between two nodes in AST. + */ +public class BinaryOperationNode implements ExpressionNode { + + private final ExpressionNode left; + private final ExpressionNode right; + private final Operator operator; + + public enum Operator { + ADD("+"), + SUBTRACT("-"), + MULTIPLY("*"), + DIVIDE("/"), + MODULO("%"), + EQUALS("=="), + NOT_EQUALS("!="), + LESS_THAN("<"), + LESS_THAN_OR_EQUAL("<="), + GREATER_THAN(">"), + GREATER_THAN_OR_EQUAL(">="), + AND("and"), + AND_SYMBOL("&&"), + OR("or"), + OR_SYMBOL("||"); + + private final String symbol; + + Operator(String symbol) { + this.symbol = symbol; + } + + public static Operator fromString(String symbol) { + for (Operator op : values()) { + if (op.symbol.equals(symbol)) { + return op; + } + } + throw new IllegalArgumentException("Unsupported operator: " + symbol); + } + } + + public BinaryOperationNode(ExpressionNode left, String operator, ExpressionNode right) { + this.left = left; + this.operator = Operator.fromString(operator.trim().toLowerCase()); + this.right = right; + } + + @Override + public ExpressionResult evaluate(EvaluationContext context) throws EvaluationException { + ExpressionResult leftValue = left.evaluate(context); + ExpressionResult rightValue = right.evaluate(context); + if ((leftValue == null || rightValue == null) && + (operator != Operator.EQUALS && operator != Operator.NOT_EQUALS)) { + throw new EvaluationException("Null inputs for " + operator + " operation: " + leftValue + + " and " + rightValue); + } + + switch (operator) { + case ADD: + return handleAddition(leftValue, rightValue); + case SUBTRACT: + case MULTIPLY: + case DIVIDE: + case MODULO: + return handleArithmetic(leftValue, rightValue, operator); + case EQUALS: + return handleEquality(leftValue, rightValue); + case NOT_EQUALS: + return handleNotEquality(leftValue, rightValue); + case LESS_THAN: + return handleComparison(leftValue, rightValue, (a, b) -> a < b); + case LESS_THAN_OR_EQUAL: + return handleComparison(leftValue, rightValue, (a, b) -> a <= b); + case GREATER_THAN: + return handleComparison(leftValue, rightValue, (a, b) -> a > b); + case GREATER_THAN_OR_EQUAL: + return handleComparison(leftValue, rightValue, (a, b) -> a >= b); + case AND: + case AND_SYMBOL: + case OR: + case OR_SYMBOL: + return handleLogical(leftValue, rightValue, operator); + default: + throw new EvaluationException("Unsupported operator: " + operator + " between " + + leftValue.asString() + " and " + rightValue.asString()); + } + } + + private ExpressionResult handleComparison(ExpressionResult leftValue, ExpressionResult rightValue, + BiFunction comparison) { + if ((leftValue.isDouble() || leftValue.isInteger()) && (rightValue.isDouble() || rightValue.isInteger())) { + return new ExpressionResult(comparison.apply(leftValue.asDouble(), rightValue.asDouble())); + } + throw new EvaluationException("Comparison between non-numeric values: " + + leftValue.asString() + " and " + rightValue.asString()); + } + + private ExpressionResult handleEquality(ExpressionResult leftValue, ExpressionResult rightValue) { + if (leftValue != null && rightValue != null) { + return new ExpressionResult(leftValue.asString().equals(rightValue.asString())); + } else if (leftValue == null && rightValue == null) { + return new ExpressionResult(true); + } + return new ExpressionResult(false); + } + + private ExpressionResult handleNotEquality(ExpressionResult leftValue, ExpressionResult rightValue) { + if (leftValue != null && rightValue != null) { + return new ExpressionResult(!leftValue.asString().equals(rightValue.asString())); + } else if (leftValue == null && rightValue == null) { + return new ExpressionResult(false); + } + return new ExpressionResult(true); + } + + private ExpressionResult handleLogical(ExpressionResult leftValue, ExpressionResult rightValue, Operator operator) { + if (leftValue.isBoolean() && rightValue.isBoolean()) { + BiFunction logicOperation = operator + == Operator.AND || operator == Operator.AND_SYMBOL ? (a, b) -> a && b : (a, b) -> a || b; + return new ExpressionResult(logicOperation.apply(leftValue.asBoolean(), rightValue.asBoolean())); + } + throw new EvaluationException("Logical operation between non-boolean values: " + + leftValue.asString() + " and " + rightValue.asString()); + } + + 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()); + } else if (leftValue.isString() && rightValue.isString()) { + return new ExpressionResult(leftValue.asString().concat(rightValue.asString())); + } + throw new EvaluationException("Addition between non-numeric values: " + leftValue.asString() + + " and " + rightValue.asString()); + } + + private ExpressionResult handleArithmetic(ExpressionResult leftValue, ExpressionResult rightValue, + Operator operator) { + if (!leftValue.isNumeric() || !rightValue.isNumeric()) { + throw new EvaluationException("Arithmetic operation: " + operator + " between non-numeric values: " + + leftValue.asString() + " and " + rightValue.asString()); + } + boolean isInteger = leftValue.isInteger() && rightValue.isInteger(); + switch (operator) { + case SUBTRACT: + return isInteger ? new ExpressionResult(leftValue.asInt() - rightValue.asInt()) : + new ExpressionResult(leftValue.asDouble() - rightValue.asDouble()); + case MULTIPLY: + return isInteger ? new ExpressionResult(leftValue.asInt() * rightValue.asInt()) : + new ExpressionResult(leftValue.asDouble() * rightValue.asDouble()); + case DIVIDE: + return isInteger ? new ExpressionResult(leftValue.asInt() / rightValue.asInt()) : + new ExpressionResult(leftValue.asDouble() / rightValue.asDouble()); + case MODULO: + return isInteger ? new ExpressionResult(leftValue.asInt() % rightValue.asInt()) : + new ExpressionResult(leftValue.asDouble() % rightValue.asDouble()); + default: + throw new EvaluationException("Unsupported operator: " + operator + " between " + + leftValue.asString() + " and " + rightValue.asString()); + } + } +} diff --git a/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ConditionalExpressionNode.java b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ConditionalExpressionNode.java new file mode 100644 index 0000000000..20b9a5f35c --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ConditionalExpressionNode.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.synapse.util.synapse_path.ast; + +import org.apache.synapse.util.synapse_path.context.EvaluationContext; +import org.apache.synapse.util.synapse_path.exception.EvaluationException; + +/** + * Represents a conditional expression node ( a ? b : c ) in the AST. + */ +public class ConditionalExpressionNode implements ExpressionNode { + + private final ExpressionNode condition; + private final ExpressionNode trueExpression; + private final ExpressionNode falseExpression; + + public ConditionalExpressionNode(ExpressionNode condition, ExpressionNode trueExpression, + ExpressionNode falseExpression) { + this.condition = condition; + this.trueExpression = trueExpression; + this.falseExpression = falseExpression; + } + + @Override + public ExpressionResult evaluate(EvaluationContext context) { + ExpressionResult conditionResult = condition.evaluate(context); + if (conditionResult == null || conditionResult.isNull()) { + throw new EvaluationException("Condition is null in conditional expression"); + } + if (conditionResult.isBoolean()) { + return conditionResult.asBoolean() ? trueExpression.evaluate(context) : falseExpression.evaluate(context); + } else { + throw new EvaluationException("Condition is not a boolean in conditional expression"); + } + } +} diff --git a/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ExpressionNode.java b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ExpressionNode.java new file mode 100644 index 0000000000..bd3847ef43 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ExpressionNode.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.synapse.util.synapse_path.ast; + +import org.apache.synapse.util.synapse_path.context.EvaluationContext; +import org.apache.synapse.util.synapse_path.exception.EvaluationException; + +/** + * Represents a node in the AST. + */ +public interface ExpressionNode { + ExpressionResult evaluate(EvaluationContext context) throws EvaluationException; +} diff --git a/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ExpressionResult.java b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ExpressionResult.java new file mode 100644 index 0000000000..2957dadd16 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ExpressionResult.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.synapse.util.synapse_path.ast; + +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonPrimitive; +import com.google.gson.internal.LazilyParsedNumber; +import org.apache.synapse.util.synapse_path.exception.EvaluationException; + +/** + * This class represents the result of an expression evaluation. + * It can hold values of different types such as String, Number, Boolean, JsonElement, and null. + */ +public class ExpressionResult { + private final Object value; + + public ExpressionResult() { + this.value = null; + } + + public ExpressionResult(String value) { + this.value = value; + } + + public ExpressionResult(Number value) { + this.value = value; + } + + public ExpressionResult(int value) { + this.value = value; + } + + public ExpressionResult(double value) { + this.value = value; + } + + public ExpressionResult(boolean value) { + this.value = value; + } + + public ExpressionResult(JsonElement value) { + this.value = value; + } + + public boolean isNull() { + return value == null || (value instanceof JsonElement && value.equals(JsonNull.INSTANCE)); + } + + // Method to get value as String + public String asString() { + if (value == null) { + return null; + } else if (value instanceof String) { + // if quoted, remove quotes + if (((String) value).startsWith("\"") && ((String) value).endsWith("\"")) { + return ((String) value).substring(1, ((String) value).length() - 1); + } else if (((String) value).startsWith("'") && ((String) value).endsWith("'")) { + return ((String) value).substring(1, ((String) value).length() - 1); + } + return (String) value; + } else if (value instanceof JsonPrimitive && ((JsonPrimitive) value).isString()) { + return ((JsonPrimitive) value).getAsString(); + } + return value.toString(); // Fallback to toString() for other types + } + + // Method to get value as int + public int asInt() { + if (value instanceof Number) { + return ((Number) value).intValue(); + } else if (value instanceof JsonPrimitive && ((JsonPrimitive) value).isNumber()) { + return ((JsonPrimitive) value).getAsInt(); + } + throw new EvaluationException("Value : " + value + " cannot be converted to int"); + } + + // Method to get value as double + public double asDouble() { + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } else if (value instanceof JsonPrimitive && ((JsonPrimitive) value).isNumber()) { + return ((JsonPrimitive) value).getAsDouble(); + } + throw new EvaluationException("Value : " + value + " cannot be converted to double"); + } + + // Method to get value as boolean + public boolean asBoolean() { + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof JsonPrimitive && ((JsonPrimitive) value).isBoolean()) { + return ((JsonPrimitive) value).getAsBoolean(); + } + throw new EvaluationException("Value : " + value + " cannot be converted to boolean"); + } + + // Method to get value as JsonElement + public JsonElement asJsonElement() { + if (value instanceof JsonElement) { + return (JsonElement) value; + } + throw new EvaluationException("Value is not a JsonElement"); + } + + // Method to check the actual type of the result + public Class getType() { + if (value == null) { + return null; + } + return value.getClass(); + } + + public boolean isNumeric() { + return isInteger() || isDouble(); + } + + public boolean isInteger() { + return value instanceof Integer || (value instanceof JsonPrimitive && isInteger((JsonPrimitive) value)); + } + + public boolean isDouble() { + return value instanceof Double || (value instanceof JsonPrimitive && isDouble((JsonPrimitive) value)); + } + + public boolean isBoolean() { + return value instanceof Boolean || (value instanceof JsonPrimitive && ((JsonPrimitive) value).isBoolean()); + } + + public boolean isString() { + return value instanceof String || (value instanceof JsonPrimitive && ((JsonPrimitive) value).isString()); + } + + public boolean isObject() { + return value instanceof JsonElement && ((JsonElement) value).isJsonObject(); + } + + public boolean isArray() { + return value instanceof JsonElement && ((JsonElement) value).isJsonArray(); + } + + public boolean isJsonPrimitive() { + return value instanceof JsonPrimitive; + } + + 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) { + // Check if the number is an instance of integer types (int, long, short) + String numberString = number.toString(); + try { + Integer.parseInt(numberString); + return true; + } catch (NumberFormatException e) { + return false; + } + } + return initialCheck; + } + return false; // Not a number, so it's not an integer + } + + 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 && number instanceof LazilyParsedNumber) { + // Check if the number is an instance of integer types (int, long, short) + String numberString = number.toString(); + try { + Double.parseDouble(numberString); + return true; + } catch (NumberFormatException e) { + return false; + } + } + } + return false; // Not a number, so it's not a double + } +} diff --git a/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/FilterExpressionNode.java b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/FilterExpressionNode.java new file mode 100644 index 0000000000..0c176d32e4 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/FilterExpressionNode.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.synapse.util.synapse_path.ast; + +import org.apache.synapse.util.synapse_path.context.EvaluationContext; +import org.apache.synapse.util.synapse_path.utils.ExpressionUtils; + +import java.util.Map; + +/** + * Represents a json-path filter expression node in the AST. + */ +public class FilterExpressionNode implements ExpressionNode { + + + private String expression; + private final Map arguments; + + public FilterExpressionNode(String expression, Map arguments) { + this.expression = expression; + this.arguments = arguments; + } + + /** + * return the formatted JSONPath filter expression. + * Not evaluating here. + */ + @Override + public ExpressionResult evaluate(EvaluationContext context) { + for (Map.Entry entry : arguments.entrySet()) { + if (entry.getValue() != null) { + ExpressionResult result = entry.getValue().evaluate(context); + if (result != null) { + String regex = ExpressionUtils.escapeSpecialCharacters(entry.getKey()); + String resultString = result.asString(); + if (result.isString()) { + resultString = "\"" + resultString + "\""; + } + expression = expression.replaceFirst(regex, resultString); + } + } + } + //TODO: Need to stop adding "?" for expressions like $..book[(@.length-1)].title. But not handling this for + // now since its not even working in json-path. + return new ExpressionResult("?" + expression); + } +} diff --git a/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/HeadersAndPropertiesAccessNode.java b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/HeadersAndPropertiesAccessNode.java new file mode 100644 index 0000000000..01e9a2dccd --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/HeadersAndPropertiesAccessNode.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.synapse.util.synapse_path.ast; + +import org.apache.synapse.util.synapse_path.context.EvaluationContext; +import org.apache.synapse.util.synapse_path.exception.EvaluationException; + +/** + * Represents a node in the abstract syntax tree that provides access to headers and properties. + */ +public class HeadersAndPropertiesAccessNode implements ExpressionNode { + private final String scope; + + public enum Type { + HEADER, + PROPERTY, + CONFIG + } + + private final Type type; + + // property key or header name + private final ExpressionNode key; + + public HeadersAndPropertiesAccessNode(ExpressionNode node, Type type) { + this.key = node; + this.type = type; + scope = null; + } + + public HeadersAndPropertiesAccessNode(ExpressionNode node, String scope) { + this.key = node; + this.scope = scope; + this.type = Type.PROPERTY; + } + + @Override + public ExpressionResult evaluate(EvaluationContext context) { + if (key != null) { + String name = key.evaluate(context).asString(); + Object value; + if (type.equals(Type.HEADER)) { + value = context.getHeader(name); + } else { + value = context.getProperty(name, scope); + } + return new ExpressionResult(value != null ? value.toString() : null); + } + throw new EvaluationException("Key cannot be null when accessing headers or properties"); + } +} diff --git a/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/LiteralNode.java b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/LiteralNode.java new file mode 100644 index 0000000000..bd7f54e677 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/LiteralNode.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.synapse.util.synapse_path.ast; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import org.apache.synapse.util.synapse_path.context.EvaluationContext; + +/** + * Represents a leaf node in the AST that holds a literal value. + */ +public class LiteralNode implements ExpressionNode { + private final String value; + private ArgumentListNode parameterList = new ArgumentListNode(); + + public enum Type { + NUMBER, + STRING, + BOOLEAN, + NULL, + ARRAY + } + + private final Type type; + + public LiteralNode(String value, Type type) { + this.value = value; + this.type = type; + } + + public LiteralNode(ArgumentListNode value, Type type) { + this.parameterList = value; + this.type = type; + this.value = ""; + } + + @Override + public ExpressionResult evaluate(EvaluationContext context) { + switch (type) { + case NUMBER: + return parseNumber(value); + case STRING: + return new ExpressionResult(value); + case BOOLEAN: + return new ExpressionResult(Boolean.parseBoolean(value)); + case NULL: + return null; + case ARRAY: + return parseArray(context); + default: + throw new IllegalArgumentException("Unsupported type: " + type); + } + } + + private ExpressionResult parseNumber(String value) { + try { + return new ExpressionResult(Integer.parseInt(value)); + } catch (NumberFormatException e1) { + try { + return new ExpressionResult(Float.parseFloat(value)); + } catch (NumberFormatException e2) { + try { + return new ExpressionResult(Double.parseDouble(value)); + } catch (NumberFormatException e3) { + throw new IllegalArgumentException("Value " + value + " is not a number"); + } + } + } + } + + private ExpressionResult parseArray(EvaluationContext context) { + JsonArray jsonArray = new JsonArray(); + for (ExpressionNode expressionNode : parameterList.getArguments()) { + ExpressionResult result = expressionNode.evaluate(context); + if (result.getType().equals(JsonElement.class)) { + jsonArray.add(result.asJsonElement()); + } else if (result.isInteger()) { + jsonArray.add(result.asInt()); + } else if (result.isDouble()) { + jsonArray.add(result.asDouble()); + } else { + jsonArray.add(result.asString()); + } + } + return new ExpressionResult(jsonArray); + } +} diff --git a/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/PayloadAccessNode.java b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/PayloadAccessNode.java new file mode 100644 index 0000000000..196f546cb2 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/PayloadAccessNode.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.synapse.util.synapse_path.ast; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Option; +import com.jayway.jsonpath.PathNotFoundException; +import com.jayway.jsonpath.spi.json.GsonJsonProvider; +import com.jayway.jsonpath.spi.json.JsonProvider; +import com.jayway.jsonpath.spi.mapper.GsonMappingProvider; +import com.jayway.jsonpath.spi.mapper.MappingProvider; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.synapse.SynapseConstants; +import org.apache.synapse.util.synapse_path.context.EvaluationContext; +import org.apache.synapse.util.synapse_path.exception.EvaluationException; +import org.apache.synapse.util.synapse_path.utils.ExpressionUtils; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Represents a node in the AST that accesses a value in the payload or variable. + * Resolve placeholders in the expression and evaluate the expression using JSONPath. + */ +public class PayloadAccessNode implements ExpressionNode { + private static final Log log = LogFactory.getLog(PayloadAccessNode.class); + private String expression; + private final Map arguments; + + private final boolean isVariable; + + public PayloadAccessNode(String expression, Map arguments, boolean isVariable) { + this.expression = expression; + this.arguments = arguments; + this.isVariable = isVariable; + 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