diff --git a/api/kogito-api/src/main/java/org/kie/kogito/Model.java b/api/kogito-api/src/main/java/org/kie/kogito/Model.java index 1d827230d19..fbe9acb8ded 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/Model.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/Model.java @@ -19,6 +19,8 @@ package org.kie.kogito; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; /** * Represents data model type of objects that are usually descriptor of data holders. @@ -29,4 +31,10 @@ public interface Model extends MapInput, MapOutput { default void update(Map params) { Models.fromMap(this, params); } + + default Map updatePartially(Map params) { + params = params.entrySet().stream().filter(e -> e.getValue() != null).collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + update(params); + return params; + } } diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/NodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/NodeInstance.java index 47e84a4f425..bb4109242f4 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/NodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/NodeInstance.java @@ -18,6 +18,7 @@ */ package org.jbpm.workflow.instance; +import java.util.Date; import java.util.Map; import org.jbpm.process.instance.ContextInstance; @@ -52,6 +53,8 @@ public interface NodeInstance extends KogitoNodeInstance { String getSlaTimerId(); + void internalSetTriggerTime(Date date); + default KogitoProcessInstance getKogitoProcessInstance() { return (KogitoProcessInstance) getProcessInstance(); } diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/NodeInstanceContainer.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/NodeInstanceContainer.java index 015fc35119e..ac573322705 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/NodeInstanceContainer.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/NodeInstanceContainer.java @@ -63,7 +63,11 @@ default NodeInstance getByNodeDefinitionId(final String nodeDefinitionId, NodeCo for (Node node : nodeContainer.getNodes()) { if (nodeDefinitionId.equals(node.getMetaData().get(UNIQUE_ID))) { - return getNodeInstance(node); + if (nodeContainer instanceof Node) { + return ((NodeInstanceContainer) getNodeInstance((Node) nodeContainer)).getNodeInstance(node); + } else { + return getNodeInstance(node); + } } if (node instanceof NodeContainer) { diff --git a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java index 154a1cec752..218a51da754 100644 --- a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java @@ -34,7 +34,6 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Collectors; import org.jbpm.process.instance.InternalProcessRuntime; import org.jbpm.ruleflow.core.RuleFlowProcess; @@ -372,19 +371,20 @@ public void setVersion(long version) { @Override public T updateVariables(T updates) { - return updateVariables(bind(updates)); + Map map = bind(updates); + variables.update(map); + return updateVariables(map); } @Override public T updateVariablesPartially(T updates) { - return updateVariables(bind(updates).entrySet().stream().filter(e -> e.getValue() != null).collect(Collectors.toMap(Entry::getKey, Entry::getValue))); + return updateVariables(this.variables.updatePartially(bind(updates))); } private T updateVariables(Map map) { for (Entry entry : map.entrySet()) { processInstance().setVariable(entry.getKey(), entry.getValue()); } - this.variables.update(map); addToUnitOfWork(pi -> ((MutableProcessInstances) process.instances()).update(pi.id(), pi)); return variables; } @@ -704,6 +704,10 @@ public void retrigger() { pInstance.setState(STATE_ACTIVE); pInstance.internalSetErrorNodeId(null); pInstance.internalSetErrorMessage(null); + org.kie.api.runtime.process.NodeInstanceContainer nodeInstanceContainer = ni.getNodeInstanceContainer(); + if (nodeInstanceContainer instanceof NodeInstance) { + ((NodeInstance) nodeInstanceContainer).internalSetTriggerTime(new Date()); + } ni.trigger(null, Node.CONNECTION_DEFAULT_TYPE); removeOnFinish(); } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/models/JsonNodeModel.java b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/models/JsonNodeModel.java index b9fc6923fb0..72b8c8145f4 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/models/JsonNodeModel.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/models/JsonNodeModel.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import org.kie.kogito.MapInput; import org.kie.kogito.MapInputId; @@ -28,6 +29,7 @@ import org.kie.kogito.MappableToModel; import org.kie.kogito.Model; import org.kie.kogito.jackson.utils.JsonObjectUtils; +import org.kie.kogito.jackson.utils.MergeUtils; import org.kie.kogito.jackson.utils.ObjectMapperFactory; import org.kie.kogito.serverless.workflow.SWFConstants; @@ -80,13 +82,34 @@ public JsonNodeModelOutput toModel() { @Override public void update(Map params) { - Map copy = mutableMap(params); - update((String) copy.remove("id"), copy); + update(params, w -> w); + } + + @Override + public Map updatePartially(Map params) { + update(params, w -> MergeUtils.merge(w, this.workflowdata)); + return toMap(); + } + + private void update(Map params, Function merger) { + if (params.containsKey(SWFConstants.DEFAULT_WORKFLOW_VAR)) { + params = mutableMap(params); + this.workflowdata = merger.apply(JsonObjectUtils.fromValue(params.remove(SWFConstants.DEFAULT_WORKFLOW_VAR))); + this.additionalProperties = params; + } else { + this.workflowdata = merger.apply(JsonObjectUtils.fromValue(params)); + this.additionalProperties = Collections.emptyMap(); + } + } + + private static Map mutableMap(Map map) { + return map instanceof HashMap ? map : new HashMap<>(map); } @Override public void fromMap(String id, Map params) { - update(id, mutableMap(params)); + this.id = id; + update(params); } @Override @@ -103,21 +126,6 @@ public Map toMap() { return map; } - private void update(String id, Map params) { - this.id = id; - if (params.containsKey(SWFConstants.DEFAULT_WORKFLOW_VAR)) { - this.workflowdata = JsonObjectUtils.fromValue(params.remove(SWFConstants.DEFAULT_WORKFLOW_VAR)).deepCopy(); - this.additionalProperties = params; - } else { - this.workflowdata = JsonObjectUtils.fromValue(params); - this.additionalProperties = Collections.emptyMap(); - } - } - - private static Map mutableMap(Map map) { - return map instanceof HashMap ? map : new HashMap<>(map); - } - @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/division.sw.json b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/division.sw.json new file mode 100644 index 00000000000..e6be4fecadc --- /dev/null +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/division.sw.json @@ -0,0 +1,26 @@ +{ + "id": "division", + "version": "1.0", + "name": "Workflow Expression example", + "start": "divide", + "functions": [ + { + "name": "divide", + "type": "expression", + "operation": ".number1 / .number2" + } + ], + "states": [ + { + "name": "divide", + "type": "operation", + "actions": [ + { + "name": "divide", + "functionRef": "divide" + } + ], + "end": true + } + ] +} diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/RetriggerIT.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/RetriggerIT.java new file mode 100644 index 00000000000..db4fad71f2d --- /dev/null +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/RetriggerIT.java @@ -0,0 +1,58 @@ +/* + * 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.quarkus.workflows; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.restassured.http.ContentType; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +@QuarkusIntegrationTest +class RetriggerIT { + + @Test + void testRetrigger() { + String id = given() + .contentType(ContentType.JSON) + .accept(ContentType.JSON) + .body("{\"number1\":2,\"number2\":0}").when() + .post("/division") + .then() + .statusCode(400) + .extract().path("id"); + + given() + .contentType(ContentType.JSON) + .accept(ContentType.JSON) + .body("{\"number2\":1}").when() + .patch("/division/{id}", id) + .then() + .statusCode(200); + + given() + .contentType(ContentType.JSON) + .accept(ContentType.JSON).when() + .post("/management/processes/division/instances/{id}/retrigger", id) + .then().statusCode(200).body("workflowdata.response", is(2)); + } + +}