This is an automated email from the ASF dual-hosted git repository.
fjtiradosarti pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/incubator-kie-kogito-runtimes.git
The following commit(s) were added to refs/heads/main by this push:
new de3b86b249 [Fix_#3465] Adds DMN support to SonataFlow (#3468)
de3b86b249 is described below
commit de3b86b249d36702fa10e2b48cafac7e0a8ed5dc
Author: Francisco Javier Tirado Sarti
<[email protected]>
AuthorDate: Thu Apr 18 13:07:55 2024 +0200
[Fix_#3465] Adds DMN support to SonataFlow (#3468)
* [Fix_#3465] Adding DMNWorkItemHandler
* [Fix_#3465] Node approach
* [Fix #3465] Supporting white space
* [Fix #3465] Refactor mapping
* [Fix #3465] Support function arguments
* [Fix #3465] Gabrieles comment
---
kogito-bom/pom.xml | 22 +++++
.../workflow/io/ClassPathContentLoader.java | 4 +-
.../workflow/io/URIContentLoaderFactory.java | 4 +-
.../workflow/parser/handlers/MappingSetter.java | 26 ++++++
.../workflow/parser/handlers/MappingUtils.java | 74 +++++++++++++++
.../serverless/workflow/utils/WorkItemBuilder.java | 78 +++++-----------
.../workflow/io/URIContentLoaderTest.java | 3 +-
.../kogito-serverless-workflow-dmn-parser/pom.xml | 62 +++++++++++++
.../workflow/parser/types/DMNTypeHandler.java | 103 +++++++++++++++++++++
....serverless.workflow.parser.FunctionTypeHandler | 1 +
.../kogito/serverless/workflow/dmn/SWFDMNTest.java | 57 ++++++++++++
.../kogito-serverless-workflow-dmn/pom.xml | 24 +++++
.../serverless/workflow/dmn/SWFDecisionEngine.java | 96 +++++++++++++++++++
...m.workflow.instance.rule.DecisionRuleTypeEngine | 1 +
.../workflow/fluent/FunctionBuilder.java | 9 +-
.../kogito/serverless/workflow/SWFConstants.java | 1 +
kogito-serverless-workflow/pom.xml | 2 +
17 files changed, 507 insertions(+), 60 deletions(-)
diff --git a/kogito-bom/pom.xml b/kogito-bom/pom.xml
index dc54a9d717..b07eec3f81 100755
--- a/kogito-bom/pom.xml
+++ b/kogito-bom/pom.xml
@@ -1941,6 +1941,28 @@
<version>${project.version}</version>
<classifier>sources</classifier>
</dependency>
+ <dependency>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow-dmn-parser</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow-dmn-parser</artifactId>
+ <version>${project.version}</version>
+ <classifier>sources</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow-dmn</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow-dmn</artifactId>
+ <version>${project.version}</version>
+ <classifier>sources</classifier>
+ </dependency>
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-serverless-workflow-rest-parser</artifactId>
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java
index 56b80a8beb..413c199a67 100644
---
a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java
@@ -24,6 +24,8 @@ import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Optional;
@@ -40,7 +42,7 @@ public class ClassPathContentLoader extends
CachedContentLoader {
static String getPath(URI uri) {
final String classPathPrefix = "classpath:";
- String str = uri.toString();
+ String str = URLDecoder.decode(uri.toString(),
Charset.defaultCharset());
if (str.toLowerCase().startsWith(classPathPrefix)) {
str = str.substring(classPathPrefix.length());
while (str.startsWith("/")) {
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java
index 002174e876..6e5b806594 100644
---
a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java
@@ -23,6 +23,8 @@ import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
import java.util.Optional;
import org.kie.kogito.serverless.workflow.parser.ParserContext;
@@ -91,7 +93,7 @@ public class URIContentLoaderFactory {
}
public static Builder builder(String uri) {
- return new Builder(URI.create(uri));
+ return new Builder(URI.create(URLEncoder.encode(uri,
Charset.defaultCharset())));
}
public static class Builder {
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingSetter.java
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingSetter.java
new file mode 100644
index 0000000000..adfa6a4e62
--- /dev/null
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingSetter.java
@@ -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);
+}
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingUtils.java
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingUtils.java
new file mode 100644
index 0000000000..ec64424831
--- /dev/null
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingUtils.java
@@ -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,
+ JsonNode functionArgs, MappingSetter setter) {
+ 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;
+ }
+ }
+}
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/WorkItemBuilder.java
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/WorkItemBuilder.java
index bd7b522b47..fbb03806de 100644
---
a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/WorkItemBuilder.java
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/WorkItemBuilder.java
@@ -19,20 +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.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;
@@ -45,8 +42,6 @@ import io.serverlessworkflow.api.functions.FunctionRef;
public abstract class WorkItemBuilder {
- private static final String RESULT = "Result";
-
protected <T extends RuleFlowNodeContainerFactory<T, ?>>
WorkItemNodeFactory<T> addFunctionArgs(Workflow workflow,
WorkItemNodeFactory<T> node, FunctionRef functionRef) {
JsonNode functionArgs = functionRef.getArguments();
if (functionArgs != null) {
@@ -72,59 +67,40 @@ public abstract class WorkItemBuilder {
ParserContext parserContext,
String inputVar,
String outputVar) {
- return embeddedSubProcess.workItemNode(parserContext.newId())
- .inMapping(inputVar, SWFConstants.MODEL_WORKFLOW_VAR)
- .outMapping(RESULT, 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, functionArgs, 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;
- }
- }
-
- 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) {
@@ -133,8 +109,4 @@ public abstract class WorkItemBuilder {
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;
- }
}
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java
index d3d9ac9b3d..e3c2ec3696 100644
---
a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java
@@ -27,7 +27,6 @@ import
org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.Builder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static
org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static
org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.builder;
import static
org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.compoundURI;
import static
org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.readString;
@@ -53,7 +52,7 @@ class URIContentLoaderTest {
@Test
void testNotExistingClasspath() {
Builder builder = builder("classpath:/noPepe.txt");
- assertThatIllegalArgumentException().isThrownBy(() ->
readString(builder));
+ assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(() ->
readString(builder));
}
@Test
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/pom.xml
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/pom.xml
new file mode 100644
index 0000000000..cbb756c105
--- /dev/null
+++ b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/pom.xml
@@ -0,0 +1,62 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow</artifactId>
+ <version>999-SNAPSHOT</version>
+ </parent>
+ <name>Kogito :: Serverless Workflow :: DMN :: Parser</name>
+
+ <properties>
+
<java.module.name>org.kie.kogito.serverless.workflow.dmn.parser</java.module.name>
+ </properties>
+
+ <artifactId>kogito-serverless-workflow-dmn-parser</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow-builder</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow-dmn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow-fluent</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow-executor-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.kie</groupId>
+ <artifactId>kie-dmn-test-resources</artifactId>
+ <classifier>tests</classifier>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/java/org/kie/kogito/serverless/workflow/parser/types/DMNTypeHandler.java
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/java/org/kie/kogito/serverless/workflow/parser/types/DMNTypeHandler.java
new file mode 100644
index 0000000000..0b98f50dc2
--- /dev/null
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/java/org/kie/kogito/serverless/workflow/parser/types/DMNTypeHandler.java
@@ -0,0 +1,103 @@
+/*
+ * 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.types;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UncheckedIOException;
+import java.util.Map;
+import java.util.Objects;
+
+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.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;
+import io.serverlessworkflow.api.functions.FunctionRef;
+
+public class DMNTypeHandler implements FunctionTypeHandler {
+
+ private static final String DMN_TYPE = "dmn";
+ public static final String NAMESPACE = "namespace";
+ public static final String MODEL = "model";
+ public static final String FILE = "file";
+
+ private static final String REQUIRED_MESSAGE = "%s is required on metadata
for DMN";
+
+ @Override
+ public String type() {
+ return DMN_TYPE;
+ }
+
+ @Override
+ public boolean isCustom() {
+ return true;
+ }
+
+ @Override
+ public NodeFactory<?, ?> getActionNode(Workflow workflow, ParserContext
context,
+ RuleFlowNodeContainerFactory<?, ?> embeddedSubProcess,
FunctionDefinition functionDef,
+ FunctionRef functionRef, VariableInfo varInfo) {
+ Map<String, String> metadata =
Objects.requireNonNull(functionDef.getMetadata(), "Metadata is required for
DMN");
+ 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));
+ 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, functionArgs, 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);
+ }
+ });
+ }
+ return nodeFactory;
+ }
+
+ private DecisionModel loadDMNFromFile(String namespace, String model,
String file) {
+ try (Reader reader = new
InputStreamReader(URIContentLoaderFactory.builder(file).withClassloader(this.getClass().getClassLoader()).build().getInputStream()))
{
+ return new
DmnDecisionModel(DMNKogito.createGenericDMNRuntime(reader), namespace, model);
+ } catch (IOException io) {
+ throw new UncheckedIOException(io);
+ }
+ }
+}
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/resources/META-INF/services/org.kie.kogito.serverless.workflow.parser.FunctionTypeHandler
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/resources/META-INF/services/org.kie.kogito.serverless.workflow.parser.FunctionTypeHandler
new file mode 100644
index 0000000000..8169c5bf60
--- /dev/null
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/resources/META-INF/services/org.kie.kogito.serverless.workflow.parser.FunctionTypeHandler
@@ -0,0 +1 @@
+org.kie.kogito.serverless.workflow.parser.types.DMNTypeHandler
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/test/java/org/kie/kogito/serverless/workflow/dmn/SWFDMNTest.java
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/test/java/org/kie/kogito/serverless/workflow/dmn/SWFDMNTest.java
new file mode 100644
index 0000000000..d2828b9893
--- /dev/null
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/test/java/org/kie/kogito/serverless/workflow/dmn/SWFDMNTest.java
@@ -0,0 +1,57 @@
+package org.kie.kogito.serverless.workflow.dmn;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.serverless.workflow.executor.StaticWorkflowApplication;
+import org.kie.kogito.serverless.workflow.parser.types.DMNTypeHandler;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+
+import io.serverlessworkflow.api.Workflow;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.call;
+import static org.kie.kogito.serverless.workflow.fluent.FunctionBuilder.custom;
+import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.operation;
+import static
org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow;
+
+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")));
+ }
+
+ @Test
+ void testDMNFileWithExprArg() throws IOException {
+ doIt(buildWorkflow("{Driver:.Driver,Violation:.Violation}"));
+ }
+
+ private void doIt(Workflow workflow) {
+ try (StaticWorkflowApplication application =
StaticWorkflowApplication.create()) {
+ 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"));
+ response = application.execute(workflow, Map.of("Driver",
Map.of("Name", "Pepe", "Age", 19, "Points", 19, "State", "Spain", "City",
"Zaragoza"), "Violation", Map.of("Code", "12",
+ "Date", new Date(System.currentTimeMillis()), "Type",
"speed", "Speed Limit", "120", "Actual Speed", "180"))).getWorkflowdata();
+ assertThat(response.get("Should the driver be
suspended?")).isEqualTo(new TextNode("Yes"));
+ }
+ }
+
+ private Workflow buildWorkflow(Object args) {
+ return workflow("PlayingWithDMN")
+ .start(operation().action(call(custom("DMNTest",
"dmn").metadata(DMNTypeHandler.FILE, "classpath:valid_models/DMNv1_x/Traffic
Violation Simple.dmn")
+ .metadata(DMNTypeHandler.MODEL, "Traffic Violation")
+ .metadata(DMNTypeHandler.NAMESPACE,
"https://github.com/kiegroup/drools/kie-dmn/_A4BCA8B8-CF08-433F-93B2-A2598F19ECFF"),
args)))
+ .end().build();
+ }
+}
diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-dmn/pom.xml
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/pom.xml
new file mode 100644
index 0000000000..7a8d68802b
--- /dev/null
+++ b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/pom.xml
@@ -0,0 +1,24 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow</artifactId>
+ <version>999-SNAPSHOT</version>
+ </parent>
+ <artifactId>kogito-serverless-workflow-dmn</artifactId>
+ <name>Kogito :: Serverless Workflow :: DMN :: Runtime</name>
+ <properties>
+
<java.module.name>org.kie.kogito.serverless.workflow.dmn.runtime</java.module.name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-serverless-workflow-runtime</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.kie.kogito</groupId>
+ <artifactId>kogito-dmn</artifactId>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/java/org/kie/kogito/serverless/workflow/dmn/SWFDecisionEngine.java
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/java/org/kie/kogito/serverless/workflow/dmn/SWFDecisionEngine.java
new file mode 100644
index 0000000000..c84acfc652
--- /dev/null
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/java/org/kie/kogito/serverless/workflow/dmn/SWFDecisionEngine.java
@@ -0,0 +1,96 @@
+package org.kie.kogito.serverless.workflow.dmn;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.jbpm.util.ContextFactory;
+import org.jbpm.workflow.core.impl.NodeIoHelper;
+import org.jbpm.workflow.core.node.RuleSetNode;
+import org.jbpm.workflow.instance.node.RuleSetNodeInstance;
+import org.jbpm.workflow.instance.rule.DecisionRuleTypeEngine;
+import org.kie.api.runtime.KieSession;
+import org.kie.dmn.api.core.DMNContext;
+import org.kie.dmn.api.core.DMNMessage;
+import org.kie.dmn.api.core.DMNResult;
+import org.kie.dmn.api.core.DMNRuntime;
+import org.kie.kogito.decision.DecisionModel;
+import org.kie.kogito.dmn.DmnDecisionModel;
+import org.kie.kogito.dmn.rest.DMNJSONUtils;
+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.process.expr.ExpressionHandlerFactory;
+import org.kie.kogito.serverless.workflow.SWFConstants;
+
+import static org.kie.kogito.serverless.workflow.SWFConstants.CONTENT_DATA;
+
+public class SWFDecisionEngine implements DecisionRuleTypeEngine {
+
+ public static final String EXPR_LANG = "lang";
+
+ @Override
+ public void evaluate(RuleSetNodeInstance rsni, String inputNamespace,
String inputModel, String decision) {
+ String namespace = rsni.resolveExpression(inputNamespace);
+ String model = rsni.resolveExpression(inputModel);
+ DecisionModel modelInstance =
+ Optional.ofNullable(rsni.getRuleSetNode().getDecisionModel())
+ .orElse(() -> new DmnDecisionModel(
+ ((KieSession)
getKieRuntime(rsni)).getKieRuntime(DMNRuntime.class),
+ namespace,
+ model))
+ .get();
+
+ //Input Binding
+ DMNContext context = DMNJSONUtils.ctx(modelInstance,
getInputParameters(rsni));
+ DMNResult dmnResult = modelInstance.evaluateAll(context);
+ if (dmnResult.hasErrors()) {
+ String errors =
dmnResult.getMessages(DMNMessage.Severity.ERROR).stream()
+ .map(Object::toString)
+ .collect(Collectors.joining(", "));
+
+ throw new RuntimeException("DMN result errors:: " + errors);
+ }
+ //Output Binding
+ Map<String, Object> outputSet = Map.of(SWFConstants.RESULT,
dmnResult.getContext().getAll());
+ NodeIoHelper.processOutputs(rsni, outputSet::get, rsni::getVariable);
+
+ rsni.triggerCompleted();
+ }
+
+ private Map<String, Object> getInputParameters(RuleSetNodeInstance rsni) {
+ RuleSetNode node = rsni.getRuleSetNode();
+ Map<String, Object> inputParameters = node.getParameters();
+ int size = inputParameters.size();
+ if (size == 0) {
+ inputParameters =
JsonObjectUtils.convertValue(getInputs(rsni).get(SWFConstants.MODEL_WORKFLOW_VAR),
Map.class);
+ } else if (size == 1 && inputParameters.containsKey(CONTENT_DATA)) {
+ return eval(ContextFactory.fromNode(rsni),
ExpressionHandlerFactory.get((String) node.getMetaData().get(EXPR_LANG),
(String) inputParameters.get(CONTENT_DATA)));
+ } else {
+ inputParameters =
getInputParameters(ContextFactory.fromNode(rsni), (String)
node.getMetaData().get(EXPR_LANG), new HashMap<>(inputParameters));
+ }
+ return inputParameters;
+
+ }
+
+ private Map<String, Object> getInputParameters(KogitoProcessContext
context, String exprLang, Map<String, Object> inputParameters) {
+ for (Map.Entry<String, Object> entry : inputParameters.entrySet()) {
+ Object value = entry.getValue();
+ if (value instanceof Map) {
+ entry.setValue(getInputParameters(context, exprLang,
(Map<String, Object>) value));
+ } else if (value instanceof CharSequence) {
+ Expression expr = ExpressionHandlerFactory.get(exprLang,
value.toString());
+ if (expr.isValid()) {
+ entry.setValue(eval(context, expr));
+ }
+ }
+ }
+ return inputParameters;
+ }
+
+ private Map<String, Object> eval(KogitoProcessContext context, Expression
expr) {
+ return
expr.eval(JsonObjectUtils.fromValue(context.getVariable(SWFConstants.DEFAULT_WORKFLOW_VAR)),
Map.class, context);
+ }
+
+}
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/resources/META-INF/services/org.jbpm.workflow.instance.rule.DecisionRuleTypeEngine
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/resources/META-INF/services/org.jbpm.workflow.instance.rule.DecisionRuleTypeEngine
new file mode 100644
index 0000000000..077e0fb2c7
--- /dev/null
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/resources/META-INF/services/org.jbpm.workflow.instance.rule.DecisionRuleTypeEngine
@@ -0,0 +1 @@
+org.kie.kogito.serverless.workflow.dmn.SWFDecisionEngine
\ No newline at end of file
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/fluent/FunctionBuilder.java
b/kogito-serverless-workflow/kogito-serverless-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/fluent/FunctionBuilder.java
index cc2182c414..1abe1d2fca 100644
---
a/kogito-serverless-workflow/kogito-serverless-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/fluent/FunctionBuilder.java
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/fluent/FunctionBuilder.java
@@ -76,12 +76,15 @@ public class FunctionBuilder {
}
private static FunctionBuilder service(String name, String langName,
String moduleName, String methodName) {
- return new FunctionBuilder(new
FunctionDefinition(name).withType(Type.CUSTOM)
- .withOperation(ServiceTypeHandler.SERVICE_TYPE +
CUSTOM_TYPE_SEPARATOR + langName + CUSTOM_TYPE_SEPARATOR + moduleName +
ServiceTypeHandler.INTFC_SEPARATOR + methodName));
+ return custom(name, ServiceTypeHandler.SERVICE_TYPE +
CUSTOM_TYPE_SEPARATOR + langName + CUSTOM_TYPE_SEPARATOR + moduleName +
ServiceTypeHandler.INTFC_SEPARATOR + methodName);
}
public static FunctionBuilder log(String name, WorkflowLogLevel level) {
- return new FunctionBuilder(new
FunctionDefinition(name).withType(Type.CUSTOM).withOperation(SYSOUT_TYPE +
CUSTOM_TYPE_SEPARATOR + level));
+ return custom(name, SYSOUT_TYPE + CUSTOM_TYPE_SEPARATOR + level);
+ }
+
+ public static FunctionBuilder custom(String name, String operation) {
+ return new FunctionBuilder(new
FunctionDefinition(name).withType(Type.CUSTOM).withOperation(operation));
}
private FunctionBuilder(FunctionDefinition functionDefinition) {
diff --git
a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/SWFConstants.java
b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/SWFConstants.java
index 20a7b7d5ad..3d822ab0be 100644
---
a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/SWFConstants.java
+++
b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/SWFConstants.java
@@ -21,6 +21,7 @@ package org.kie.kogito.serverless.workflow;
public class SWFConstants {
public static final String DEFAULT_WORKFLOW_VAR = "workflowdata";
+ public static final String RESULT = "Result";
public static final String MODEL_WORKFLOW_VAR = "Parameter";
public static final String CONTENT_DATA = "ContentData";
public static final String SCRIPT = "script";
diff --git a/kogito-serverless-workflow/pom.xml
b/kogito-serverless-workflow/pom.xml
index f752d16d20..cfca4a918e 100644
--- a/kogito-serverless-workflow/pom.xml
+++ b/kogito-serverless-workflow/pom.xml
@@ -55,6 +55,8 @@
<module>kogito-jq-expression</module>
<module>kogito-serverless-workflow-executor</module>
<module>kogito-serverless-workflow-executor-tests</module>
+ <module>kogito-serverless-workflow-dmn-parser</module>
+ <module>kogito-serverless-workflow-dmn</module>
</modules>
<profiles>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]