diff --git a/.gitignore b/.gitignore
index 96689a4935..ec32ace4b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,5 @@
target/
## various other file types we don't want to have in the repository:
-.DS_Store
\ No newline at end of file
+.DS_Store
+/modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/gen/
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
+
+
+
+
+
+
+
+
+
@@ -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..c0ec2e0265
--- /dev/null
+++ b/modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/ExpressionLexer.g4
@@ -0,0 +1,118 @@
+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' | 'attr';
+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';
+OBJECT: 'object';
+ARRAY: 'array';
+XPATH: 'xpath';
+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';
+EXISTS: 'exists';
+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
+GETPROPERTY: 'getProperty';
+ID: [a-zA-Z_][a-zA-Z_0-9]*;
+// 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..dbeb77dd32
--- /dev/null
+++ b/modules/core/src/main/antlr4/org/apache/synapse/util/synapse_path/ExpressionParser.g4
@@ -0,0 +1,193 @@
+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)? )
+ ;
+
+registryAccess
+ : (DOUBLE_DOT ASTERISK
+ | DOUBLE_DOT ID
+ | DOT ID
+ | LBRACKET arrayIndex RBRACKET
+ | DOT LBRACKET arrayIndex RBRACKET
+ | DOT ASTERISK)*
+ ;
+
+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) | registryAccess )?
+ | 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
+ | EXISTS LPAREN expression RPAREN
+ | OBJECT LPAREN expression RPAREN
+ | ARRAY LPAREN expression RPAREN
+ | XPATH 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..f7758d524e 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,55 @@ 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 OBJECT = "object";
+ public static final String ARRAY = "array";
+ public static final String REGISTRY = "registry";
+ public static final String EXISTS = "exists";
+ public static final String XPATH = "xpath";
+ public static final String SECRET = "secret";
+
+ 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 String VAULT_LOOKUP = "wso2:vault-lookup('";
}
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..952c8abfde 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.Synapse_Path;
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 Synapse_Path(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..c66d4eb27f 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;
}
}
@@ -103,6 +106,13 @@ public String toString() {
public abstract String stringValueOf(MessageContext synCtx);
+ /**
+ * New method to get the object value of the expression in places where we can handle the Object result.
+ * @param synCtx MessageContext
+ * @return Object - can be String, Integer, Double, Boolean, OMNode, JSONElement or null.
+ */
+ public abstract Object objectValueOf(MessageContext synCtx);
+
public void handleException(String msg, Throwable e) {
log.error(msg, e);
throw new SynapseException(msg, e);
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..eccdf91bf1 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.Synapse_Path;
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 Synapse_Path(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/mediators/Value.java b/modules/core/src/main/java/org/apache/synapse/mediators/Value.java
index 7fd27e4396..9c8d9002b3 100644
--- a/modules/core/src/main/java/org/apache/synapse/mediators/Value.java
+++ b/modules/core/src/main/java/org/apache/synapse/mediators/Value.java
@@ -23,10 +23,12 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.MessageContext;
+import org.apache.synapse.SynapseConstants;
import org.apache.synapse.SynapseException;
import org.apache.synapse.config.xml.SynapsePath;
import org.apache.synapse.util.xpath.SynapseJsonPath;
import org.apache.synapse.util.xpath.SynapseXPath;
+import org.apache.synapse.util.xpath.Synapse_Path;
import org.jaxen.JaxenException;
import java.util.ArrayList;
@@ -101,6 +103,10 @@ public SynapsePath getExpression() {
new SynapseJsonPath(expressionString.substring(10, expressionString.length() - 1));
expression = expressionTypeKey;
+ } else if (expressionString.startsWith(SynapseConstants.SIEL_IDENTIFIER_START) &&
+ expressionString.endsWith(SynapseConstants.SIEL_IDENTIFIER_END)) {
+ expression = new Synapse_Path(
+ expressionString.substring(2, expressionString.length() - 1));
} else {
SynapseXPath expressionTypeKey = new SynapseXPath(expressionString);
for (OMNamespace aNamespaceList : namespaceList) {
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..7b55156f56
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/ExpressionResult.java
@@ -0,0 +1,287 @@
+/*
+ * 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 com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.internal.LazilyParsedNumber;
+import org.apache.axiom.om.OMElement;
+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(OMElement value) {
+ this.value = value;
+ }
+
+ public Object getValue() {
+ return 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");
+ }
+
+ public JsonObject asJsonObject() {
+ if (value instanceof JsonObject) {
+ return (JsonObject) value;
+ } else if (value instanceof String) {
+ return parseStringToJsonObject((String) value);
+ }
+ throw new EvaluationException("Value is not a JsonObject");
+ }
+
+ public JsonArray asJsonArray() {
+ if (value instanceof JsonArray) {
+ return (JsonArray) value;
+ } else if (value instanceof String) {
+ return parseStringToJsonArray((String) value);
+ }
+ throw new EvaluationException("Value is not a JsonArray");
+ }
+
+ // 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() {
+ if (value instanceof JsonElement && ((JsonElement) value).isJsonObject()) {
+ return true;
+ } else if (value instanceof String) {
+ try {
+ parseStringToJsonObject((String) value);
+ return true;
+ } catch (EvaluationException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ public boolean isArray() {
+ if (value instanceof JsonElement && ((JsonElement) value).isJsonArray()) {
+ return true;
+ } else if (value instanceof String) {
+ try {
+ parseStringToJsonArray((String) value);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ 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
+ }
+
+ public boolean isOMElement() {
+ return value instanceof OMElement;
+ }
+
+ private JsonObject parseStringToJsonObject(String value) throws EvaluationException {
+ String stringValue = value;
+ if (stringValue.startsWith("\"") && stringValue.endsWith("\"")) {
+ stringValue = stringValue.substring(1, stringValue.length() - 1);
+ }
+ try {
+ JsonElement jsonElement = JsonParser.parseString(stringValue.replace("\\", ""));
+ if (jsonElement.isJsonObject()) {
+ return jsonElement.getAsJsonObject();
+ }
+ } catch (JsonSyntaxException e) {
+ throw new EvaluationException("Value is not a JsonObject");
+ }
+ throw new EvaluationException("Value is not a JsonObject");
+ }
+
+ private JsonArray parseStringToJsonArray(String value) throws EvaluationException {
+ String stringValue = value;
+ if (stringValue.startsWith("\"") && stringValue.endsWith("\"")) {
+ stringValue = stringValue.substring(1, stringValue.length() - 1);
+ }
+ try {
+ JsonElement jsonElement = JsonParser.parseString(stringValue.replace("\\", ""));
+ if (jsonElement.isJsonArray()) {
+ return jsonElement.getAsJsonArray();
+ }
+ } catch (JsonSyntaxException e) {
+ throw new EvaluationException("Value is not a JsonArray");
+ }
+ throw new EvaluationException("Value is not a JsonArray");
+ }
+}
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..8311d2c64d
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/synapse/util/synapse_path/ast/PayloadAccessNode.java
@@ -0,0 +1,192 @@
+/*
+ * 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;
+ public enum Type {
+ PAYLOAD,
+ VARIABLE,
+ REGISTRY,
+ }
+ private final Type type;
+ private ExpressionNode registryAccessNode = null;
+
+ public PayloadAccessNode(String expression, Map arguments, Type type,
+ ExpressionNode registryAccessNode) {
+ this.expression = expression;
+ this.arguments = arguments;
+ this.type = type;
+ this.registryAccessNode = registryAccessNode;
+ 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