diff --git a/kogito-serverless-workflow/kogito-jq-expression/src/main/java/org/kie/kogito/expr/jq/JqExpression.java b/kogito-serverless-workflow/kogito-jq-expression/src/main/java/org/kie/kogito/expr/jq/JqExpression.java index 9d8c1676658..8039176cfe9 100644 --- a/kogito-serverless-workflow/kogito-jq-expression/src/main/java/org/kie/kogito/expr/jq/JqExpression.java +++ b/kogito-serverless-workflow/kogito-jq-expression/src/main/java/org/kie/kogito/expr/jq/JqExpression.java @@ -34,7 +34,7 @@ import org.kie.kogito.jackson.utils.PrefixJsonNode; import org.kie.kogito.process.expr.Expression; import org.kie.kogito.serverless.workflow.utils.ExpressionHandlerUtils; -import org.kie.kogito.serverless.workflow.utils.JsonNodeContext; +import org.kie.kogito.serverless.workflow.utils.VariablesHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -207,6 +207,7 @@ private Scope getScope(KogitoProcessContext processInfo) { childScope.setValue(ExpressionHandlerUtils.SECRET_MAGIC, new PrefixJsonNode<>(ExpressionHandlerUtils::getOptionalSecret)); childScope.setValue(ExpressionHandlerUtils.CONTEXT_MAGIC, new FunctionJsonNode(ExpressionHandlerUtils.getContextFunction(processInfo))); childScope.setValue(ExpressionHandlerUtils.CONST_MAGIC, ExpressionHandlerUtils.getConstants(processInfo)); + VariablesHelper.getAdditionalVariables(processInfo).forEach(childScope::setValue); return childScope; } @@ -215,8 +216,8 @@ private T eval(JsonNode context, Class returnClass, KogitoProcessContext throw new IllegalArgumentException("Unable to evaluate content " + context + " using expr " + expr, validationError); } TypedOutput output = output(returnClass); - try (JsonNodeContext jsonNode = JsonNodeContext.from(context, processInfo)) { - internalExpr.apply(getScope(processInfo), jsonNode.getNode(), output); + try { + internalExpr.apply(getScope(processInfo), context, output); return JsonObjectUtils.convertValue(output.getResult(), returnClass); } catch (JsonQueryException e) { throw new IllegalArgumentException("Unable to evaluate content " + context + " using expr " + expr, e); diff --git a/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java b/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java index 57952675962..5c6ed26968f 100644 --- a/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java +++ b/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java @@ -18,15 +18,17 @@ */ package org.kie.kogito.expr.jsonpath; +import java.util.Map; + import org.kie.kogito.internal.process.runtime.KogitoProcessContext; import org.kie.kogito.jackson.utils.JsonObjectUtils; import org.kie.kogito.process.expr.Expression; import org.kie.kogito.serverless.workflow.utils.ExpressionHandlerUtils; -import org.kie.kogito.serverless.workflow.utils.JsonNodeContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; @@ -61,24 +63,28 @@ private Configuration getConfiguration(KogitoProcessContext context) { .build(); } + private static boolean isContextAware(JsonNode context, Map additionalVars) { + return !additionalVars.isEmpty() && context instanceof ObjectNode; + } + private T eval(JsonNode context, Class returnClass, KogitoProcessContext processInfo) { - try (JsonNodeContext jsonNode = JsonNodeContext.from(context, processInfo)) { - Configuration jsonPathConfig = getConfiguration(processInfo); - DocumentContext parsedContext = JsonPath.using(jsonPathConfig).parse(jsonNode.getNode()); - if (String.class.isAssignableFrom(returnClass)) { - StringBuilder sb = new StringBuilder(); - // valid json path is $. or $[ - for (String part : expr.split("((?=\\$\\.|\\$\\[))")) { - JsonNode partResult = parsedContext.read(part, JsonNode.class); - sb.append(partResult.isTextual() ? partResult.asText() : partResult.toPrettyString()); - } - return (T) sb.toString(); - } else { - Object result = parsedContext.read(expr); - return Boolean.class.isAssignableFrom(returnClass) && result instanceof ArrayNode ? (T) Boolean.valueOf(!((ArrayNode) result).isEmpty()) - : JsonObjectUtils.convertValue(jsonPathConfig.mappingProvider().map(result, returnClass, jsonPathConfig), returnClass); + + Configuration jsonPathConfig = getConfiguration(processInfo); + DocumentContext parsedContext = JsonPath.using(jsonPathConfig).parse(context); + if (String.class.isAssignableFrom(returnClass)) { + StringBuilder sb = new StringBuilder(); + // valid json path is $. or $[ + for (String part : expr.split("((?=\\$\\.|\\$\\[))")) { + JsonNode partResult = parsedContext.read(part, JsonNode.class); + sb.append(partResult.isTextual() ? partResult.asText() : partResult.toPrettyString()); } + return (T) sb.toString(); + } else { + Object result = parsedContext.read(expr); + return Boolean.class.isAssignableFrom(returnClass) && result instanceof ArrayNode ? (T) Boolean.valueOf(!((ArrayNode) result).isEmpty()) + : JsonObjectUtils.convertValue(jsonPathConfig.mappingProvider().map(result, returnClass, jsonPathConfig), returnClass); } + } private void assign(JsonNode context, Object value, KogitoProcessContext processInfo) { diff --git a/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/WorkflowJacksonJsonNodeJsonProvider.java b/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/WorkflowJacksonJsonNodeJsonProvider.java index f423bc5c9eb..7a254728575 100644 --- a/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/WorkflowJacksonJsonNodeJsonProvider.java +++ b/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/WorkflowJacksonJsonNodeJsonProvider.java @@ -18,21 +18,26 @@ */ package org.kie.kogito.expr.jsonpath; +import java.util.Map; import java.util.function.Function; import org.kie.kogito.internal.process.runtime.KogitoProcessContext; import org.kie.kogito.jackson.utils.FunctionBaseJsonNode; import org.kie.kogito.jackson.utils.PrefixJsonNode; import org.kie.kogito.serverless.workflow.utils.ExpressionHandlerUtils; +import org.kie.kogito.serverless.workflow.utils.VariablesHelper; +import com.fasterxml.jackson.databind.JsonNode; import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider; public class WorkflowJacksonJsonNodeJsonProvider extends JacksonJsonNodeJsonProvider { private KogitoProcessContext context; + private Map variables; public WorkflowJacksonJsonNodeJsonProvider(KogitoProcessContext context) { this.context = context; + this.variables = VariablesHelper.getAdditionalVariables(context); } @Override @@ -50,7 +55,7 @@ public Object getMapValue(Object obj, String key) { case "$" + ExpressionHandlerUtils.CONST_MAGIC: return ExpressionHandlerUtils.getConstants(context); default: - return super.getMapValue(obj, key); + return variables.containsKey(key) ? variables.get(key) : super.getMapValue(obj, key); } } } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/CompositeContextNodeHandler.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/CompositeContextNodeHandler.java index d5b473bef8c..755f00e21aa 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/CompositeContextNodeHandler.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/CompositeContextNodeHandler.java @@ -37,7 +37,7 @@ import org.kie.kogito.serverless.workflow.parser.FunctionTypeHandlerFactory; import org.kie.kogito.serverless.workflow.parser.ParserContext; import org.kie.kogito.serverless.workflow.parser.VariableInfo; -import org.kie.kogito.serverless.workflow.utils.JsonNodeContext; +import org.kie.kogito.serverless.workflow.utils.VariablesHelper; import io.serverlessworkflow.api.Workflow; import io.serverlessworkflow.api.actions.Action; @@ -181,7 +181,7 @@ private TimerNodeFactory createTimerNode(RuleFlowNodeContainerFactory f factory.subProcessNode(parserContext.newId()).name(subFlowRef.getWorkflowId()).processId(subFlowRef.getWorkflowId()).waitForCompletion(true), inputVar, outputVar); - JsonNodeContext.getEvalVariables(factory.getNode()).forEach(v -> subProcessNode.inMapping(v.getName(), v.getName())); + VariablesHelper.getEvalVariables(factory.getNode()).forEach(v -> subProcessNode.inMapping(v.getName(), v.getName())); return subProcessNode; } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/JsonNodeContext.java b/kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/VariablesHelper.java similarity index 70% rename from kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/JsonNodeContext.java rename to kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/VariablesHelper.java index bb98c574cca..92696f7e352 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/JsonNodeContext.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/VariablesHelper.java @@ -22,7 +22,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,14 +40,33 @@ import org.kie.kogito.internal.process.runtime.KogitoNodeInstance; import org.kie.kogito.internal.process.runtime.KogitoProcessContext; import org.kie.kogito.jackson.utils.JsonObjectUtils; +import org.kie.kogito.serverless.workflow.SWFConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -public class JsonNodeContext implements AutoCloseable { +public class VariablesHelper { - private final JsonNode jsonNode; - private final Set keys; + private static final Set PREDEFINED_KEYS = Set.of(SWFConstants.DEFAULT_WORKFLOW_VAR, SWFConstants.INPUT_WORKFLOW_VAR); + private static final Logger logger = LoggerFactory.getLogger(VariablesHelper.class); + + private VariablesHelper() { + } + + public static Map getAdditionalVariables(KogitoProcessContext context) { + Map variables = new HashMap<>(); + KogitoNodeInstance nodeInstance = context.getNodeInstance(); + if (nodeInstance != null) { + NodeInstanceContainer container = nodeInstance instanceof NodeInstanceContainer ? (NodeInstanceContainer) nodeInstance : nodeInstance.getNodeInstanceContainer(); + while (container instanceof ContextableInstance) { + addVariablesFromContext((ContextableInstance) container, variables); + container = container instanceof KogitoNodeInstance ? ((KogitoNodeInstance) container).getNodeInstanceContainer() : null; + } + } + logger.debug("Additional variables for expression evaluation are {}", variables); + return variables; + } public static Stream getEvalVariables(Node node) { if (node instanceof ForEachNode) { @@ -66,55 +84,23 @@ private static Stream getEvalVariables(ContextableInstance containerIn private static Stream getEvalVariables(ContextContainer container) { VariableScope variableScope = (VariableScope) container.getDefaultContext(VariableScope.VARIABLE_SCOPE); - return variableScope.getVariables().stream().filter(v -> v.getMetaData(Metadata.EVAL_VARIABLE) != null); - } - - public static JsonNodeContext from(JsonNode jsonNode, KogitoProcessContext context) { - Map map = new HashMap<>(); - if (jsonNode.isObject()) { - ObjectNode objectNode = (ObjectNode) jsonNode; - addVariablesFromContext(objectNode, context, map); - } - return new JsonNodeContext(jsonNode, map.keySet()); + return variableScope.getVariables().stream().filter(VariablesHelper::isEvalVariable); } - public JsonNode getNode() { - return jsonNode; + private static boolean isEvalVariable(Variable v) { + Object isEval = v.getMetaData(Metadata.EVAL_VARIABLE); + return isEval instanceof Boolean ? ((Boolean) isEval).booleanValue() : false; } - private JsonNodeContext(JsonNode jsonNode, Set keys) { - this.jsonNode = jsonNode; - this.keys = keys; - } - - private static void addVariablesFromContext(ObjectNode jsonNode, KogitoProcessContext processInfo, Map variables) { - KogitoNodeInstance nodeInstance = processInfo.getNodeInstance(); - if (nodeInstance != null) { - NodeInstanceContainer container = nodeInstance instanceof NodeInstanceContainer ? (NodeInstanceContainer) nodeInstance : nodeInstance.getNodeInstanceContainer(); - while (container instanceof ContextableInstance) { - getVariablesFromContext(jsonNode, (ContextableInstance) container, variables); - container = container instanceof KogitoNodeInstance ? ((KogitoNodeInstance) container).getNodeInstanceContainer() : null; - } - } - variables.forEach(jsonNode::set); - } - - private static void getVariablesFromContext(ObjectNode jsonNode, ContextableInstance node, Map variables) { + private static void addVariablesFromContext(ContextableInstance node, Map variables) { VariableScopeInstance variableScope = (VariableScopeInstance) node.getContextInstance(VariableScope.VARIABLE_SCOPE); if (variableScope != null) { Collection evalVariables = getEvalVariables(node).map(Variable::getName).collect(Collectors.toList()); for (Entry e : variableScope.getVariables().entrySet()) { - if (evalVariables.contains(e.getKey()) || node instanceof WorkflowProcessInstance && !Objects.equals(jsonNode, e.getValue())) { + if (evalVariables.contains(e.getKey()) || node instanceof WorkflowProcessInstance && !PREDEFINED_KEYS.contains(e.getKey())) { variables.putIfAbsent(e.getKey(), JsonObjectUtils.fromValue(e.getValue())); } } } } - - @Override - public void close() { - if (!keys.isEmpty()) { - keys.forEach(((ObjectNode) jsonNode)::remove); - } - } } diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachCustomType.sw.json b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachCustomType.sw.json index dd7cd214ade..988e6f29909 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachCustomType.sw.json +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachCustomType.sw.json @@ -22,7 +22,7 @@ "functionRef": { "refName": "division", "arguments": { - "dividend": ".item", + "dividend": "$item", "divisor" : ".divisor" } }, diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachRest.sw.json b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachRest.sw.json index 72cc34f32d6..04c0c6e3d00 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachRest.sw.json +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachRest.sw.json @@ -22,7 +22,7 @@ "functionRef": { "refName": "division", "arguments": { - "QUERY_dividend": ".item", + "QUERY_dividend": "$item", "QUERY_divisor" : ".divisor" } }, diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/foreach_child.sw.json b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/foreach_child.sw.json index d1b25583f90..15ce29a016a 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/foreach_child.sw.json +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/foreach_child.sw.json @@ -9,7 +9,7 @@ { "name": "multiply", "type": "expression", - "operation": ".number*.constant" + "operation": "$number*.constant" } ], "states": [