diff --git a/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/Constants.java b/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/Constants.java index d9ace07b93..d359c82352 100644 --- a/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/Constants.java +++ b/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/Constants.java @@ -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"; diff --git a/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/FreeMarkerTemplateProcessor.java b/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/FreeMarkerTemplateProcessor.java index cdf99473d9..ce98a442dd 100644 --- a/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/FreeMarkerTemplateProcessor.java +++ b/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/FreeMarkerTemplateProcessor.java @@ -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; @@ -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; @@ -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); } /** @@ -400,6 +403,7 @@ private void injectProperties(MessageContext synCtx, Map data) { injectCtxProperties(synCtx, data); injectAxis2Properties(synCtx, data); injectTransportProperties(synCtx, data); + injectVariables(synCtx, data); } private void injectCtxProperties(MessageContext synCtx, Map data) { @@ -420,6 +424,21 @@ private void injectCtxProperties(MessageContext synCtx, Map data } } + private void injectVariables(MessageContext synCtx, Map data) { + + if (usingVariables) { + Map 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 data) { if (usingPropertyAxis2) { diff --git a/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/RegexTemplateProcessor.java b/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/RegexTemplateProcessor.java index 1a6ba8958c..fb2924c915 100644 --- a/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/RegexTemplateProcessor.java +++ b/modules/core/src/main/java/org/apache/synapse/mediators/transform/pfutils/RegexTemplateProcessor.java @@ -19,15 +19,29 @@ 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 @@ -35,7 +49,10 @@ 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) { @@ -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 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 = "" + replacementValue + ""; + 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 getReplacementValue(HashMap[] argValues, String matchSeq) { HashMap 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)); }