diff --git a/modules/core/src/main/java/org/apache/synapse/MessageContext.java b/modules/core/src/main/java/org/apache/synapse/MessageContext.java index 9b9efb16e0..e44795c222 100644 --- a/modules/core/src/main/java/org/apache/synapse/MessageContext.java +++ b/modules/core/src/main/java/org/apache/synapse/MessageContext.java @@ -457,4 +457,27 @@ public interface MessageContext { * @param tracingState Set whether the message flowtracing is enabled or not */ public void setMessageFlowTracingState(int tracingState); + + /** + * Get the value of a variable on the message instance + * + * @param key key to look up variable + * @return value for the given key + */ + public Object getVariable(String key); + + /** + * Set a variable with the given name on the message instance + * + * @param key key to be used + * @param value value to be saved + */ + public void setVariable(String key, Object value); + + /** + * Returns the Set of keys over the variables on this message context + * + * @return a Set of keys over message variables + */ + public Set getVariableKeySet(); } diff --git a/modules/core/src/main/java/org/apache/synapse/config/xml/AbstractMediatorFactory.java b/modules/core/src/main/java/org/apache/synapse/config/xml/AbstractMediatorFactory.java index 03d5e388c9..aac6c3112e 100644 --- a/modules/core/src/main/java/org/apache/synapse/config/xml/AbstractMediatorFactory.java +++ b/modules/core/src/main/java/org/apache/synapse/config/xml/AbstractMediatorFactory.java @@ -65,6 +65,8 @@ public abstract class AbstractMediatorFactory implements MediatorFactory { = new QName(XMLConfigConstants.STATISTICS_ATTRIB_NAME); protected static final QName PROP_Q = new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "property"); + protected static final QName VARIABLE_Q + = new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "variable"); protected static final QName PROPERTY_GROUP_Q = new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "propertyGroup"); protected static final QName FEATURE_Q diff --git a/modules/core/src/main/java/org/apache/synapse/config/xml/MediatorFactoryFinder.java b/modules/core/src/main/java/org/apache/synapse/config/xml/MediatorFactoryFinder.java index 30e2d902c8..49b3a3ff27 100644 --- a/modules/core/src/main/java/org/apache/synapse/config/xml/MediatorFactoryFinder.java +++ b/modules/core/src/main/java/org/apache/synapse/config/xml/MediatorFactoryFinder.java @@ -102,7 +102,8 @@ public class MediatorFactoryFinder implements XMLToObjectMapper { CommentMediatorFactory.class, ForEachMediatorFactory.class, JSONTransformMediatorFactory.class, - NTLMMediatorFactory.class + NTLMMediatorFactory.class, + VariableMediatorFactory.class }; private final static MediatorFactoryFinder instance = new MediatorFactoryFinder(); diff --git a/modules/core/src/main/java/org/apache/synapse/config/xml/MediatorSerializerFinder.java b/modules/core/src/main/java/org/apache/synapse/config/xml/MediatorSerializerFinder.java index 95053ff14d..174d90511c 100644 --- a/modules/core/src/main/java/org/apache/synapse/config/xml/MediatorSerializerFinder.java +++ b/modules/core/src/main/java/org/apache/synapse/config/xml/MediatorSerializerFinder.java @@ -76,7 +76,8 @@ public class MediatorSerializerFinder { CommentMediatorSerializer.class, ForEachMediatorSerializer.class, JSONTransformMediatorSerializer.class, - NTLMMediatorSerializer.class + NTLMMediatorSerializer.class, + VariableMediatorSerializer.class }; private final static MediatorSerializerFinder instance = new MediatorSerializerFinder(); diff --git a/modules/core/src/main/java/org/apache/synapse/config/xml/VariableMediatorFactory.java b/modules/core/src/main/java/org/apache/synapse/config/xml/VariableMediatorFactory.java new file mode 100644 index 0000000000..4114d4d3b3 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/config/xml/VariableMediatorFactory.java @@ -0,0 +1,95 @@ +/* + * 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.config.xml; + +import org.apache.axiom.om.OMAttribute; +import org.apache.axiom.om.OMElement; +import org.apache.synapse.Mediator; +import org.apache.synapse.SynapseException; +import org.apache.synapse.mediators.v2.VariableMediator; +import org.jaxen.JaxenException; + +import java.util.Properties; +import javax.xml.namespace.QName; + +/** + * Creates a variable mediator through the supplied XML configuration + *

+ *

+ * <variable name="string" [action=set/remove] (value="literal" | expression="expression") type="string|integer|JSON"/>
+ * 
+ */ +public class VariableMediatorFactory extends AbstractMediatorFactory { + + private static final QName ATT_ACTION = new QName("action"); + private static final QName ATT_TYPE = new QName("type"); + + public Mediator createSpecificMediator(OMElement elem, Properties properties) { + + VariableMediator variableMediator = new VariableMediator(); + OMAttribute name = elem.getAttribute(ATT_NAME); + OMAttribute value = elem.getAttribute(ATT_VALUE); + OMAttribute expression = elem.getAttribute(ATT_EXPRN); + OMAttribute action = elem.getAttribute(ATT_ACTION); + OMAttribute type = elem.getAttribute(ATT_TYPE); + + if (name == null || name.getAttributeValue().isEmpty()) { + String msg = "The 'name' attribute is required for the configuration of a variable mediator"; + log.error(msg); + throw new SynapseException(msg); + } else if ((value == null && expression == null) && + !(action != null && "remove".equals(action.getAttributeValue()))) { + String msg = "'value' or 'expression' attributes is required for a variable mediator when action is SET"; + log.error(msg); + throw new SynapseException(msg); + } + variableMediator.setName(name.getAttributeValue()); + + String dataType = null; + if (type != null) { + dataType = type.getAttributeValue(); + } + + if (value != null) { + variableMediator.setValue(value.getAttributeValue(), dataType); + } else if (expression != null) { + try { + variableMediator.setExpression(SynapsePathFactory.getSynapsePath(elem, ATT_EXPRN), + dataType); + } catch (JaxenException e) { + String msg = "Invalid expression for attribute 'expression' : " + + expression.getAttributeValue(); + log.error(msg); + throw new SynapseException(msg); + } + } + + if (action != null && "remove".equals(action.getAttributeValue())) { + variableMediator.setAction(VariableMediator.ACTION_REMOVE); + } + processAuditStatus(variableMediator, elem); + addAllCommentChildrenToList(elem, variableMediator.getCommentsList()); + return variableMediator; + } + + public QName getTagQName() { + + return VARIABLE_Q; + } +} diff --git a/modules/core/src/main/java/org/apache/synapse/config/xml/VariableMediatorSerializer.java b/modules/core/src/main/java/org/apache/synapse/config/xml/VariableMediatorSerializer.java new file mode 100644 index 0000000000..2c9bc5ced3 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/config/xml/VariableMediatorSerializer.java @@ -0,0 +1,77 @@ +/* + * 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.config.xml; + +import org.apache.axiom.om.OMElement; +import org.apache.synapse.Mediator; +import org.apache.synapse.mediators.v2.VariableMediator; + +/** + *
+ * <variable name="string" [action=set/remove] (value="literal" | expression="expression") type="string|integer|JSON"/>
+ * 
+ */ +public class VariableMediatorSerializer extends AbstractMediatorSerializer { + + public OMElement serializeSpecificMediator(Mediator m) { + + if (!(m instanceof VariableMediator)) { + handleException("Unsupported mediator passed in for serialization : " + m.getType()); + } + + VariableMediator mediator = (VariableMediator) m; + OMElement variable = fac.createOMElement("variable", synNS); + saveTracingState(variable, mediator); + + if (mediator.getName() != null) { + variable.addAttribute(fac.createOMAttribute( + "name", nullNS, mediator.getName())); + } else { + handleException("Invalid variable mediator. Name is required"); + } + + if (mediator.getValue() != null) { + variable.addAttribute(fac.createOMAttribute( + "value", nullNS, mediator.getValue().toString())); + } else if (mediator.getExpression() != null) { + SynapsePathSerializer.serializePath((SynapsePath) mediator.getExpression(), + variable, "expression"); + } else if (mediator.getAction() == VariableMediator.ACTION_SET) { + handleException("Invalid variable mediator. Value or expression is required if " + + "action is SET"); + } + + if (mediator.getAction() == VariableMediator.ACTION_REMOVE) { + variable.addAttribute(fac.createOMAttribute( + "action", nullNS, "remove")); + } else if (mediator.getType() != null) { + variable.addAttribute(fac.createOMAttribute( + "type", nullNS, mediator.getType())); + } + + serializeComments(variable, mediator.getCommentsList()); + + return variable; + } + + public String getMediatorClassName() { + + return VariableMediator.class.getName(); + } +} diff --git a/modules/core/src/main/java/org/apache/synapse/core/axis2/Axis2MessageContext.java b/modules/core/src/main/java/org/apache/synapse/core/axis2/Axis2MessageContext.java index 5bc21ab140..311cd72027 100644 --- a/modules/core/src/main/java/org/apache/synapse/core/axis2/Axis2MessageContext.java +++ b/modules/core/src/main/java/org/apache/synapse/core/axis2/Axis2MessageContext.java @@ -68,6 +68,11 @@ public class Axis2MessageContext implements MessageContext { */ private final Map properties = new HashMap(); + /** + * Synapse Message Context variables + */ + private final Map variables = new HashMap<>(); + /** * Local entries fetched from the configuration or from the registry for the transactional * resource access @@ -725,4 +730,22 @@ public HashMap getAnalyticsMetadata() { //noinspection unchecked return (HashMap) getProperty(SynapseConstants.ANALYTICS_METADATA); } + + @Override + public Object getVariable(String key) { + return variables.get(key); + } + + @Override + public void setVariable(String key, Object value) { + if (value == null) { + return; + } + variables.put(key, value); + } + + @Override + public Set getVariableKeySet() { + return variables.keySet(); + } } diff --git a/modules/core/src/main/java/org/apache/synapse/mediators/v2/VariableMediator.java b/modules/core/src/main/java/org/apache/synapse/mediators/v2/VariableMediator.java new file mode 100644 index 0000000000..03f773bd58 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/mediators/v2/VariableMediator.java @@ -0,0 +1,288 @@ +/* + * 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.mediators.v2; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import org.apache.axiom.om.OMElement; +import org.apache.axis2.util.JavaUtils; +import org.apache.synapse.MessageContext; +import org.apache.synapse.SynapseException; +import org.apache.synapse.SynapseLog; +import org.apache.synapse.aspects.ComponentType; +import org.apache.synapse.aspects.flow.statistics.collectors.CloseEventCollector; +import org.apache.synapse.config.SynapseConfigUtils; +import org.apache.synapse.config.xml.SynapsePath; +import org.apache.synapse.config.xml.XMLConfigConstants; +import org.apache.synapse.mediators.AbstractMediator; + +import java.util.Set; + +/** + * The variable mediator save or remove a named variable in the Synapse Message Context. + */ +public class VariableMediator extends AbstractMediator { + + public static final int ACTION_SET = 0; + public static final int ACTION_REMOVE = 1; + private String name = null; + private SynapsePath expression = null; + private Object value = null; + private String type = null; + private int action = ACTION_SET; + + public boolean mediate(MessageContext synCtx) { + + if (synCtx.getEnvironment().isDebuggerEnabled()) { + if (super.divertMediationRoute(synCtx)) { + return true; + } + } + + SynapseLog synLog = getLog(synCtx); + + if (synLog.isTraceOrDebugEnabled()) { + synLog.traceOrDebug("Start : Variable mediator"); + + if (synLog.isTraceTraceEnabled()) { + synLog.traceTrace("Message : " + synCtx.getEnvelope()); + } + } + + String name = this.name; + if (action == ACTION_SET) { + + Object resultValue = getResultValue(synCtx); + + if (synLog.isTraceOrDebugEnabled()) { + synLog.traceOrDebug("Setting variable : " + name + " to : " + resultValue); + } + + if (resultValue instanceof OMElement) { + ((OMElement) resultValue).build(); + } + + synCtx.setVariable(name, resultValue); + + } else { + if (synLog.isTraceOrDebugEnabled()) { + synLog.traceOrDebug("Removing variable : " + name); + } + Set variableKeySet = synCtx.getVariableKeySet(); + if (variableKeySet != null) { + variableKeySet.remove(name); + } + } + synLog.traceOrDebug("End : Variable mediator"); + + return true; + } + + public String getName() { + + return name; + } + + public void setName(String name) { + + this.name = name; + } + + public Object getValue() { + + return value; + } + + public void setValue(String value) { + + setValue(value, null); + } + + /** + * Set the value to be set by this variable mediator and the data type to be used when setting the value. + * Accepted type names are defined in XMLConfigConstants.DATA_TYPES enumeration. Passing null as the type + * implies that 'STRING' type should be used. + * + * @param value the value to be set as a string + * @param type the type name + */ + public void setValue(String value, String type) { + + this.type = type; + this.value = convertValue(value, type, false); + } + + public String getType() { + + return type; + } + + public void reportCloseStatistics(MessageContext messageContext, Integer currentIndex) { + + CloseEventCollector + .closeEntryEvent(messageContext, getMediatorName(), ComponentType.MEDIATOR, currentIndex, + isContentAltering()); + } + + public int getAction() { + + return action; + } + + public void setAction(int action) { + + this.action = action; + } + + public SynapsePath getExpression() { + + return expression; + } + + public void setExpression(SynapsePath expression, String type) { + + this.expression = expression; + this.type = type; + } + + private Object getResultValue(MessageContext synCtx) { + + if (value != null) { + return value; + } else { + if (expression != null) { + return convertValue(expression.stringValueOf(synCtx), type, true); + } + } + + return null; + } + + private Object convertValue(String value, String type, boolean isExpression) { + + if (type == null) { + return value; + } + + try { + XMLConfigConstants.DATA_TYPES dataType = XMLConfigConstants.DATA_TYPES.valueOf(type); + switch (dataType) { + case BOOLEAN: + return JavaUtils.isTrueExplicitly(value); + case DOUBLE: + return Double.parseDouble(value); + case FLOAT: + return Float.parseFloat(value); + case INTEGER: + return parseInteger(value, isExpression); + case LONG: + return Long.parseLong(value); + case OM: + return buildOMElement(value); + case SHORT: + return parseShort(value, isExpression); + case JSON: + return buildJSONElement(value); + default: + return value; + } + } catch (IllegalArgumentException e) { + String msg = "Unknown type : " + type + " for the variable mediator or the " + + "variable value cannot be converted into the specified type."; + log.error(msg, e); + throw new SynapseException(msg, e); + } + } + + /** + * This method will explicitly convert decimals to int since XPAth functions return numbers with decimal. + * + * @param value String value returned from XPAth function + * @param isExpression Boolean to check whether the value is from XPAth function + * @return parsed Short value + */ + private int parseInteger(String value, boolean isExpression) { + + if (isExpression && value.contains(".")) { + return (int) Double.parseDouble(value); + } + return Integer.parseInt(value); + } + + /** + * This method will explicitly convert decimals to short since XPAth functions return numbers with decimal. + * + * @param value String value returned from XPAth function + * @param isExpression Boolean to check whether the value is from XPAth function + * @return parsed Short value + */ + private short parseShort(String value, boolean isExpression) { + + if (isExpression && value.contains(".")) { + return (short) Double.parseDouble(value); + } + return Short.parseShort(value); + } + + @Override + public boolean isContentAware() { + + boolean contentAware = false; + if (expression != null) { + contentAware = expression.isContentAware(); + } + return contentAware; + } + + private OMElement buildOMElement(String xml) { + + if (xml == null) { + return null; + } + OMElement result = SynapseConfigUtils.stringToOM(xml); + result.buildWithAttachments(); + return result; + } + + private JsonElement buildJSONElement(String jsonPayload) { + + JsonParser jsonParser = new JsonParser(); + try { + return jsonParser.parse(jsonPayload); + } catch (JsonSyntaxException ex) { + // Enclosing using quotes due to the following issue + // https://github.com/google/gson/issues/1286 + String enclosed = "\"" + jsonPayload + "\""; + try { + return jsonParser.parse(enclosed); + } catch (JsonSyntaxException e) { + // log the original exception and discard the new exception + log.error("Malformed JSON payload : " + jsonPayload, ex); + return null; + } + } + } + + @Override + public String getMediatorName() { + + return super.getMediatorName() + ":" + name; + } +} diff --git a/modules/core/src/test/java/org/apache/synapse/TestMessageContext.java b/modules/core/src/test/java/org/apache/synapse/TestMessageContext.java index 727a062bf2..3f2b944d6b 100644 --- a/modules/core/src/test/java/org/apache/synapse/TestMessageContext.java +++ b/modules/core/src/test/java/org/apache/synapse/TestMessageContext.java @@ -43,6 +43,8 @@ public class TestMessageContext implements MessageContext { private Map properties = new HashMap(); + private Map variables = new HashMap(); + private Map localEntries = new HashMap(); private Stack faultStack = new Stack(); @@ -386,4 +388,22 @@ public void setMessageFlowTracingState(int tracingState){ public int getMessageFlowTracingState(){ return SynapseConstants.TRACING_OFF; } + + @Override + public Object getVariable(String key) { + + return variables.get(key); + } + + @Override + public void setVariable(String key, Object value) { + + variables.put(key, value); + } + + @Override + public Set getVariableKeySet() { + + return variables.keySet(); + } } diff --git a/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/CommonScriptMessageContext.java b/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/CommonScriptMessageContext.java index 8853c27d7c..20df76bb26 100644 --- a/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/CommonScriptMessageContext.java +++ b/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/CommonScriptMessageContext.java @@ -1090,4 +1090,34 @@ public void setMessageFlowTracingState(int state) { public int getMessageFlowTracingState() { return SynapseConstants.TRACING_OFF; } + + @Override + public Object getVariable(String key) { + + return mc.getVariable(key); + } + + @Override + public void setVariable(String key, Object value) { + + if (value instanceof XMLObject) { + OMElement omElement = null; + try { + omElement = xmlHelper.toOMElement(value); + } catch (ScriptException e) { + mc.setVariable(key, value); + } + if (omElement != null) { + mc.setVariable(key, omElement); + } + } else { + mc.setVariable(key, value); + } + } + + @Override + public Set getVariableKeySet() { + + return mc.getVariableKeySet(); + } } diff --git a/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/GraalVMJavaScriptMessageContext.java b/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/GraalVMJavaScriptMessageContext.java index 92b3e57d49..d7b6a0aa15 100644 --- a/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/GraalVMJavaScriptMessageContext.java +++ b/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/GraalVMJavaScriptMessageContext.java @@ -920,4 +920,28 @@ public void setMessageFlowTracingState(int state) { mc.setMessageFlowTracingState(state); } + @Override + public Object getVariable(String key) { + + return mc.getVariable(key); + } + + @Override + public void setVariable(String key, Object value) { + + try { + OMElement omElement = xmlHelper.toOMElement(value); + mc.setVariable(key, omElement); + } catch (ScriptException e) { + // Try to convert the value into OMElement if it fails it means value is not a representation of xml so + // set as key value pair + mc.setVariable(key, value); + } + } + + @Override + public Set getVariableKeySet() { + + return mc.getVariableKeySet(); + } } diff --git a/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/NashornJavaScriptMessageContext.java b/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/NashornJavaScriptMessageContext.java index 7141bc3437..9f7029545f 100644 --- a/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/NashornJavaScriptMessageContext.java +++ b/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/NashornJavaScriptMessageContext.java @@ -915,5 +915,30 @@ public void setMessageFlowTracingState(int state) { public int getMessageFlowTracingState() { return SynapseConstants.TRACING_OFF; } + + @Override + public Object getVariable(String key) { + + return mc.getVariable(key); + } + + @Override + public void setVariable(String key, Object value) { + + try { + OMElement omElement = xmlHelper.toOMElement(value); + mc.setVariable(key, omElement); + } catch (ScriptException e) { + // Try to convert the value into OMElement if it fails it means value is not a representation of xml so + // set as key value pair + mc.setVariable(key, value); + } + } + + @Override + public Set getVariableKeySet() { + + return mc.getVariableKeySet(); + } } diff --git a/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/OpenJDKNashornJavaScriptMessageContext.java b/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/OpenJDKNashornJavaScriptMessageContext.java index a63a36bf77..2c1392fcd6 100644 --- a/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/OpenJDKNashornJavaScriptMessageContext.java +++ b/modules/extensions/src/main/java/org/apache/synapse/mediators/bsf/OpenJDKNashornJavaScriptMessageContext.java @@ -902,4 +902,29 @@ public int getMessageFlowTracingState() { public void setMessageFlowTracingState(int state) { mc.setMessageFlowTracingState(state); } + + @Override + public Object getVariable(String key) { + + return mc.getVariable(key); + } + + @Override + public void setVariable(String key, Object value) { + + try { + OMElement omElement = xmlHelper.toOMElement(value); + mc.setVariable(key, omElement); + } catch (ScriptException e) { + // Try to convert the value into OMElement if it fails it means value is not a representation of xml so + // set as key value pair + mc.setVariable(key, value); + } + } + + @Override + public Set getVariableKeySet() { + + return mc.getVariableKeySet(); + } }