Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add Inline synapse expression support for Payload Factory mediator #2236

Merged
merged 3 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ private Constants() {
public static final String CTX_PROPERTY_INJECTING_NAME = "ctx";
public static final String AXIS2_PROPERTY_INJECTING_NAME = "axis2";
public static final String TRANSPORT_PROPERTY_INJECTING_NAME = "trp";
public static final String VARIABLE_INJECTING_NAME = "var";
public static final String JSON_TYPE = "json";
public static final String XML_TYPE = "xml";
public static final String TEXT_TYPE = "text";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import static org.apache.synapse.mediators.transform.pfutils.Constants.PAYLOAD_INJECTING_NAME;
import static org.apache.synapse.mediators.transform.pfutils.Constants.TEXT_PAYLOAD_TYPE;
import static org.apache.synapse.mediators.transform.pfutils.Constants.TRANSPORT_PROPERTY_INJECTING_NAME;
import static org.apache.synapse.mediators.transform.pfutils.Constants.VARIABLE_INJECTING_NAME;
import static org.apache.synapse.mediators.transform.pfutils.Constants.XML_PAYLOAD_TYPE;
import static org.apache.synapse.util.PayloadHelper.TEXTELT;
import static org.apache.synapse.util.PayloadHelper.getXMLPayload;
Expand All @@ -88,6 +89,7 @@ public class FreeMarkerTemplateProcessor extends TemplateProcessor {
private boolean usingPropertyAxis2;
private boolean usingPropertyTransport;
private boolean usingArgs;
private boolean usingVariables;

private static final Log log = LogFactory.getLog(FreeMarkerTemplateProcessor.class);
private boolean templateLoaded = false;
Expand Down Expand Up @@ -218,6 +220,7 @@ private void findRequiredInjections(String templateString) {
usingPropertyCtx = templateString.contains(CTX_PROPERTY_INJECTING_NAME);
usingPropertyAxis2 = templateString.contains(AXIS2_PROPERTY_INJECTING_NAME);
usingPropertyTransport = templateString.contains(TRANSPORT_PROPERTY_INJECTING_NAME);
usingVariables = templateString.contains(VARIABLE_INJECTING_NAME);
}

/**
Expand Down Expand Up @@ -400,6 +403,7 @@ private void injectProperties(MessageContext synCtx, Map<String, Object> data) {
injectCtxProperties(synCtx, data);
injectAxis2Properties(synCtx, data);
injectTransportProperties(synCtx, data);
injectVariables(synCtx, data);
}

private void injectCtxProperties(MessageContext synCtx, Map<String, Object> data) {
Expand All @@ -420,6 +424,21 @@ private void injectCtxProperties(MessageContext synCtx, Map<String, Object> data
}
}

private void injectVariables(MessageContext synCtx, Map<String, Object> data) {

if (usingVariables) {
Map<String, String> variables = new HashMap<>();
for (Object o : synCtx.getVariableKeySet()) {
String varName = (String) o;
Object variable = synCtx.getVariable(varName);
if (variable != null) {
variables.put(varName, variable.toString());
}
}
data.put(VARIABLE_INJECTING_NAME, variables);
}
}

private void injectAxis2Properties(MessageContext synCtx, Map<String, Object> data) {

if (usingPropertyAxis2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,40 @@

package org.apache.synapse.mediators.transform.pfutils;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMException;
import org.apache.axis2.AxisFault;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.commons.json.JsonUtil;
import org.apache.synapse.mediators.transform.ArgumentDetails;
import org.apache.synapse.util.xpath.SynapseExpression;
import org.jaxen.JaxenException;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.stream.XMLStreamException;

/**
* TemplateProcessor implementation for Regex based templates
*/
public class RegexTemplateProcessor extends TemplateProcessor {

private static final Log log = LogFactory.getLog(RegexTemplateProcessor.class);
private final Pattern pattern = Pattern.compile("\\$(\\d)+");
// Pattern matches "${...}" (quoted), ${...} (unquoted), and $n
private final Pattern pattern = Pattern.compile("\"\\$\\{(.+?)\\}\"|\\$\\{(.+?)\\}|\\$(\\d+)");

private final Gson gson = new Gson();

@Override
public String processTemplate(String template, String mediaType, MessageContext synCtx) {
Expand Down Expand Up @@ -72,25 +89,157 @@ private void replace(String format, StringBuffer result, String mediaType, Messa
}
try {
while (matcher.find()) {
String matchSeq = matcher.group();
replacement = getReplacementValue(argValues, matchSeq);
replacementEntry = replacement.entrySet().iterator().next();
replacementValue = prepareReplacementValue(mediaType, synCtx, replacementEntry);
matcher.appendReplacement(result, replacementValue);
if (matcher.group(1) != null) {
// Handle "${...}" pattern (with quotes)
String expression = matcher.group(1);
Object expressionResult = evaluateExpression(expression, synCtx);
if (expressionResult instanceof JsonPrimitive) {
replacementValue = prepareJSONPrimitiveReplacementValue(expressionResult, mediaType);
} else if (expressionResult instanceof JsonElement) {
// Escape JSON object and Arrays since we need to consider it as
replacementValue = escapeJson(Matcher.quoteReplacement(gson.toJson(expressionResult)));
if (XML_TYPE.equals(mediaType)) {
replacementValue = convertJsonToXML(replacementValue);
}
} else {
replacementValue = expressionResult.toString();
if (XML_TYPE.equals(mediaType)) {
replacementValue = StringEscapeUtils.escapeXml10(replacementValue);
} else if (JSON_TYPE.equals(mediaType)) {
if (isXML(replacementValue)) {
// consider the replacement value as a literal XML
replacementValue = Matcher.quoteReplacement(replacementValue);
} else {
replacementValue = escapeSpecialCharactersOfJson(replacementValue);
}
}
}
matcher.appendReplacement(result, "\"" + replacementValue + "\"");
} else if (matcher.group(2) != null) {
// Handle ${...} pattern (without quotes)
String expression = matcher.group(2);
Object expressionResult = evaluateExpression(expression, synCtx);
replacementValue = expressionResult.toString();
if (expressionResult instanceof JsonPrimitive) {
replacementValue = prepareJSONPrimitiveReplacementValue(expressionResult, mediaType);
} else if (expressionResult instanceof JsonElement) {
if (XML_TYPE.equals(mediaType)) {
replacementValue = convertJsonToXML(replacementValue);
replacementValue = Matcher.quoteReplacement(replacementValue);
} else {
replacementValue = Matcher.quoteReplacement(gson.toJson(expressionResult));
}
} else {
if (JSON_TYPE.equals(mediaType) && isXML(replacementValue)) {
replacementValue = convertXMLToJSON(replacementValue);
} else {
if (XML_TYPE.equals(mediaType) && !isXML(replacementValue)) {
replacementValue = StringEscapeUtils.escapeXml10(replacementValue);
}
replacementValue = Matcher.quoteReplacement(replacementValue);
}
}
matcher.appendReplacement(result, replacementValue);
} else if (matcher.group(3) != null) {
// Handle $n pattern
String matchSeq = matcher.group(3);
replacement = getReplacementValue(argValues, matchSeq);
replacementEntry = replacement.entrySet().iterator().next();
replacementValue = prepareReplacementValue(mediaType, synCtx, replacementEntry);
matcher.appendReplacement(result, replacementValue);
}
}
} catch (ArrayIndexOutOfBoundsException e) {
log.error("#replace. Mis-match detected between number of formatters and arguments", e);
} catch (JaxenException e) {
throw new SynapseException("Error evaluating expression" , e);
}
matcher.appendTail(result);
}

private String prepareJSONPrimitiveReplacementValue(Object expressionResult, String mediaType) {

String replacementValue = ((JsonPrimitive) expressionResult).getAsString();
replacementValue = escapeSpecialChars(Matcher.quoteReplacement(replacementValue));
if (XML_TYPE.equals(mediaType)) {
replacementValue = StringEscapeUtils.escapeXml10(replacementValue);
}
return replacementValue;
}

/**
* Evaluates the expression and returns the result as a string or an object.
* If the expression contains "xpath(", we meed to evaluate it as a string.
*
* @param expression expression to evaluate
* @param synCtx message context
* @return evaluated result
* @throws JaxenException if an error occurs while evaluating the expression
*/
private Object evaluateExpression(String expression, MessageContext synCtx) throws JaxenException {

if (expression.contains("xpath(")) {
return new SynapseExpression(expression).stringValueOf(synCtx);
}
return new SynapseExpression(expression).objectValueOf(synCtx);
}

private String escapeJson(String value) {
// Manual escape for JSON: escaping double quotes and backslashes
return value.replace("\"", "\\\"").replace("\\", "\\\\");
}

private String convertJsonToXML(String replacementValue) {

try {
// replacementValue = Matcher.quoteReplacement(replacementValue);
OMElement omXML = JsonUtil.toXml(IOUtils.toInputStream(replacementValue), false);
if (JsonUtil.isAJsonPayloadElement(omXML)) { // remove <jsonObject/> from result.
Iterator children = omXML.getChildElements();
String childrenStr = "";
while (children.hasNext()) {
childrenStr += (children.next()).toString().trim();
}
replacementValue = childrenStr;
} else {
replacementValue = omXML.toString();
}
} catch (AxisFault e) {
handleException(
"Error converting JSON to XML, please check your expressions return valid JSON: ");
}
return escapeSpecialCharactersOfXml(replacementValue);
}

private String convertXMLToJSON(String replacementValue) {

try {
replacementValue = "<jsonObject>" + replacementValue + "</jsonObject>";
OMElement omXML = convertStringToOM(replacementValue);
replacementValue = JsonUtil.toJsonString(omXML).toString();
replacementValue = escapeSpecialCharactersOfJson(replacementValue);
} catch (XMLStreamException e) {
handleException(
"Error parsing XML for JSON conversion, please check your expressions return valid XML: ");
} catch (AxisFault e) {
handleException("Error converting XML to JSON");
} catch (OMException e) {
// if the logic comes to this means, it was tried as a XML, which means it has
// "<" as starting element and ">" as end element, so basically if the logic comes here, that means
// value is a string value, that means No conversion required, as path evaluates to regular String.
replacementValue = escapeSpecialChars(replacementValue);

}
return replacementValue;
}

private HashMap<String, ArgumentDetails> getReplacementValue(HashMap<String, ArgumentDetails>[] argValues,
String matchSeq) {

HashMap<String, ArgumentDetails> replacement;
int argIndex;
try {
argIndex = Integer.parseInt(matchSeq.substring(1));
argIndex = Integer.parseInt(matchSeq);
} catch (NumberFormatException e) {
argIndex = Integer.parseInt(matchSeq.substring(2, matchSeq.length() - 1));
}
Expand Down
Loading