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

[Fix #3694] Avoid generation of fake ProcessInstanceVariableDataEvent #3696

Merged
merged 1 commit into from
Oct 10, 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 @@ -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;

Expand Down Expand Up @@ -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;
}

Expand All @@ -215,8 +216,8 @@ private <T> T eval(JsonNode context, Class<T> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,24 +63,28 @@ private Configuration getConfiguration(KogitoProcessContext context) {
.build();
}

private static boolean isContextAware(JsonNode context, Map<String, JsonNode> additionalVars) {
return !additionalVars.isEmpty() && context instanceof ObjectNode;
}

private <T> T eval(JsonNode context, Class<T> 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 $[
fjtirado marked this conversation as resolved.
Show resolved Hide resolved
for (String part : expr.split("((?=\\$\\.|\\$\\[))")) {
JsonNode partResult = parsedContext.read(part, JsonNode.class);
gmunozfe marked this conversation as resolved.
Show resolved Hide resolved
sb.append(partResult.isTextual() ? partResult.asText() : partResult.toPrettyString());
fjtirado marked this conversation as resolved.
Show resolved Hide resolved
}
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, JsonNode> variables;

public WorkflowJacksonJsonNodeJsonProvider(KogitoProcessContext context) {
this.context = context;
this.variables = VariablesHelper.getAdditionalVariables(context);
}

@Override
Expand All @@ -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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ void testExpr() {
void testForEach() {
final String SQUARE = "square";
try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) {
Workflow subflow = workflow("Square").start(operation().action(call(expr(SQUARE, ".input*.input"))).outputFilter(".response")).end().build();
Workflow subflow = workflow("Square").start(operation().action(call(expr(SQUARE, "$input*$input"))).outputFilter(".response")).end().build();

Workflow workflow = workflow("ForEachTest")
.start(forEach(".numbers").loopVar("input").outputCollection(".result").action(subprocess(application.process(subflow)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String> keys;
private static final Set<String> 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<String, JsonNode> getAdditionalVariables(KogitoProcessContext context) {
Map<String, JsonNode> 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<Variable> getEvalVariables(Node node) {
if (node instanceof ForEachNode) {
Expand All @@ -66,55 +84,23 @@ private static Stream<Variable> getEvalVariables(ContextableInstance containerIn

private static Stream<Variable> 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<String, JsonNode> 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<String> keys) {
this.jsonNode = jsonNode;
this.keys = keys;
}

private static void addVariablesFromContext(ObjectNode jsonNode, KogitoProcessContext processInfo, Map<String, JsonNode> 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<String, JsonNode> variables) {
private static void addVariablesFromContext(ContextableInstance node, Map<String, JsonNode> variables) {
VariableScopeInstance variableScope = (VariableScopeInstance) node.getContextInstance(VariableScope.VARIABLE_SCOPE);
if (variableScope != null) {
Collection<String> evalVariables = getEvalVariables(node).map(Variable::getName).collect(Collectors.toList());
for (Entry<String, Object> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"functionRef": {
"refName": "division",
"arguments": {
"dividend": ".item",
"dividend": "$item",
"divisor" : ".divisor"
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"functionRef": {
"refName": "division",
"arguments": {
"QUERY_dividend": ".item",
"QUERY_dividend": "$item",
"QUERY_divisor" : ".divisor"
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"name": "multiply",
"type": "expression",
"operation": ".number*.constant"
"operation": "$number*.constant"
}
],
"states": [
Expand Down
Loading