From 0e3a8899c0207e914fc32abd3fa8f2103c84977a Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Fri, 22 Nov 2024 17:06:40 +0100 Subject: [PATCH] [Fix #466] Implement switch Signed-off-by: Francisco Javier Tirado Sarti --- .github/workflows/maven-verify.yml | 4 +- .github/workflows/release.yml | 6 +- .../serverlessworkflow/impl/TaskContext.java | 20 +- .../impl/WorkflowApplication.java | 148 +++++++++++++ .../impl/WorkflowContext.java | 31 ++- .../impl/WorkflowDefinition.java | 209 +++++++----------- .../impl/WorkflowFactories.java | 56 ----- .../impl/WorkflowInstance.java | 52 +++++ .../impl/WorkflowState.java | 7 + .../impl/WorkflowUtils.java | 90 +++++++- .../impl/executors/AbstractTaskExecutor.java | 41 ++-- .../executors/DefaultTaskExecutorFactory.java | 13 +- .../impl/executors/DoExecutor.java | 34 +++ .../impl/executors/HttpExecutor.java | 20 +- .../impl/executors/SetExecutor.java | 39 ++++ .../impl/executors/SwitchExecutor.java | 50 +++++ .../impl/executors/TaskExecutor.java | 3 +- .../impl/executors/TaskExecutorFactory.java | 4 +- .../impl/WorkflowDefinitionTest.java | 54 ++++- .../call-http-endpoint-interpolation.yaml | 2 +- ...http-query-parameters-external-schema.yaml | 2 +- impl/src/test/resources/callGetHttp.yaml | 4 +- impl/src/test/resources/callPostHttp.yaml | 2 +- .../test/resources/switch-then-string.yaml | 45 ++++ pom.xml | 2 +- 25 files changed, 680 insertions(+), 258 deletions(-) create mode 100644 impl/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java delete mode 100644 impl/src/main/java/io/serverlessworkflow/impl/WorkflowFactories.java create mode 100644 impl/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java create mode 100644 impl/src/main/java/io/serverlessworkflow/impl/WorkflowState.java create mode 100644 impl/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java create mode 100644 impl/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java create mode 100644 impl/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java create mode 100644 impl/src/test/resources/switch-then-string.yaml diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml index 1b7d432c..12ab91f8 100644 --- a/.github/workflows/maven-verify.yml +++ b/.github/workflows/maven-verify.yml @@ -16,11 +16,11 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: temurin - java-version: 11 + java-version: 17 cache: 'maven' - name: Verify with Maven diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef4ee698..99b13727 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,11 +29,11 @@ jobs: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: temurin - java-version: 11 + java-version: 17 cache: 'maven' server-id: ossrh server-username: MAVEN_USERNAME @@ -57,4 +57,4 @@ jobs: MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} - name: Push tags - run: git push && git push --tags \ No newline at end of file + run: git push && git push --tags diff --git a/impl/src/main/java/io/serverlessworkflow/impl/TaskContext.java b/impl/src/main/java/io/serverlessworkflow/impl/TaskContext.java index c9e28f12..91f6aa61 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/TaskContext.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/TaskContext.java @@ -16,6 +16,8 @@ package io.serverlessworkflow.impl; import com.fasterxml.jackson.databind.JsonNode; +import io.serverlessworkflow.api.types.FlowDirective; +import io.serverlessworkflow.api.types.FlowDirectiveEnum; import io.serverlessworkflow.api.types.TaskBase; public class TaskContext { @@ -26,11 +28,15 @@ public class TaskContext { private JsonNode input; private JsonNode output; private JsonNode rawOutput; + private FlowDirective flowDirective; public TaskContext(JsonNode rawInput, T task) { this.rawInput = rawInput; this.input = rawInput; + this.rawOutput = rawInput; + this.output = rawInput; this.task = task; + this.flowDirective = task.getThen(); } public void input(JsonNode input) { @@ -54,6 +60,10 @@ public void rawOutput(JsonNode output) { this.output = output; } + public JsonNode rawOutput() { + return rawOutput; + } + public void output(JsonNode output) { this.output = output; } @@ -62,7 +72,13 @@ public JsonNode output() { return output; } - public JsonNode rawOutput() { - return rawOutput; + public void flowDirective(FlowDirective flowDirective) { + this.flowDirective = flowDirective; + } + + public FlowDirective flowDirective() { + return flowDirective == null + ? new FlowDirective().withFlowDirectiveEnum(FlowDirectiveEnum.CONTINUE) + : flowDirective; } } diff --git a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java new file mode 100644 index 00000000..3fd81b00 --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -0,0 +1,148 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed 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 io.serverlessworkflow.impl; + +import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.executors.DefaultTaskExecutorFactory; +import io.serverlessworkflow.impl.executors.TaskExecutorFactory; +import io.serverlessworkflow.impl.expressions.ExpressionFactory; +import io.serverlessworkflow.impl.expressions.JQExpressionFactory; +import io.serverlessworkflow.impl.jsonschema.DefaultSchemaValidatorFactory; +import io.serverlessworkflow.impl.jsonschema.SchemaValidatorFactory; +import io.serverlessworkflow.resources.DefaultResourceLoaderFactory; +import io.serverlessworkflow.resources.ResourceLoaderFactory; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class WorkflowApplication implements AutoCloseable { + + private final TaskExecutorFactory taskFactory; + private final ExpressionFactory exprFactory; + private final ResourceLoaderFactory resourceLoaderFactory; + private final SchemaValidatorFactory schemaValidatorFactory; + private final Collection listeners; + private final Map definitions; + + public WorkflowApplication( + TaskExecutorFactory taskFactory, + ExpressionFactory exprFactory, + ResourceLoaderFactory resourceLoaderFactory, + SchemaValidatorFactory schemaValidatorFactory, + Collection listeners) { + this.taskFactory = taskFactory; + this.exprFactory = exprFactory; + this.resourceLoaderFactory = resourceLoaderFactory; + this.schemaValidatorFactory = schemaValidatorFactory; + this.listeners = listeners; + this.definitions = new ConcurrentHashMap<>(); + } + + public TaskExecutorFactory taskFactory() { + return taskFactory; + } + + public static Builder builder() { + return new Builder(); + } + + public ExpressionFactory expressionFactory() { + return exprFactory; + } + + public SchemaValidatorFactory validatorFactory() { + return schemaValidatorFactory; + } + + public ResourceLoaderFactory resourceLoaderFactory() { + return resourceLoaderFactory; + } + + public Collection listeners() { + return listeners; + } + + public static class Builder { + private TaskExecutorFactory taskFactory = DefaultTaskExecutorFactory.get(); + private ExpressionFactory exprFactory = JQExpressionFactory.get(); + private Collection listeners; + private ResourceLoaderFactory resourceLoaderFactory = DefaultResourceLoaderFactory.get(); + private SchemaValidatorFactory schemaValidatorFactory = DefaultSchemaValidatorFactory.get(); + + private Builder() {} + + public Builder withListener(WorkflowExecutionListener listener) { + if (listeners == null) { + listeners = new HashSet<>(); + } + listeners.add(listener); + return this; + } + + public Builder withTaskExecutorFactory(TaskExecutorFactory factory) { + this.taskFactory = factory; + return this; + } + + public Builder withExpressionFactory(ExpressionFactory factory) { + this.exprFactory = factory; + return this; + } + + public Builder withResourceLoaderFactory(ResourceLoaderFactory resourceLoader) { + this.resourceLoaderFactory = resourceLoader; + return this; + } + + public Builder withSchemaValidatorFactory(SchemaValidatorFactory factory) { + this.schemaValidatorFactory = factory; + return this; + } + + public WorkflowApplication build() { + return new WorkflowApplication( + taskFactory, + exprFactory, + resourceLoaderFactory, + schemaValidatorFactory, + listeners == null + ? Collections.emptySet() + : Collections.unmodifiableCollection(listeners)); + } + } + + private static record WorkflowId(String namespace, String name, String version) { + static WorkflowId of(Document document) { + return new WorkflowId(document.getNamespace(), document.getName(), document.getVersion()); + } + } + + public WorkflowDefinition workflowDefinition(Workflow workflow) { + return definitions.computeIfAbsent( + WorkflowId.of(workflow.getDocument()), k -> WorkflowDefinition.of(this, workflow)); + } + + @Override + public void close() throws Exception { + for (WorkflowDefinition definition : definitions.values()) { + definition.close(); + } + definitions.clear(); + } +} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java index de88d2d0..4f0f0f16 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java @@ -20,24 +20,31 @@ public class WorkflowContext { private final WorkflowPosition position; - private JsonNode context; + private final WorkflowDefinition definition; private final JsonNode input; + private JsonNode current; + private JsonNode context; - private WorkflowContext(WorkflowPosition position, JsonNode input) { + private WorkflowContext( + WorkflowPosition position, WorkflowDefinition definition, JsonNode input) { this.position = position; + this.definition = definition; this.input = input; + this.current = input.deepCopy(); this.context = JsonUtils.mapper().createObjectNode(); } - public static Builder builder(JsonNode input) { - return new Builder(input); + public static Builder builder(WorkflowDefinition definition, JsonNode input) { + return new Builder(definition, input); } public static class Builder { private WorkflowPosition position = new DefaultWorkflowPosition(); + private WorkflowDefinition definition; private JsonNode input; - private Builder(JsonNode input) { + private Builder(WorkflowDefinition definition, JsonNode input) { + this.definition = definition; this.input = input; } @@ -47,7 +54,7 @@ public Builder position(WorkflowPosition position) { } public WorkflowContext build() { - return new WorkflowContext(position, input); + return new WorkflowContext(position, definition, input); } } @@ -66,4 +73,16 @@ public void context(JsonNode context) { public JsonNode rawInput() { return input; } + + public void current(JsonNode output) { + this.current = output; + } + + public JsonNode current() { + return current; + } + + public WorkflowDefinition definition() { + return definition; + } } diff --git a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java index afcebeba..136d2853 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java @@ -16,190 +16,133 @@ package io.serverlessworkflow.impl; import static io.serverlessworkflow.impl.WorkflowUtils.*; -import static io.serverlessworkflow.impl.json.JsonUtils.*; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.Input; import io.serverlessworkflow.api.types.Output; import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.api.types.Workflow; -import io.serverlessworkflow.impl.executors.DefaultTaskExecutorFactory; import io.serverlessworkflow.impl.executors.TaskExecutor; import io.serverlessworkflow.impl.executors.TaskExecutorFactory; import io.serverlessworkflow.impl.expressions.ExpressionFactory; -import io.serverlessworkflow.impl.expressions.JQExpressionFactory; import io.serverlessworkflow.impl.json.JsonUtils; -import io.serverlessworkflow.impl.jsonschema.DefaultSchemaValidatorFactory; import io.serverlessworkflow.impl.jsonschema.SchemaValidator; import io.serverlessworkflow.impl.jsonschema.SchemaValidatorFactory; -import io.serverlessworkflow.resources.DefaultResourceLoaderFactory; -import io.serverlessworkflow.resources.ResourceLoaderFactory; +import io.serverlessworkflow.resources.ResourceLoader; import java.nio.file.Path; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -public class WorkflowDefinition { +public class WorkflowDefinition implements AutoCloseable { + + private final Workflow workflow; + private final Collection listeners; + private Optional inputSchemaValidator = Optional.empty(); + private Optional outputSchemaValidator = Optional.empty(); + private Optional inputFilter = Optional.empty(); + private Optional outputFilter = Optional.empty(); + private final TaskExecutorFactory taskFactory; + private final ExpressionFactory exprFactory; + private final ResourceLoader resourceLoader; + private final SchemaValidatorFactory schemaValidatorFactory; + private final Map> taskExecutors = + new ConcurrentHashMap<>(); private WorkflowDefinition( Workflow workflow, Collection listeners, - WorkflowFactories factories) { + TaskExecutorFactory taskFactory, + ResourceLoader resourceLoader, + ExpressionFactory exprFactory, + SchemaValidatorFactory schemaValidatorFactory) { this.workflow = workflow; this.listeners = listeners; - this.factories = factories; + this.taskFactory = taskFactory; + this.exprFactory = exprFactory; + this.schemaValidatorFactory = schemaValidatorFactory; + this.resourceLoader = resourceLoader; if (workflow.getInput() != null) { Input input = workflow.getInput(); this.inputSchemaValidator = getSchemaValidator( - factories.getValidatorFactory(), schemaToNode(factories, input.getSchema())); - this.inputFilter = buildWorkflowFilter(factories.getExpressionFactory(), input.getFrom()); + schemaValidatorFactory, schemaToNode(resourceLoader, input.getSchema())); + this.inputFilter = buildWorkflowFilter(exprFactory, input.getFrom()); } if (workflow.getOutput() != null) { Output output = workflow.getOutput(); this.outputSchemaValidator = getSchemaValidator( - factories.getValidatorFactory(), schemaToNode(factories, output.getSchema())); - this.outputFilter = buildWorkflowFilter(factories.getExpressionFactory(), output.getAs()); + schemaValidatorFactory, schemaToNode(resourceLoader, output.getSchema())); + this.outputFilter = buildWorkflowFilter(exprFactory, output.getAs()); } } - private final Workflow workflow; - private final Collection listeners; - private final WorkflowFactories factories; - private Optional inputSchemaValidator = Optional.empty(); - private Optional outputSchemaValidator = Optional.empty(); - private Optional inputFilter = Optional.empty(); - private Optional outputFilter = Optional.empty(); + static WorkflowDefinition of(WorkflowApplication application, Workflow workflow) { + return of(application, workflow, null); + } - private final Map> taskExecutors = - new ConcurrentHashMap<>(); + static WorkflowDefinition of(WorkflowApplication application, Workflow workflow, Path path) { + return new WorkflowDefinition( + workflow, + application.listeners(), + application.taskFactory(), + application.resourceLoaderFactory().getResourceLoader(path), + application.expressionFactory(), + application.validatorFactory()); + } - public static class Builder { - private final Workflow workflow; - private TaskExecutorFactory taskFactory = DefaultTaskExecutorFactory.get(); - private ExpressionFactory exprFactory = JQExpressionFactory.get(); - private Collection listeners; - private ResourceLoaderFactory resourceLoaderFactory = DefaultResourceLoaderFactory.get(); - private SchemaValidatorFactory schemaValidatorFactory = DefaultSchemaValidatorFactory.get(); - private Path path; - - private Builder(Workflow workflow) { - this.workflow = workflow; - } + public WorkflowInstance execute(Object input) { + return new WorkflowInstance(this, JsonUtils.fromValue(input)); + } - public Builder withListener(WorkflowExecutionListener listener) { - if (listeners == null) { - listeners = new HashSet<>(); - } - listeners.add(listener); - return this; - } + public Optional inputSchemaValidator() { + return inputSchemaValidator; + } - public Builder withTaskExecutorFactory(TaskExecutorFactory factory) { - this.taskFactory = factory; - return this; - } + public Optional inputFilter() { + return inputFilter; + } - public Builder withExpressionFactory(ExpressionFactory factory) { - this.exprFactory = factory; - return this; - } + public Workflow workflow() { + return workflow; + } - public Builder withPath(Path path) { - this.path = path; - return this; - } + public Collection listeners() { + return listeners; + } - public Builder withResourceLoaderFactory(ResourceLoaderFactory resourceLoader) { - this.resourceLoaderFactory = resourceLoader; - return this; - } + public Map> taskExecutors() { + return taskExecutors; + } - public Builder withSchemaValidatorFactory(SchemaValidatorFactory factory) { - this.schemaValidatorFactory = factory; - return this; - } + public TaskExecutorFactory taskFactory() { + return taskFactory; + } - public WorkflowDefinition build() { - WorkflowDefinition def = - new WorkflowDefinition( - workflow, - listeners == null - ? Collections.emptySet() - : Collections.unmodifiableCollection(listeners), - new WorkflowFactories( - taskFactory, - resourceLoaderFactory.getResourceLoader(path), - exprFactory, - schemaValidatorFactory)); - return def; - } + public Optional outputFilter() { + return outputFilter; } - public static Builder builder(Workflow workflow) { - return new Builder(workflow); + public Optional outputSchemaValidator() { + return outputSchemaValidator; } - public WorkflowInstance execute(Object input) { - return new WorkflowInstance(JsonUtils.fromValue(input)); - } - - enum State { - STARTED, - WAITING, - FINISHED - }; - - public class WorkflowInstance { - - private JsonNode output; - private State state; - private WorkflowContext context; - - private WorkflowInstance(JsonNode input) { - this.output = input; - inputSchemaValidator.ifPresent(v -> v.validate(input)); - this.context = WorkflowContext.builder(input).build(); - inputFilter.ifPresent(f -> output = f.apply(context, Optional.empty(), output)); - this.state = State.STARTED; - processDo(workflow.getDo()); - outputFilter.ifPresent(f -> output = f.apply(context, Optional.empty(), output)); - outputSchemaValidator.ifPresent(v -> v.validate(output)); - } + public ExpressionFactory expressionFactory() { + return exprFactory; + } - private void processDo(List tasks) { - context.position().addProperty("do"); - int index = 0; - for (TaskItem task : tasks) { - context.position().addIndex(++index).addProperty(task.getName()); - listeners.forEach(l -> l.onTaskStarted(context.position(), task.getTask())); - this.output = - taskExecutors - .computeIfAbsent( - context.position().jsonPointer(), - k -> factories.getTaskFactory().getTaskExecutor(task.getTask(), factories)) - .apply(context, output); - listeners.forEach(l -> l.onTaskEnded(context.position(), task.getTask())); - context.position().back().back(); - } - } + public SchemaValidatorFactory validatorFactory() { + return schemaValidatorFactory; + } - public State state() { - return state; - } + public ResourceLoader resourceLoader() { - public Object output() { - return toJavaValue(output); - } + return resourceLoader; + } - public Object outputAsJsonNode() { - return output; - } + @Override + public void close() { + // TODO close resourcers hold for uncompleted process instances, if any } } diff --git a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowFactories.java b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowFactories.java deleted file mode 100644 index 6b0408b5..00000000 --- a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowFactories.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed 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 io.serverlessworkflow.impl; - -import io.serverlessworkflow.impl.executors.TaskExecutorFactory; -import io.serverlessworkflow.impl.expressions.ExpressionFactory; -import io.serverlessworkflow.impl.jsonschema.SchemaValidatorFactory; -import io.serverlessworkflow.resources.ResourceLoader; - -public class WorkflowFactories { - - private final TaskExecutorFactory taskFactory; - private final ResourceLoader resourceLoader; - private final ExpressionFactory expressionFactory; - private final SchemaValidatorFactory validatorFactory; - - public WorkflowFactories( - TaskExecutorFactory taskFactory, - ResourceLoader resourceLoader, - ExpressionFactory expressionFactory, - SchemaValidatorFactory validatorFactory) { - this.taskFactory = taskFactory; - this.resourceLoader = resourceLoader; - this.expressionFactory = expressionFactory; - this.validatorFactory = validatorFactory; - } - - public TaskExecutorFactory getTaskFactory() { - return taskFactory; - } - - public ResourceLoader getResourceLoader() { - return resourceLoader; - } - - public ExpressionFactory getExpressionFactory() { - return expressionFactory; - } - - public SchemaValidatorFactory getValidatorFactory() { - return validatorFactory; - } -} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java new file mode 100644 index 00000000..bd2f94b8 --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java @@ -0,0 +1,52 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed 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 io.serverlessworkflow.impl; + +import static io.serverlessworkflow.impl.json.JsonUtils.toJavaValue; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.Optional; + +public class WorkflowInstance { + private WorkflowState state; + private WorkflowContext context; + + WorkflowInstance(WorkflowDefinition definition, JsonNode input) { + definition.inputSchemaValidator().ifPresent(v -> v.validate(input)); + context = WorkflowContext.builder(definition, input).build(); + definition + .inputFilter() + .ifPresent(f -> context.current(f.apply(context, Optional.empty(), context.current()))); + state = WorkflowState.STARTED; + WorkflowUtils.processTaskList(definition.workflow().getDo(), context); + definition + .outputFilter() + .ifPresent(f -> context.current(f.apply(context, Optional.empty(), context.current()))); + definition.outputSchemaValidator().ifPresent(v -> v.validate(context.current())); + } + + public WorkflowState state() { + return state; + } + + public Object output() { + return toJavaValue(context.current()); + } + + public Object outputAsJsonNode() { + return context.current(); + } +} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowState.java b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowState.java new file mode 100644 index 00000000..939fd1e8 --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowState.java @@ -0,0 +1,7 @@ +package io.serverlessworkflow.impl; + +public enum WorkflowState { + STARTED, + WAITING, + COMPLETED +} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java index 787d17dc..47191272 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -19,21 +19,27 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.serverlessworkflow.api.WorkflowFormat; import io.serverlessworkflow.api.types.ExportAs; +import io.serverlessworkflow.api.types.FlowDirective; import io.serverlessworkflow.api.types.InputFrom; import io.serverlessworkflow.api.types.OutputAs; import io.serverlessworkflow.api.types.SchemaExternal; import io.serverlessworkflow.api.types.SchemaInline; import io.serverlessworkflow.api.types.SchemaUnion; +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.impl.expressions.Expression; import io.serverlessworkflow.impl.expressions.ExpressionFactory; import io.serverlessworkflow.impl.expressions.ExpressionUtils; import io.serverlessworkflow.impl.json.JsonUtils; import io.serverlessworkflow.impl.jsonschema.SchemaValidator; import io.serverlessworkflow.impl.jsonschema.SchemaValidatorFactory; +import io.serverlessworkflow.resources.ResourceLoader; import io.serverlessworkflow.resources.StaticResource; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Optional; @@ -46,14 +52,14 @@ public static Optional getSchemaValidator( return node.map(n -> validatorFactory.getValidator(n)); } - public static Optional schemaToNode(WorkflowFactories factories, SchemaUnion schema) { + public static Optional schemaToNode(ResourceLoader resourceLoader, SchemaUnion schema) { if (schema != null) { if (schema.getSchemaInline() != null) { SchemaInline inline = schema.getSchemaInline(); return Optional.of(JsonUtils.mapper().convertValue(inline.getDocument(), JsonNode.class)); } else if (schema.getSchemaExternal() != null) { SchemaExternal external = schema.getSchemaExternal(); - StaticResource resource = factories.getResourceLoader().loadStatic(external.getResource()); + StaticResource resource = resourceLoader.loadStatic(external.getResource()); ObjectMapper mapper = WorkflowFormat.fromFileName(resource.name()).mapper(); try (InputStream in = resource.open()) { return Optional.of(mapper.readTree(in)); @@ -89,9 +95,8 @@ public static Optional buildWorkflowFilter( private static WorkflowFilter buildWorkflowFilter( ExpressionFactory exprFactory, String str, Object object) { if (str != null) { - Expression expression = exprFactory.getExpression(str); - return expression::eval; - } else { + return buildWorkflowFilter(exprFactory, str); + } else if (object != null) { Object exprObj = ExpressionUtils.buildExpressionObject(object, exprFactory); return exprObj instanceof Map ? (w, t, n) -> @@ -99,5 +104,80 @@ private static WorkflowFilter buildWorkflowFilter( ExpressionUtils.evaluateExpressionMap((Map) exprObj, w, t, n)) : (w, t, n) -> JsonUtils.fromValue(object); } + throw new IllegalStateException("Both object and str are null"); + } + + private static TaskItem findTaskByName(ListIterator iter, String taskName) { + int currentIndex = iter.nextIndex(); + while (iter.hasPrevious()) { + TaskItem item = iter.previous(); + if (item.getName().equals(taskName)) { + return item; + } + } + while (iter.nextIndex() < currentIndex) { + iter.next(); + } + while (iter.hasNext()) { + TaskItem item = iter.next(); + if (item.getName().equals(taskName)) { + return item; + } + } + throw new IllegalArgumentException("Cannot find task with name " + taskName); + } + + public static void processTaskList(List tasks, WorkflowContext context) { + context.position().addProperty("do"); + if (!tasks.isEmpty()) { + ListIterator iter = tasks.listIterator(); + TaskItem nextTask = iter.next(); + while (nextTask != null) { + TaskItem task = nextTask; + context.position().addIndex(iter.nextIndex()).addProperty(task.getName()); + context + .definition() + .listeners() + .forEach(l -> l.onTaskStarted(context.position(), task.getTask())); + TaskContext taskContext = + context + .definition() + .taskExecutors() + .computeIfAbsent( + context.position().jsonPointer(), + k -> + context + .definition() + .taskFactory() + .getTaskExecutor(task.getTask(), context.definition())) + .apply(context, context.current()); + context.current(taskContext.output()); + FlowDirective flowDirective = taskContext.flowDirective(); + if (flowDirective.getFlowDirectiveEnum() != null) { + switch (flowDirective.getFlowDirectiveEnum()) { + case CONTINUE: + nextTask = iter.hasNext() ? iter.next() : null; + break; + case END: + case EXIT: + nextTask = null; + break; + } + } else { + nextTask = WorkflowUtils.findTaskByName(iter, flowDirective.getString()); + } + context + .definition() + .listeners() + .forEach(l -> l.onTaskEnded(context.position(), task.getTask())); + context.position().back(); + } + } + context.position().back(); + } + + public static WorkflowFilter buildWorkflowFilter(ExpressionFactory exprFactory, String str) { + Expression expression = exprFactory.getExpression(str); + return expression::eval; } } diff --git a/impl/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java b/impl/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java index 3ed5d6e6..56e55120 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java @@ -24,7 +24,7 @@ import io.serverlessworkflow.api.types.TaskBase; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowFactories; +import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowFilter; import io.serverlessworkflow.impl.jsonschema.SchemaValidator; import java.util.Optional; @@ -40,53 +40,57 @@ public abstract class AbstractTaskExecutor implements TaskEx private Optional outputSchemaValidator = Optional.empty(); private Optional contextSchemaValidator = Optional.empty(); - protected AbstractTaskExecutor(T task, WorkflowFactories holder) { + protected AbstractTaskExecutor(T task, WorkflowDefinition definition) { this.task = task; - buildInputProcessors(holder); - buildOutputProcessors(holder); - buildContextProcessors(holder); + buildInputProcessors(definition); + buildOutputProcessors(definition); + buildContextProcessors(definition); } - private void buildInputProcessors(WorkflowFactories holder) { + private void buildInputProcessors(WorkflowDefinition definition) { if (task.getInput() != null) { Input input = task.getInput(); - this.inputProcessor = buildWorkflowFilter(holder.getExpressionFactory(), input.getFrom()); + this.inputProcessor = buildWorkflowFilter(definition.expressionFactory(), input.getFrom()); this.inputSchemaValidator = - getSchemaValidator(holder.getValidatorFactory(), schemaToNode(holder, input.getSchema())); + getSchemaValidator( + definition.validatorFactory(), + schemaToNode(definition.resourceLoader(), input.getSchema())); } } - private void buildOutputProcessors(WorkflowFactories holder) { + private void buildOutputProcessors(WorkflowDefinition definition) { if (task.getOutput() != null) { Output output = task.getOutput(); - this.outputProcessor = buildWorkflowFilter(holder.getExpressionFactory(), output.getAs()); + this.outputProcessor = buildWorkflowFilter(definition.expressionFactory(), output.getAs()); this.outputSchemaValidator = getSchemaValidator( - holder.getValidatorFactory(), schemaToNode(holder, output.getSchema())); + definition.validatorFactory(), + schemaToNode(definition.resourceLoader(), output.getSchema())); } } - private void buildContextProcessors(WorkflowFactories holder) { + private void buildContextProcessors(WorkflowDefinition definition) { if (task.getExport() != null) { Export export = task.getExport(); if (export.getAs() != null) { - this.contextProcessor = buildWorkflowFilter(holder.getExpressionFactory(), export.getAs()); + this.contextProcessor = buildWorkflowFilter(definition.expressionFactory(), export.getAs()); } this.contextSchemaValidator = getSchemaValidator( - holder.getValidatorFactory(), schemaToNode(holder, export.getSchema())); + definition.validatorFactory(), + schemaToNode(definition.resourceLoader(), export.getSchema())); } } @Override - public JsonNode apply(WorkflowContext workflowContext, JsonNode rawInput) { + public TaskContext apply(WorkflowContext workflowContext, JsonNode rawInput) { TaskContext taskContext = new TaskContext<>(rawInput, task); inputSchemaValidator.ifPresent(s -> s.validate(taskContext.rawInput())); inputProcessor.ifPresent( p -> taskContext.input( p.apply(workflowContext, Optional.of(taskContext), taskContext.rawInput()))); - taskContext.rawOutput(internalExecute(workflowContext, taskContext, taskContext.input())); + internalExecute(workflowContext, taskContext); outputProcessor.ifPresent( p -> taskContext.output( @@ -97,9 +101,8 @@ public JsonNode apply(WorkflowContext workflowContext, JsonNode rawInput) { workflowContext.context( p.apply(workflowContext, Optional.of(taskContext), workflowContext.context()))); contextSchemaValidator.ifPresent(s -> s.validate(workflowContext.context())); - return taskContext.output(); + return taskContext; } - protected abstract JsonNode internalExecute( - WorkflowContext workflow, TaskContext task, JsonNode node); + protected abstract void internalExecute(WorkflowContext workflow, TaskContext taskContext); } diff --git a/impl/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java b/impl/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java index cb76e395..117a8ed2 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java @@ -18,7 +18,7 @@ import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.impl.WorkflowFactories; +import io.serverlessworkflow.impl.WorkflowDefinition; public class DefaultTaskExecutorFactory implements TaskExecutorFactory { @@ -30,12 +30,19 @@ public static TaskExecutorFactory get() { protected DefaultTaskExecutorFactory() {} - public TaskExecutor getTaskExecutor(Task task, WorkflowFactories factories) { + public TaskExecutor getTaskExecutor( + Task task, WorkflowDefinition definition) { if (task.getCallTask() != null) { CallTask callTask = task.getCallTask(); if (callTask.getCallHTTP() != null) { - return new HttpExecutor(callTask.getCallHTTP(), factories); + return new HttpExecutor(callTask.getCallHTTP(), definition); } + } else if (task.getSwitchTask() != null) { + return new SwitchExecutor(task.getSwitchTask(), definition); + } else if (task.getDoTask() != null) { + return new DoExecutor(task.getDoTask(), definition); + } else if (task.getSetTask() != null) { + return new SetExecutor(task.getSetTask(), definition); } throw new UnsupportedOperationException(task.get().getClass().getName() + " not supported yet"); } diff --git a/impl/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java b/impl/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java new file mode 100644 index 00000000..30f35f95 --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed 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 io.serverlessworkflow.impl.executors; + +import io.serverlessworkflow.api.types.DoTask; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowUtils; + +public class DoExecutor extends AbstractTaskExecutor { + + protected DoExecutor(DoTask task, WorkflowDefinition definition) { + super(task, definition); + } + + @Override + protected void internalExecute(WorkflowContext workflow, TaskContext taskContext) { + WorkflowUtils.processTaskList(task.getDo(), workflow); + } +} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java b/impl/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java index e17fd8dc..b8548549 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java @@ -24,7 +24,7 @@ import io.serverlessworkflow.api.types.UriTemplate; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowFactories; +import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.expressions.Expression; import io.serverlessworkflow.impl.expressions.ExpressionFactory; import io.serverlessworkflow.impl.expressions.ExpressionUtils; @@ -58,25 +58,25 @@ private interface RequestSupplier { JsonNode apply(Builder request, WorkflowContext workflow, TaskContext task, JsonNode node); } - public HttpExecutor(CallHTTP task, WorkflowFactories holder) { - super(task, holder); + public HttpExecutor(CallHTTP task, WorkflowDefinition definition) { + super(task, definition); HTTPArguments httpArgs = task.getWith(); - this.targetSupplier = getTargetSupplier(httpArgs.getEndpoint(), holder.getExpressionFactory()); + this.targetSupplier = getTargetSupplier(httpArgs.getEndpoint(), definition.expressionFactory()); this.headersMap = httpArgs.getHeaders() != null ? ExpressionUtils.buildExpressionMap( - httpArgs.getHeaders().getAdditionalProperties(), holder.getExpressionFactory()) + httpArgs.getHeaders().getAdditionalProperties(), definition.expressionFactory()) : Map.of(); this.queryMap = httpArgs.getQuery() != null ? ExpressionUtils.buildExpressionMap( - httpArgs.getQuery().getAdditionalProperties(), holder.getExpressionFactory()) + httpArgs.getQuery().getAdditionalProperties(), definition.expressionFactory()) : Map.of(); switch (httpArgs.getMethod().toUpperCase()) { case HttpMethod.POST: Object body = ExpressionUtils.buildExpressionObject( - httpArgs.getBody(), holder.getExpressionFactory()); + httpArgs.getBody(), definition.expressionFactory()); this.requestFunction = (request, workflow, context, node) -> request.post( @@ -92,8 +92,8 @@ public HttpExecutor(CallHTTP task, WorkflowFactories holder) { } @Override - protected JsonNode internalExecute( - WorkflowContext workflow, TaskContext taskContext, JsonNode input) { + protected void internalExecute(WorkflowContext workflow, TaskContext taskContext) { + JsonNode input = taskContext.input(); WebTarget target = targetSupplier.apply(workflow, taskContext, input); for (Entry entry : ExpressionUtils.evaluateExpressionMap(queryMap, workflow, Optional.of(taskContext), input) @@ -103,7 +103,7 @@ protected JsonNode internalExecute( Builder request = target.request(); ExpressionUtils.evaluateExpressionMap(headersMap, workflow, Optional.of(taskContext), input) .forEach(request::header); - return requestFunction.apply(request, workflow, taskContext, input); + taskContext.rawOutput(requestFunction.apply(request, workflow, taskContext, input)); } private static TargetSupplier getTargetSupplier( diff --git a/impl/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java b/impl/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java new file mode 100644 index 00000000..b9d9db86 --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed 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 io.serverlessworkflow.impl.executors; + +import com.fasterxml.jackson.databind.JsonNode; +import io.serverlessworkflow.api.types.SetTask; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.json.JsonUtils; +import io.serverlessworkflow.impl.json.MergeUtils; + +public class SetExecutor extends AbstractTaskExecutor { + + private JsonNode toBeSet; + + protected SetExecutor(SetTask task, WorkflowDefinition definition) { + super(task, definition); + this.toBeSet = JsonUtils.fromValue(task.getSet().getAdditionalProperties()); + } + + @Override + protected void internalExecute(WorkflowContext workflow, TaskContext taskContext) { + taskContext.rawOutput(MergeUtils.merge(toBeSet, taskContext.input())); + } +} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java b/impl/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java new file mode 100644 index 00000000..9f421acb --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java @@ -0,0 +1,50 @@ +package io.serverlessworkflow.impl.executors; + +import io.serverlessworkflow.api.types.FlowDirective; +import io.serverlessworkflow.api.types.SwitchCase; +import io.serverlessworkflow.api.types.SwitchItem; +import io.serverlessworkflow.api.types.SwitchTask; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowUtils; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class SwitchExecutor extends AbstractTaskExecutor { + + private Map workflowFilters = new ConcurrentHashMap<>(); + private FlowDirective defaultDirective; + + protected SwitchExecutor(SwitchTask task, WorkflowDefinition definition) { + super(task, definition); + for (SwitchItem item : task.getSwitch()) { + SwitchCase switchCase = item.getSwitchCase(); + if (switchCase.getWhen() != null) { + workflowFilters.put( + switchCase, + WorkflowUtils.buildWorkflowFilter( + definition.expressionFactory(), switchCase.getWhen())); + } else { + defaultDirective = switchCase.getThen(); + } + } + } + + @Override + protected void internalExecute(WorkflowContext workflow, TaskContext taskContext) { + for (Entry entry : workflowFilters.entrySet()) { + if (entry + .getValue() + .apply(workflow, Optional.of(taskContext), taskContext.input()) + .asBoolean()) { + taskContext.flowDirective(entry.getKey().getThen()); + return; + } + } + taskContext.flowDirective(defaultDirective); + } +} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java b/impl/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java index 8c896385..62228199 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java @@ -17,8 +17,9 @@ import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; import java.util.function.BiFunction; public interface TaskExecutor - extends BiFunction {} + extends BiFunction> {} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorFactory.java b/impl/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorFactory.java index 85cef4b1..8c399cf6 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorFactory.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorFactory.java @@ -17,8 +17,8 @@ import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.impl.WorkflowFactories; +import io.serverlessworkflow.impl.WorkflowDefinition; public interface TaskExecutorFactory { - TaskExecutor getTaskExecutor(Task task, WorkflowFactories factories); + TaskExecutor getTaskExecutor(Task task, WorkflowDefinition definition); } diff --git a/impl/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java b/impl/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java index c1dbd85f..187c0215 100644 --- a/impl/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java +++ b/impl/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.stream.Stream; import org.assertj.core.api.Condition; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -30,15 +31,18 @@ public class WorkflowDefinitionTest { + private static WorkflowApplication appl; + + @BeforeAll + static void init() { + appl = WorkflowApplication.builder().build(); + } + @ParameterizedTest @MethodSource("provideParameters") void testWorkflowExecution(String fileName, Object input, Condition condition) throws IOException { - assertThat( - WorkflowDefinition.builder(readWorkflowFromClasspath(fileName)) - .build() - .execute(input) - .output()) + assertThat(appl.workflowDefinition(readWorkflowFromClasspath(fileName)).execute(input).output()) .is(condition); } @@ -52,10 +56,7 @@ void testWrongSchema(String fileName) { IllegalArgumentException exception = catchThrowableOfType( IllegalArgumentException.class, - () -> - WorkflowDefinition.builder(readWorkflowFromClasspath(fileName)) - .build() - .execute(Map.of())); + () -> appl.workflowDefinition(readWorkflowFromClasspath(fileName)).execute(Map.of())); assertThat(exception) .isNotNull() .hasMessageContaining("There are JsonSchema validation errors"); @@ -82,6 +83,39 @@ private static Stream provideParameters() { Arguments.of( "callPostHttp.yaml", Map.of("name", "Javierito", "status", "available"), - new Condition<>(o -> o.equals("Javierito"), "CallHttpPostCondition"))); + new Condition<>(o -> o.equals("Javierito"), "CallHttpPostCondition")), + Arguments.of( + "switch-then-string.yaml", + Map.of("orderType", "electronic"), + new Condition( + o -> + o.equals( + Map.of("orderType", "electronic", "validate", true, "status", "fulfilled")), + "switch-electronic")), + Arguments.of( + "switch-then-string.yaml", + Map.of("orderType", "physical"), + new Condition( + o -> + o.equals( + Map.of( + "orderType", + "physical", + "inventory", + "clear", + "items", + 1, + "address", + "Elmer St")), + "switch-physical")), + Arguments.of( + "switch-then-string.yaml", + Map.of("orderType", "unknown"), + new Condition( + o -> + o.equals( + Map.of( + "orderType", "unknown", "log", "warn", "message", "something's wrong")), + "switch-unknown"))); } } diff --git a/impl/src/test/resources/call-http-endpoint-interpolation.yaml b/impl/src/test/resources/call-http-endpoint-interpolation.yaml index 8380a9aa..4d6453a5 100644 --- a/impl/src/test/resources/call-http-endpoint-interpolation.yaml +++ b/impl/src/test/resources/call-http-endpoint-interpolation.yaml @@ -1,5 +1,5 @@ document: - dsl: '1.0.0-alpha5' + dsl: 1.0.0-alpha5 namespace: examples name: call-http-shorthand-endpoint version: '0.1.0' diff --git a/impl/src/test/resources/call-http-query-parameters-external-schema.yaml b/impl/src/test/resources/call-http-query-parameters-external-schema.yaml index a5bb1437..e72b79e4 100644 --- a/impl/src/test/resources/call-http-query-parameters-external-schema.yaml +++ b/impl/src/test/resources/call-http-query-parameters-external-schema.yaml @@ -1,7 +1,7 @@ document: dsl: 1.0.0-alpha2 namespace: examples - name: http-query-params + name: http-query-params-schema version: 1.0.0-alpha2 input: schema: diff --git a/impl/src/test/resources/callGetHttp.yaml b/impl/src/test/resources/callGetHttp.yaml index 0fdeb10a..eb7145fa 100644 --- a/impl/src/test/resources/callGetHttp.yaml +++ b/impl/src/test/resources/callGetHttp.yaml @@ -1,7 +1,7 @@ document: dsl: 1.0.0-alpha1 - namespace: default - name: http-call-with-response-output + namespace: examples + name: http-call-with-response version: 1.0.0 do: - getPet: diff --git a/impl/src/test/resources/callPostHttp.yaml b/impl/src/test/resources/callPostHttp.yaml index d898dbf7..0e998094 100644 --- a/impl/src/test/resources/callPostHttp.yaml +++ b/impl/src/test/resources/callPostHttp.yaml @@ -1,6 +1,6 @@ document: dsl: 1.0.0-alpha1 - namespace: default + namespace: examples name: http-call-with-response-output version: 1.0.0 do: diff --git a/impl/src/test/resources/switch-then-string.yaml b/impl/src/test/resources/switch-then-string.yaml new file mode 100644 index 00000000..6c54b359 --- /dev/null +++ b/impl/src/test/resources/switch-then-string.yaml @@ -0,0 +1,45 @@ +document: + dsl: 1.0.0-alpha5 + namespace: examples + name: switch + version: 0.1.0 +do: + - processOrder: + switch: + - case1: + when: .orderType == "electronic" + then: processElectronicOrder + - case2: + when: .orderType == "physical" + then: processPhysicalOrder + - default: + then: handleUnknownOrderType + - processElectronicOrder: + do: + - validatePayment: + set: + validate: true + - fulfillOrder: + set: + status: fulfilled + then: exit + - processPhysicalOrder: + do: + - checkInventory: + set: + inventory: clear + - packItems: + set: + items: 1 + - scheduleShipping: + set: + address: Elmer St + then: exit + - handleUnknownOrderType: + do: + - logWarning: + set: + log: warn + - notifyAdmin: + set: + message: something's wrong diff --git a/pom.xml b/pom.xml index 4f74b387..36fc113b 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ - 11 + 17 ${java.version} ${java.version} UTF-8 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy