Skip to content

Commit

Permalink
workflow java dsl: added support for error end nodes to complete proc…
Browse files Browse the repository at this point in the history
…essing with error
  • Loading branch information
mswiderski committed Sep 22, 2022
1 parent 7da1ae6 commit 4f4f344
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

package io.automatiko.engine.codegen.tests;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import io.automatiko.engine.api.Application;
import io.automatiko.engine.api.Model;
import io.automatiko.engine.api.definition.process.WorkflowProcess;
import io.automatiko.engine.api.workflow.Process;
import io.automatiko.engine.api.workflow.ProcessInstance;
import io.automatiko.engine.codegen.AbstractCodegenTest;
import io.automatiko.engine.codegen.LambdaParser;
import io.automatiko.engine.codegen.process.ProcessNodeLocator;
import io.automatiko.engine.workflow.AbstractProcess;
import io.automatiko.engine.workflow.builder.BuilderContext;
import io.automatiko.engine.workflow.builder.WorkflowBuilder;
import io.automatiko.engine.workflow.process.core.node.FaultNode;

public class ErrorEndAsCodeTest extends AbstractCodegenTest {

@BeforeAll
public static void prepare() {
LambdaParser.parseLambdas(
"src/test/java/" + ErrorEndAsCodeTest.class.getCanonicalName().replace(".", "/") + ".java",
md -> true);
}

@AfterAll
public static void clear() {

BuilderContext.clear();
}

@Test
public void testBasicErrorEndEvent() throws Exception {

WorkflowBuilder builder = WorkflowBuilder.newWorkflow("samples", "test workflow with error end event")
.dataObject("a", Integer.class)
.dataObject("b", String.class);

builder.start("start here").then().log("Log", "Running {} {}", "a", "b")
.then().endWithError("error").error("error", "409").expressionAsInput(String.class, () -> "error");
Application app = generateCode(List.of(builder.get()));
assertThat(app).isNotNull();

Process<? extends Model> p = app.processes().processById("samples");

Collection<FaultNode> errors = ProcessNodeLocator.findFaultNodes(((AbstractProcess<?>) p).process(),
((WorkflowProcess) ((AbstractProcess<?>) p).process()).getNodes()[0]);
assertThat(errors).hasSize(1);

Model m = p.createModel();
m.fromMap(Map.of("a", 1, "b", "test"));
ProcessInstance<?> processInstance = p.createInstance(m);
processInstance.start();

assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED);
assertThat(processInstance.abortCode()).isEqualTo("409");
assertThat(processInstance.abortData()).isEqualTo("error");

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.automatiko.engine.workflow.builder;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

import io.automatiko.engine.workflow.base.core.context.variable.Variable;
import io.automatiko.engine.workflow.process.core.Node;
import io.automatiko.engine.workflow.process.core.node.FaultNode;
import io.automatiko.engine.workflow.process.executable.core.Metadata;

/**
* Builder responsible for building an end and terminate node
*/
public class EndWithErrorNodeBuilder extends AbstractNodeBuilder {

private FaultNode node;

public EndWithErrorNodeBuilder(String name, boolean terminate, WorkflowBuilder workflowBuilder) {
super(workflowBuilder);
this.node = new FaultNode();

this.node.setId(ids.incrementAndGet());
this.node.setName(name);
this.node.setMetaData("UniqueId", generateUiqueId(this.node));
this.node.setTerminateParent(true);

this.node.setMetaData("functionFlowContinue", "true");

this.node.setMetaData(Metadata.DATA_INPUTS, new HashMap<>());

workflowBuilder.container().addNode(node);

contect();
}

public EndWithErrorNodeBuilder errorCode(String errorCode) {

this.node.setErrorName(this.node.getName());
this.node.setFaultName(errorCode);
return this;
}

public EndWithErrorNodeBuilder error(String errorName, String errorCode) {

this.node.setErrorName(errorName);
this.node.setFaultName(errorCode);
return this;
}

public EndWithErrorNodeBuilder fromDataObject(String name) {
Variable var = this.workflowBuilder.get().getVariableScope().findVariable(name);

if (var == null) {
throw new IllegalArgumentException("Cannot find data object with '" + name + "' name");
}
addToDataInputs(name, var.getType().getStringType());
this.node.setFaultVariable(name);

return this;
}

public <T> EndWithErrorNodeBuilder expressionAsInput(Class<T> type, Supplier<T> expression) {

this.node.setFaultVariable(
"#{" + BuilderContext.get(Thread.currentThread().getStackTrace()[2].getMethodName()).replace("\"", "\\\"")
+ "}");

return this;
}

/**
* Completes given workflow path and returns the builder
*
* @return the builder
*/
public WorkflowBuilder done() {
return workflowBuilder;
}

@Override
protected Node getNode() {
return this.node;
}

@SuppressWarnings("unchecked")
private void addToDataInputs(String name, String type) {
((Map<String, String>) this.node.getMetaData(Metadata.DATA_INPUTS)).put(name, type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,16 @@ public EndWithMessageNodeBuilder endWithMessage(String name) {
return new EndWithMessageNodeBuilder(name, false, this);
}

/**
* Adds an ending node with error
*
* @param name name of the node
* @return the builder
*/
public EndWithErrorNodeBuilder endWithError(String name) {
return new EndWithErrorNodeBuilder(name, false, this);
}

/**
* Adds an expression node (aka script) that will allow to invoke expressions
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

package io.automatiko.engine.workflow.process.instance.node;

import static io.automatiko.engine.workflow.base.core.context.variable.VariableScope.VARIABLE_SCOPE;
import static io.automatiko.engine.workflow.process.executable.core.Metadata.UNIQUE_ID;

import java.util.ArrayList;
Expand All @@ -10,6 +11,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -40,6 +42,7 @@
import io.automatiko.engine.workflow.process.instance.impl.NodeInstanceImpl;
import io.automatiko.engine.workflow.process.instance.impl.NodeInstanceResolverFactory;
import io.automatiko.engine.workflow.process.instance.impl.WorkflowProcessInstanceImpl;
import io.automatiko.engine.workflow.util.PatternConstants;

/**
* Runtime counterpart of a fault node.
Expand Down Expand Up @@ -194,17 +197,55 @@ protected Object getFaultData() {

} else if (faultVariable != null) {

VariableScopeInstance variableScopeInstance = (VariableScopeInstance) resolveContextInstance(
VariableScope.VARIABLE_SCOPE, faultVariable);
if (variableScopeInstance != null) {
value = variableScopeInstance.getVariable(faultVariable);
Map<String, Object> replacements = new HashMap<>();
Matcher matcher = PatternConstants.PARAMETER_MATCHER.matcher(faultVariable);
if (matcher.find()) {
String paramName = matcher.group(1);
String replacementKey = paramName;
String defaultValue = "";
if (paramName.contains(":")) {

String[] items = paramName.split(":");
paramName = items[0];
defaultValue = items[1];
}
if (replacements.get(replacementKey) == null) {
VariableScopeInstance variableScopeInstance = (VariableScopeInstance) resolveContextInstance(
VARIABLE_SCOPE, paramName);
if (variableScopeInstance != null) {
Object variableValue = variableScopeInstance.getVariable(paramName);
value = variableValue == null ? defaultValue : variableValue;
} else {
try {
ExpressionEvaluator evaluator = (ExpressionEvaluator) ((WorkflowProcess) getProcessInstance()
.getProcess())
.getDefaultContext(ExpressionEvaluator.EXPRESSION_EVALUATOR);
Object variableValue = evaluator.evaluate(paramName, new NodeInstanceResolverFactory(this));
value = variableValue == null ? defaultValue : variableValue;

} catch (Throwable t) {
logger.error("Could not find variable scope for variable {}", faultVariable);
logger.error("when trying to execute fault node {}", getFaultNode().getName());
logger.error("Continuing without setting value.");
}
}
}

} else {
logger.error("Could not find variable scope for variable {}", faultVariable);
logger.error("when trying to execute fault node {}", getFaultNode().getName());
logger.error("Continuing without setting value.");

VariableScopeInstance variableScopeInstance = (VariableScopeInstance) resolveContextInstance(
VariableScope.VARIABLE_SCOPE, faultVariable);
if (variableScopeInstance != null) {
value = variableScopeInstance.getVariable(faultVariable);
} else {
logger.error("Could not find variable scope for variable {}", faultVariable);
logger.error("when trying to execute fault node {}", getFaultNode().getName());
logger.error("Continuing without setting value.");
}
}
}
return value;

}

protected void handleException(String faultName, ExceptionScopeInstance exceptionScopeInstance) {
Expand Down

0 comments on commit 4f4f344

Please sign in to comment.