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

[incubator-kie-issues-1286] Adhoc functionality missing in kogito jBPM engine #3543

Merged
merged 7 commits into from
Jun 13, 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 @@ -63,17 +63,26 @@ protected Node handleNode(final Node node, final Element element, final String u
if ("false".equals(cancelRemainingInstances)) {
dynamicNode.setCancelRemainingInstances(false);
}

dynamicNode.setLanguage("http://www.java.com/java");

// by default it should not autocomplete as it's adhoc
org.w3c.dom.Node xmlNode = element.getFirstChild();
dynamicNode.setActivationCondition((String) node.getMetaData().get(CUSTOM_ACTIVATION_CONDITION));
while (xmlNode != null) {
String nodeName = xmlNode.getNodeName();
if (COMPLETION_CONDITION.equals(nodeName)) {
Element completeConditionElement = (Element) xmlNode;
String dialect = completeConditionElement.getAttribute("language");

String expression = xmlNode.getTextContent();
if (AUTOCOMPLETE_EXPRESSIONS.contains(expression)) {
dynamicNode.setAutoComplete(true);
} else {
dynamicNode.setCompletionCondition(expression);
if (!dialect.isBlank()) {
dynamicNode.setLanguage(dialect);
}
}
}
xmlNode = xmlNode.getNextSibling();
Expand Down Expand Up @@ -104,7 +113,7 @@ public void writeNode(Node node, StringBuilder xmlDump, int metaDataType) {
visitConnectionsAndAssociations(dynamicNode, xmlDump, metaDataType);

if (dynamicNode.isAutoComplete()) {
xmlDump.append(" <completionCondition xsi:type=\"tFormalExpression\">" + AUTOCOMPLETE_COMPLETION_CONDITION + "</completionCondition>" + EOL);
xmlDump.append("<completionCondition xsi:type=\"tFormalExpression\" language=\"" + dynamicNode.getLanguage() + "\">" + AUTOCOMPLETE_COMPLETION_CONDITION + "</completionCondition>" + EOL);
}
endNode("adHocSubProcess", xmlDump);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Collection;
import java.util.stream.Stream;

import org.jbpm.compiler.canonical.builtin.ReturnValueEvaluatorBuilderService;
import org.jbpm.compiler.canonical.node.NodeVisitorBuilderService;
import org.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.ruleflow.core.factory.DynamicNodeFactory;
Expand All @@ -35,8 +36,11 @@

public class DynamicNodeVisitor extends CompositeContextNodeVisitor<DynamicNode> {

public DynamicNodeVisitor(NodeVisitorBuilderService nodeVisitorService) {
private ReturnValueEvaluatorBuilderService builder;

public DynamicNodeVisitor(NodeVisitorBuilderService nodeVisitorService, ClassLoader classLoader) {
super(nodeVisitorService);
this.builder = ReturnValueEvaluatorBuilderService.instance(classLoader);
}

@Override
Expand All @@ -56,22 +60,23 @@ protected String getDefaultName() {

@Override
public Stream<MethodCallExpr> visitCustomFields(DynamicNode node, VariableScope variableScope) {

Collection<MethodCallExpr> methods = new ArrayList<>();
methods.add(getFactoryMethod(getNodeId(node), METHOD_LANGUAGE, getOrNullExpr(node.getLanguage())));
if (node.getActivationCondition() != null && !node.getActivationCondition().trim().isEmpty()) {
methods.add(getActivationConditionStatement(node, variableScope));
if (node.getActivationCondition() != null && !node.getActivationCondition().isBlank()) {
methods.add(getActivationConditionStatement(node));
}
if (node.getCompletionCondition() != null && !node.getCompletionCondition().trim().isEmpty()) {
methods.add(getCompletionConditionStatement(node, variableScope));
if (!node.isAutoComplete() && node.getCompletionCondition() != null && !node.getCompletionCondition().isBlank()) {
methods.add(getCompletionConditionStatement(node));
}
return methods.stream();
}

private MethodCallExpr getActivationConditionStatement(DynamicNode node, VariableScope scope) {
return getFactoryMethod(getNodeId(node), METHOD_ACTIVATION_EXPRESSION, createLambdaExpr(node.getActivationCondition(), scope));
private MethodCallExpr getActivationConditionStatement(DynamicNode node) {
return getFactoryMethod(getNodeId(node), METHOD_ACTIVATION_EXPRESSION, builder.build(node, node.getLanguage(), node.getActivationCondition(), Boolean.class, (String) null));
}

private MethodCallExpr getCompletionConditionStatement(DynamicNode node, VariableScope scope) {
return getFactoryMethod(getNodeId(node), METHOD_COMPLETION_EXPRESSION, createLambdaExpr(node.getCompletionCondition(), scope));
private MethodCallExpr getCompletionConditionStatement(DynamicNode node) {
return getFactoryMethod(getNodeId(node), METHOD_COMPLETION_EXPRESSION, builder.build(node, node.getLanguage(), node.getCompletionCondition(), Boolean.class, (String) null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
import org.jbpm.process.core.context.variable.Variable;
import org.jbpm.process.core.context.variable.VariableScope;

import com.github.javaparser.ParseProblemException;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.LambdaExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.ast.type.UnknownType;

public class JavaConstraintEvaluatorBuilder implements ReturnValueEvaluatorBuilder {
Expand All @@ -48,7 +50,20 @@ public Expression build(ContextResolver resolver, String expression, Class<?> ty
new Parameter(new UnknownType(), KCONTEXT_VAR), // (kcontext) ->
actionBody);

BlockStmt blockStmt = StaticJavaParser.parseBlock("{" + expression + "}");
BlockStmt blockStmt = parseIdentifier(expression);

if (blockStmt == null) {
blockStmt = parseExpression(expression);
}

if (blockStmt == null) {
blockStmt = parseStatement(expression);
}

if (blockStmt == null) {
blockStmt = StaticJavaParser.parseBlock("{" + expression + "}");
}

Set<NameExpr> identifiers = new HashSet<>(blockStmt.findAll(NameExpr.class));

for (NameExpr v : identifiers) {
Expand All @@ -57,12 +72,41 @@ public Expression build(ContextResolver resolver, String expression, Class<?> ty
continue;
}
Variable variable = variableScope.findVariable(v.getNameAsString());
actionBody.addStatement(AbstractNodeVisitor.makeAssignment(variable));
actionBody.addStatement(0, AbstractNodeVisitor.makeAssignment(variable));
}

blockStmt.getStatements().forEach(actionBody::addStatement);

return lambda;
}

private BlockStmt parseStatement(String expression) {
try {
BlockStmt block = new BlockStmt();
block.addStatement(StaticJavaParser.parseStatement(expression));
return block;
} catch (ParseProblemException e) {
return null;
}
}

private BlockStmt parseExpression(String expression) {
try {
BlockStmt block = new BlockStmt();
block.addStatement(new ReturnStmt(StaticJavaParser.parseExpression(expression)));
return block;
} catch (ParseProblemException e) {
return null;
}
}

private BlockStmt parseIdentifier(String expression) {
try {
BlockStmt block = new BlockStmt();
block.addStatement(new ReturnStmt(new NameExpr(StaticJavaParser.parseSimpleName(expression.trim()))));
return block;
} catch (ParseProblemException e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public Class<?> type() {

@Override
public AbstractNodeVisitor<? extends Node> visitor(NodeVisitorBuilderService nodeVisitorService, ClassLoader classLoader) {
return new DynamicNodeVisitor(nodeVisitorService);
return new DynamicNodeVisitor(nodeVisitorService, classLoader);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@
*/
package org.jbpm.ruleflow.core.factory;

import java.util.function.Predicate;

import org.jbpm.process.instance.impl.ReturnValueEvaluator;
import org.jbpm.ruleflow.core.RuleFlowNodeContainerFactory;
import org.jbpm.workflow.core.NodeContainer;
import org.jbpm.workflow.core.node.DynamicNode;
import org.kie.api.definition.process.WorkflowElementIdentifier;
import org.kie.api.runtime.process.ProcessContext;

public class DynamicNodeFactory<T extends RuleFlowNodeContainerFactory<T, ?>> extends AbstractCompositeNodeFactory<DynamicNodeFactory<T>, T> {

Expand All @@ -45,12 +43,12 @@ public DynamicNodeFactory<T> language(String language) {
return this;
}

public DynamicNodeFactory<T> activationExpression(Predicate<ProcessContext> activationExpression) {
public DynamicNodeFactory<T> activationExpression(ReturnValueEvaluator activationExpression) {
getDynamicNode().setActivationExpression(activationExpression);
return this;
}

public DynamicNodeFactory<T> completionExpression(Predicate<ProcessContext> completionExpression) {
public DynamicNodeFactory<T> completionExpression(ReturnValueEvaluator completionExpression) {
getDynamicNode().setCompletionExpression(completionExpression);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.jbpm.process.instance.impl.ReturnValueEvaluator;
import org.kie.api.definition.process.Node;
import org.kie.api.definition.process.WorkflowElementIdentifier;
import org.kie.api.runtime.process.ProcessContext;
import org.kie.kogito.internal.process.runtime.KogitoProcessContext;

import static org.jbpm.ruleflow.core.Metadata.CUSTOM_AUTO_START;

Expand All @@ -42,8 +42,8 @@ public class DynamicNode extends CompositeContextNode {
*/
private String completionCondition;

private Predicate<ProcessContext> activationPredicate;
private Predicate<ProcessContext> completionPredicate;
private ReturnValueEvaluator activationPredicate;
private ReturnValueEvaluator completionPredicate;
private String language;

public DynamicNode() {
Expand Down Expand Up @@ -89,22 +89,22 @@ public void setCompletionCondition(String completionCondition) {
this.completionCondition = completionCondition;
}

public DynamicNode setActivationExpression(Predicate<ProcessContext> activationPredicate) {
public DynamicNode setActivationExpression(ReturnValueEvaluator activationPredicate) {
this.activationPredicate = activationPredicate;
return this;
}

public DynamicNode setCompletionExpression(Predicate<ProcessContext> copmletionPredicate) {
public DynamicNode setCompletionExpression(ReturnValueEvaluator copmletionPredicate) {
this.completionPredicate = copmletionPredicate;
return this;
}

public boolean canActivate(ProcessContext context) {
return activationPredicate == null || activationPredicate.test(context);
public boolean canActivate(KogitoProcessContext context) {
return activationPredicate == null || (Boolean) activationPredicate.evaluate(context);
}

public boolean canComplete(ProcessContext context) {
return isAutoComplete() || (completionPredicate != null && completionPredicate.test(context));
public boolean canComplete(KogitoProcessContext context) {
return isAutoComplete() || (completionPredicate != null && (Boolean) completionPredicate.evaluate(context));
}

public boolean hasCompletionCondition() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@

import java.util.Collection;
import java.util.Map;
import java.util.function.Predicate;

import org.jbpm.workflow.core.node.AsyncEventNode;
import org.kie.api.definition.process.Node;
import org.kie.api.definition.process.NodeContainer;
import org.kie.api.definition.process.WorkflowElementIdentifier;
import org.kie.kogito.internal.process.runtime.KogitoNode;
import org.kie.kogito.internal.process.runtime.KogitoNodeInstance;
import org.kie.kogito.internal.process.runtime.KogitoNodeInstanceContainer;

Expand Down Expand Up @@ -61,12 +63,11 @@ public interface NodeInstanceContainer extends KogitoNodeInstanceContainer {

NodeInstance getNodeInstance(String nodeInstanceId, boolean recursive);

default NodeInstance getByNodeDefinitionId(final String nodeDefinitionId, NodeContainer nodeContainer) {
default NodeInstance getNodeByPredicate(NodeContainer nodeContainer, Predicate<KogitoNode> nodeTest) {
for (Node node : nodeContainer.getNodes()) {

if (nodeDefinitionId.equals(node.getUniqueId())) {
if (nodeTest.test((KogitoNode) node)) {
if (nodeContainer instanceof Node) {
Collection<KogitoNodeInstance> nodeInstances = getKogitoNodeInstances(ni -> ni.getNode().getId() == (((Node) nodeContainer).getId()), true);
Collection<KogitoNodeInstance> nodeInstances = getKogitoNodeInstances(ni -> ni.getNode().getId().equals(((Node) nodeContainer).getId()), true);
if (nodeInstances.isEmpty()) {
return ((NodeInstanceContainer) getNodeInstance((Node) nodeContainer)).getNodeInstance(node);
} else {
Expand All @@ -78,15 +79,21 @@ default NodeInstance getByNodeDefinitionId(final String nodeDefinitionId, NodeCo
}

if (node instanceof NodeContainer) {
NodeInstance ni = getByNodeDefinitionId(nodeDefinitionId, ((NodeContainer) node));

NodeInstance ni = getNodeByPredicate(((NodeContainer) node), nodeTest);
if (ni != null) {
return ni;
}
}
}
return null;
}

throw new IllegalArgumentException("Node with definition id " + nodeDefinitionId + " was not found");
default NodeInstance getByNodeDefinitionId(String nodeDefinitionId, NodeContainer nodeContainer) {
NodeInstance nodeInstance = getNodeByPredicate(nodeContainer, ni -> nodeDefinitionId.equals(ni.getUniqueId()));
if (nodeInstance == null) {
throw new IllegalArgumentException("Node with definition id " + nodeDefinitionId + " was not found");
}
return nodeInstance;
}

default Node resolveAsync(Node node) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1228,4 +1228,5 @@ public final void wrap(ProcessInstance<?> kogitoProcessInstance) {
public final ProcessInstance<?> unwrap() {
return this.kogitoProcessInstance;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import org.jbpm.workflow.core.Node;
import org.jbpm.workflow.core.WorkflowProcess;
import org.jbpm.workflow.instance.NodeInstance;
import org.jbpm.workflow.instance.NodeInstanceContainer;
import org.jbpm.workflow.instance.WorkflowProcessInstance;
import org.jbpm.workflow.instance.impl.NodeInstanceImpl;
import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl;
Expand Down Expand Up @@ -441,17 +440,13 @@ public void triggerNode(String nodeId) {
WorkflowProcessInstance wfpi = processInstance();
RuleFlowProcess rfp = ((RuleFlowProcess) wfpi.getProcess());

org.kie.api.definition.process.Node node = rfp.getNodesRecursively()
.stream()
.filter(ni -> Objects.equals(nodeId, ni.getUniqueId()) || Objects.equals(nodeId, ni.getName()) || Objects.equals(nodeId, ni.getId().toExternalFormat()))
.findFirst()
.orElseThrow(() -> new NodeNotFoundException(this.id, nodeId));

org.kie.api.definition.process.Node parentNode = rfp.getParentNode(node.getId());

NodeInstanceContainer nodeInstanceContainerNode = parentNode == null ? wfpi : ((NodeInstanceContainer) wfpi.getNodeInstance(parentNode));

nodeInstanceContainerNode.getNodeInstance(node).trigger(null, Node.CONNECTION_DEFAULT_TYPE);
// we avoid create containers incorrectly
NodeInstance nodeInstance = wfpi.getNodeByPredicate(rfp,
ni -> Objects.equals(nodeId, ni.getName()) || Objects.equals(nodeId, ni.getId().toExternalFormat()));
if (nodeInstance == null) {
throw new NodeNotFoundException(this.id, nodeId);
}
nodeInstance.trigger(null, Node.CONNECTION_DEFAULT_TYPE);

addToUnitOfWork(pi -> ((MutableProcessInstances<T>) process.instances()).update(pi.id(), pi));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private NodeInstance givenExistingNode(String nodeId) {
when(process.getNodesRecursively()).thenReturn(Arrays.asList(node));

NodeInstance nodeInstance = mock(NodeInstance.class);
when(wpi.getNodeInstance(node)).thenReturn(nodeInstance);
when(wpi.getNodeByPredicate(any(), any())).thenReturn(nodeInstance);
return nodeInstance;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:tns="http://www.jboss.org/drools">

<process processType="Private" isExecutable="true" id="AdHocSubProcess" name="AdHoc SubProcess" >
<process processType="Private" isExecutable="true" id="AdHocSubProcessAutoComplete" name="AdHoc SubProcess" tns:packageName="org.jbpm.bpmn2.adhoc" >

<!-- nodes -->
<startEvent id="_1" name="StartProcess" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
xmlns:tns="http://www.jboss.org/drools">

<itemDefinition id="_testItem" structureRef="Integer" />
<process processType="Private" isExecutable="true" id="AdHocSubProcess" name="AdHoc SubProcess" >
<process processType="Private" isExecutable="true" id="AdHocSubProcessAutoCompleteExpression" name="AdHoc SubProcess" tns:packageName="org.jbpm.bpmn2.adhoc" >
<!-- process variables -->
<property id="counter" itemSubjectRef="_testItem"/>
<!-- nodes -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
xmlns:tns="http://www.jboss.org/drools">

<itemDefinition id="_testItem" structureRef="Integer" />
<process processType="Private" isExecutable="true" id="AdHocSubProcess" name="AdHoc SubProcess" >
<process processType="Private" isExecutable="true" id="AdHocSubProcessEmptyCompleteExpression" name="AdHoc SubProcess" tns:packageName="org.jbpm.bpmn2.adhoc">
<!-- process variables -->
<property id="counter" itemSubjectRef="_testItem"/>
<!-- nodes -->
Expand Down
Loading
Loading