Skip to content

Commit

Permalink
[Fix #3465] Support function arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
fjtirado committed Apr 16, 2024
1 parent c534d91 commit 92da15d
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.kie.kogito.serverless.workflow.parser.handlers;

public interface MappingSetter {

void accept(Object value);

void accept(String key, Object value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.kie.kogito.serverless.workflow.parser.handlers;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.jbpm.ruleflow.core.factory.MappableNodeFactory;
import org.kie.kogito.jackson.utils.JsonNodeVisitor;
import org.kie.kogito.jackson.utils.JsonObjectUtils;
import org.kie.kogito.serverless.workflow.SWFConstants;
import org.kie.kogito.serverless.workflow.utils.ExpressionHandlerUtils;

import com.fasterxml.jackson.databind.JsonNode;

import io.serverlessworkflow.api.Workflow;

public class MappingUtils {

public static <T extends MappableNodeFactory<?>> T addMapping(T nodeFactory, String inputVar, String outputVar) {
return (T) nodeFactory.inMapping(inputVar, SWFConstants.MODEL_WORKFLOW_VAR)
.outMapping(SWFConstants.RESULT, outputVar);
}

public static final void processArgs(Workflow workflow, MappingSetter setter,
JsonNode functionArgs) {
if (functionArgs.isObject()) {
functionsToMap(workflow, functionArgs).forEach((key, value) -> setter.accept(key, value));
} else {
Object object = functionReference(workflow, JsonObjectUtils.simpleToJavaValue(functionArgs));
setter.accept(object);
}
}

private static Map<String, Object> functionsToMap(Workflow workflow, JsonNode jsonNode) {
Map<String, Object> map = new LinkedHashMap<>();
if (jsonNode != null) {
Iterator<Entry<String, JsonNode>> iter = jsonNode.fields();
while (iter.hasNext()) {
Entry<String, JsonNode> entry = iter.next();
map.put(entry.getKey(), functionReference(workflow, JsonObjectUtils.simpleToJavaValue(entry.getValue())));
}
}
return map;
}

private static Object functionReference(Workflow workflow, Object object) {
if (object instanceof JsonNode) {
return JsonNodeVisitor.transformTextNode((JsonNode) object, node -> JsonObjectUtils.fromValue(ExpressionHandlerUtils.replaceExpr(workflow, node.asText())));
} else if (object instanceof CharSequence) {
return ExpressionHandlerUtils.replaceExpr(workflow, object.toString());
} else {
return object;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.jbpm.ruleflow.core.RuleFlowNodeContainerFactory;
import org.jbpm.ruleflow.core.factory.EventNodeFactory;
import org.jbpm.ruleflow.core.factory.JoinFactory;
import org.jbpm.ruleflow.core.factory.MappableNodeFactory;
import org.jbpm.ruleflow.core.factory.NodeFactory;
import org.jbpm.ruleflow.core.factory.SplitFactory;
import org.jbpm.ruleflow.core.factory.StartNodeFactory;
Expand All @@ -39,7 +38,6 @@
import org.jbpm.workflow.core.node.Split;
import org.kie.kogito.correlation.CompositeCorrelation;
import org.kie.kogito.correlation.SimpleCorrelation;
import org.kie.kogito.serverless.workflow.SWFConstants;
import org.kie.kogito.serverless.workflow.parser.ServerlessWorkflowParser;

import io.serverlessworkflow.api.events.EventDefinition;
Expand Down Expand Up @@ -136,11 +134,6 @@ private static CompositeCorrelation getCorrelationAttributes(EventDefinition eve
.metaData("EventBased", "true");
}

public static <T extends MappableNodeFactory<?>> T addMapping(T nodeFactory, String inputVar, String outputVar) {
return (T) nodeFactory.inMapping(inputVar, SWFConstants.MODEL_WORKFLOW_VAR)
.outMapping(SWFConstants.RESULT, outputVar);
}

public static <T extends RuleFlowNodeContainerFactory<T, ?>> SplitFactory<T> exclusiveSplitNode(SplitFactory<T> nodeFactory) {
return nodeFactory.name("ExclusiveSplit_" + nodeFactory.getNode().getId().toExternalFormat())
.type(Split.TYPE_XOR);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,17 @@
package org.kie.kogito.serverless.workflow.utils;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.jbpm.process.core.datatype.DataType;
import org.jbpm.process.core.datatype.DataTypeResolver;
import org.jbpm.ruleflow.core.RuleFlowNodeContainerFactory;
import org.jbpm.ruleflow.core.factory.WorkItemNodeFactory;
import org.kie.kogito.jackson.utils.JsonNodeVisitor;
import org.kie.kogito.jackson.utils.JsonObjectUtils;
import org.kie.kogito.process.expr.ExpressionHandlerFactory;
import org.kie.kogito.serverless.workflow.SWFConstants;
import org.kie.kogito.serverless.workflow.parser.ParserContext;
import org.kie.kogito.serverless.workflow.parser.handlers.NodeFactoryUtils;
import org.kie.kogito.serverless.workflow.parser.handlers.MappingSetter;
import org.kie.kogito.serverless.workflow.parser.handlers.MappingUtils;
import org.kie.kogito.serverless.workflow.suppliers.ExpressionParametersFactorySupplier;
import org.kie.kogito.serverless.workflow.suppliers.ObjectResolverSupplier;

Expand Down Expand Up @@ -71,57 +67,40 @@ protected WorkItemNodeFactory<?> buildWorkItem(RuleFlowNodeContainerFactory<?, ?
ParserContext parserContext,
String inputVar,
String outputVar) {
return NodeFactoryUtils.addMapping(embeddedSubProcess.workItemNode(parserContext.newId()), inputVar, outputVar);
return MappingUtils.addMapping(embeddedSubProcess.workItemNode(parserContext.newId()), inputVar, outputVar);
}

protected final void processArgs(Workflow workflow, WorkItemNodeFactory<?> workItemFactory,
JsonNode functionArgs, String paramName) {
if (functionArgs.isObject()) {
functionsToMap(workflow, functionArgs).forEach((key, value) -> processArg(workflow, key, value, workItemFactory, paramName));
} else {
Object object = functionReference(workflow, JsonObjectUtils.simpleToJavaValue(functionArgs));
boolean isExpr = isExpression(workflow, object);
if (isExpr) {
workItemFactory.workParameterFactory(new ExpressionParametersFactorySupplier(workflow.getExpressionLang(), object, paramName));
} else {
workItemFactory.workParameter(SWFConstants.CONTENT_DATA, object);
MappingUtils.processArgs(workflow, new MappingSetter() {
@Override
public void accept(String key, Object value) {
boolean isExpr = isExpression(workflow, value);
workItemFactory
.workParameter(key,
isExpr ? new ObjectResolverSupplier(workflow.getExpressionLang(), value, paramName) : value)
.workParameterDefinition(key,
getDataType(value, isExpr));
}
workItemFactory.workParameterDefinition(SWFConstants.CONTENT_DATA, getDataType(object, isExpr));
}
}

private Map<String, Object> functionsToMap(Workflow workflow, JsonNode jsonNode) {
Map<String, Object> map = new LinkedHashMap<>();
if (jsonNode != null) {
Iterator<Entry<String, JsonNode>> iter = jsonNode.fields();
while (iter.hasNext()) {
Entry<String, JsonNode> entry = iter.next();
map.put(entry.getKey(), functionReference(workflow, JsonObjectUtils.simpleToJavaValue(entry.getValue())));
@Override
public void accept(Object value) {
boolean isExpr = isExpression(workflow, value);
if (isExpr) {
workItemFactory.workParameterFactory(new ExpressionParametersFactorySupplier(workflow.getExpressionLang(), value, paramName));
} else {
workItemFactory.workParameter(SWFConstants.CONTENT_DATA, value);
}
workItemFactory.workParameterDefinition(SWFConstants.CONTENT_DATA, getDataType(value, isExpr));
}
}
return map;
}

private Object functionReference(Workflow workflow, Object object) {
if (object instanceof JsonNode) {
return JsonNodeVisitor.transformTextNode((JsonNode) object, node -> JsonObjectUtils.fromValue(ExpressionHandlerUtils.replaceExpr(workflow, node.asText())));
} else if (object instanceof CharSequence) {
return ExpressionHandlerUtils.replaceExpr(workflow, object.toString());
} else {
return object;
}
}, functionArgs);
}

private void processArg(Workflow workflow, String key, Object value, WorkItemNodeFactory<?> workItemFactory, String paramName) {
boolean isExpr = isExpression(workflow, value);
workItemFactory
.workParameter(key,
isExpr ? new ObjectResolverSupplier(workflow.getExpressionLang(), value, paramName) : value)
.workParameterDefinition(key,
getDataType(value, isExpr));
private static boolean isExpression(Workflow workflow, Object value) {
return value instanceof CharSequence && ExpressionHandlerFactory.get(workflow.getExpressionLang(), value.toString()).isValid() || value instanceof JsonNode;
}

DataType getDataType(Object object, boolean isExpr) {
private static DataType getDataType(Object object, boolean isExpr) {
if (object instanceof ObjectNode) {
return DataTypeResolver.fromClass(Map.class);
} else if (object instanceof ArrayNode) {
Expand All @@ -130,8 +109,4 @@ DataType getDataType(Object object, boolean isExpr) {
return DataTypeResolver.fromObject(object, isExpr);
}
}

private boolean isExpression(Workflow workflow, Object value) {
return value instanceof CharSequence && ExpressionHandlerFactory.get(workflow.getExpressionLang(), value.toString()).isValid() || value instanceof JsonNode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@

import org.jbpm.ruleflow.core.RuleFlowNodeContainerFactory;
import org.jbpm.ruleflow.core.factory.NodeFactory;
import org.jbpm.ruleflow.core.factory.RuleSetNodeFactory;
import org.kie.kogito.decision.DecisionModel;
import org.kie.kogito.dmn.DMNKogito;
import org.kie.kogito.dmn.DmnDecisionModel;
import org.kie.kogito.serverless.workflow.SWFConstants;
import org.kie.kogito.serverless.workflow.dmn.SWFDecisionEngine;
import org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory;
import org.kie.kogito.serverless.workflow.parser.FunctionTypeHandler;
import org.kie.kogito.serverless.workflow.parser.ParserContext;
import org.kie.kogito.serverless.workflow.parser.VariableInfo;
import org.kie.kogito.serverless.workflow.parser.handlers.NodeFactoryUtils;
import org.kie.kogito.serverless.workflow.parser.handlers.MappingSetter;
import org.kie.kogito.serverless.workflow.parser.handlers.MappingUtils;

import com.fasterxml.jackson.databind.JsonNode;

import io.serverlessworkflow.api.Workflow;
import io.serverlessworkflow.api.functions.FunctionDefinition;
Expand Down Expand Up @@ -67,8 +73,24 @@ public boolean isCustom() {
String namespace = Objects.requireNonNull(metadata.get(NAMESPACE), String.format(REQUIRED_MESSAGE, NAMESPACE));
String model = Objects.requireNonNull(metadata.get(MODEL), String.format(REQUIRED_MESSAGE, MODEL));
String file = Objects.requireNonNull(metadata.get(FILE), String.format(REQUIRED_MESSAGE, FILE));
return NodeFactoryUtils.addMapping(embeddedSubProcess.ruleSetNode(context.newId()).decision(namespace, model, model, () -> loadDMNFromFile(namespace, model, file)),
RuleSetNodeFactory<?> nodeFactory = MappingUtils.addMapping(embeddedSubProcess.ruleSetNode(context.newId()).decision(namespace, model, model, () -> loadDMNFromFile(namespace, model, file)),
varInfo.getInputVar(), varInfo.getOutputVar());
JsonNode functionArgs = functionRef.getArguments();
if (functionArgs != null) {
nodeFactory.metaData(SWFDecisionEngine.EXPR_LANG, workflow.getExpressionLang());
MappingUtils.processArgs(workflow, new MappingSetter() {
@Override
public void accept(String key, Object value) {
nodeFactory.parameter(key, value);
}

@Override
public void accept(Object value) {
nodeFactory.parameter(SWFConstants.CONTENT_DATA, value);
}
}, functionArgs);
}
return nodeFactory;
}

private DecisionModel loadDMNFromFile(String namespace, String model, String file) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.kie.kogito.serverless.workflow.dmn;

import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.Map;

Expand All @@ -22,12 +23,25 @@
public class SWFDMNTest {
@Test
void testDMNFile() throws IOException {
doIt(buildWorkflow(Collections.emptyMap()));
}

@Test
void testDMNFileWithArgs() throws IOException {
doIt(buildWorkflow(Map.of("Driver", ".Driver", "Violation", ".Violation")));
}

private Workflow buildWorkflow(Map<String, Object> args) {
return workflow("PlayingWithDMN")
.start(operation().action(call(custom("DMNTest", "dmn").metadata(DMNTypeHandler.FILE, "Traffic Violation.dmn")
.metadata(DMNTypeHandler.MODEL, "Traffic Violation")
.metadata(DMNTypeHandler.NAMESPACE, "https://github.com/kiegroup/drools/kie-dmn/_A4BCA8B8-CF08-433F-93B2-A2598F19ECFF"), args))
.outputFilter("{\"Should the driver be suspended?\"}"))
.end().build();
}

private void doIt(Workflow workflow) {
try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) {
Workflow workflow = workflow("PlayingWithDMN")
.start(operation().action(call(custom("DMNTest", "dmn").metadata(DMNTypeHandler.FILE, "Traffic Violation.dmn")
.metadata(DMNTypeHandler.MODEL, "Traffic Violation")
.metadata(DMNTypeHandler.NAMESPACE, "https://github.com/kiegroup/drools/kie-dmn/_A4BCA8B8-CF08-433F-93B2-A2598F19ECFF"))))
.end().build();
JsonNode response = application.execute(workflow, Map.of("Driver", Map.of("Name", "Pepe", "Age", 19, "Points", 0, "State", "Spain", "City", "Zaragoza"), "Violation", Map.of("Code", "12",
"Date", new Date(System.currentTimeMillis()), "Type", "parking"))).getWorkflowdata();
assertThat(response.get("Should the driver be suspended?")).isEqualTo(new TextNode("No"));
Expand Down
Loading

0 comments on commit 92da15d

Please sign in to comment.