From 6a56b89dcf2edfdaec3a74b9bdce399020271851 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Tue, 15 Jul 2025 13:25:41 +0200 Subject: [PATCH 1/2] [Fix #636] Initial refactor to separate Jackson/JQ Signed-off-by: fjtirado --- impl/core/pom.xml | 10 +- .../serverlessworkflow/impl/TaskContext.java | 33 ++-- .../impl/WorkflowApplication.java | 4 + .../impl/WorkflowContext.java | 8 +- .../impl/WorkflowDefinition.java | 7 +- .../impl/WorkflowFilter.java | 4 +- .../impl/WorkflowInstance.java | 29 ++-- .../impl/WorkflowModel.java | 50 ++++++ .../impl/WorkflowModelCollection.java | 61 +++++++ .../impl/WorkflowModelFactory.java | 67 ++++++++ .../impl/WorkflowUtils.java | 113 ++++--------- .../impl/events/AbstractTypeConsumer.java | 2 +- .../impl/events/CloudEventUtils.java | 13 -- .../events/DefaultCloudEventPredicate.java | 86 ++++++---- .../impl/events/InMemoryEvents.java | 2 +- .../impl/executors/AbstractTaskExecutor.java | 15 +- .../impl/executors/CallTaskExecutor.java | 7 +- .../impl/executors/CallableTask.java | 6 +- .../impl/executors/DoExecutor.java | 4 +- .../impl/executors/EmitExecutor.java | 78 +++++---- .../impl/executors/ForExecutor.java | 23 ++- .../impl/executors/ForkExecutor.java | 19 ++- .../impl/executors/ListenExecutor.java | 56 +++---- .../impl/executors/RaiseExecutor.java | 24 ++- .../impl/executors/RegularTaskExecutor.java | 4 +- .../impl/executors/SetExecutor.java | 6 +- .../impl/executors/SwitchExecutor.java | 10 +- .../impl/executors/TaskExecutor.java | 4 +- .../impl/executors/TaskExecutorHelper.java | 6 +- .../impl/executors/TryExecutor.java | 20 ++- .../impl/executors/WaitExecutor.java | 7 +- .../impl/expressions/Expression.java | 4 +- .../impl/expressions/ExpressionFactory.java | 9 +- .../impl/expressions/ExpressionUtils.java | 24 +-- .../impl/expressions/JQExpression.java | 20 ++- .../impl/expressions/JQExpressionFactory.java | 15 +- .../impl/expressions/JacksonModel.java | 125 ++++++++++++++ .../expressions/JacksonModelCollection.java | 157 ++++++++++++++++++ .../impl/expressions/JacksonModelFactory.java | 125 ++++++++++++++ .../expressions/JacksonModelSerializer.java | 36 ++++ .../expressions/ObjectExpressionFactory.java | 37 +++++ .../impl/expressions/TaskDescriptor.java | 6 +- .../impl/expressions/WorkflowDescriptor.java | 4 +- .../impl/json/JsonUtils.java | 18 ++ .../jsonschema/DefaultSchemaValidator.java | 11 +- .../DefaultSchemaValidatorFactory.java | 23 ++- .../impl/jsonschema/SchemaValidator.java | 4 +- .../jsonschema/SchemaValidatorFactory.java | 7 +- .../impl/EventDefinitionTest.java | 8 +- .../impl/WorkflowDefinitionTest.java | 14 +- .../impl/executors/HttpExecutor.java | 97 ++++++----- .../impl/executors/HttpModelConverter.java} | 18 +- .../impl/HTTPWorkflowDefinitionTest.java | 17 +- 53 files changed, 1135 insertions(+), 422 deletions(-) create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelCollection.java create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpressionFactory.java rename impl/{core/src/main/java/io/serverlessworkflow/impl/LongFilter.java => http/src/main/java/io/serverlessworkflow/impl/executors/HttpModelConverter.java} (59%) diff --git a/impl/core/pom.xml b/impl/core/pom.xml index 844cf2a4..4140e747 100644 --- a/impl/core/pom.xml +++ b/impl/core/pom.xml @@ -8,9 +8,9 @@ serverlessworkflow-impl-core Serverless Workflow :: Impl :: Core - + io.serverlessworkflow - serverlessworkflow-api + serverlessworkflow-types ${project.version} @@ -58,5 +58,11 @@ logback-classic test + + io.serverlessworkflow + serverlessworkflow-api + ${project.version} + + diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java b/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java index 4fc3d1f4..91c4abab 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java @@ -15,7 +15,6 @@ */ package io.serverlessworkflow.impl; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.TaskBase; import io.serverlessworkflow.impl.executors.TransitionInfo; import java.time.Instant; @@ -25,7 +24,7 @@ public class TaskContext { - private final JsonNode rawInput; + private final WorkflowModel rawInput; private final TaskBase task; private final WorkflowPosition position; private final Instant startedAt; @@ -33,14 +32,14 @@ public class TaskContext { private final Map contextVariables; private final Optional parentContext; - private JsonNode input; - private JsonNode output; - private JsonNode rawOutput; + private WorkflowModel input; + private WorkflowModel output; + private WorkflowModel rawOutput; private Instant completedAt; private TransitionInfo transition; public TaskContext( - JsonNode input, + WorkflowModel input, WorkflowPosition position, Optional parentContext, String taskName, @@ -49,15 +48,15 @@ public TaskContext( } private TaskContext( - JsonNode rawInput, + WorkflowModel rawInput, Optional parentContext, String taskName, TaskBase task, WorkflowPosition position, Instant startedAt, - JsonNode input, - JsonNode output, - JsonNode rawOutput) { + WorkflowModel input, + WorkflowModel output, + WorkflowModel rawOutput) { this.rawInput = rawInput; this.parentContext = parentContext; this.taskName = taskName; @@ -76,17 +75,17 @@ public TaskContext copy() { rawInput, parentContext, taskName, task, position, startedAt, input, output, rawOutput); } - public void input(JsonNode input) { + public void input(WorkflowModel input) { this.input = input; this.rawOutput = input; this.output = input; } - public JsonNode input() { + public WorkflowModel input() { return input; } - public JsonNode rawInput() { + public WorkflowModel rawInput() { return rawInput; } @@ -94,22 +93,22 @@ public TaskBase task() { return task; } - public TaskContext rawOutput(JsonNode output) { + public TaskContext rawOutput(WorkflowModel output) { this.rawOutput = output; this.output = output; return this; } - public JsonNode rawOutput() { + public WorkflowModel rawOutput() { return rawOutput; } - public TaskContext output(JsonNode output) { + public TaskContext output(WorkflowModel output) { this.output = output; return this; } - public JsonNode output() { + public WorkflowModel output() { return output; } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java index b998c57d..ab09dac7 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -202,6 +202,10 @@ public WorkflowPositionFactory positionFactory() { return positionFactory; } + public WorkflowModelFactory modelFactory() { + return exprFactory.modelFactory(); + } + public RuntimeDescriptorFactory runtimeDescriptorFactory() { return runtimeDescriptorFactory; } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java index 96890c8b..6960ca66 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java @@ -15,12 +15,10 @@ */ package io.serverlessworkflow.impl; -import com.fasterxml.jackson.databind.JsonNode; - public class WorkflowContext { private final WorkflowDefinition definition; private final WorkflowInstance instance; - private JsonNode context; + private WorkflowModel context; WorkflowContext(WorkflowDefinition definition, WorkflowInstance instance) { this.definition = definition; @@ -31,11 +29,11 @@ public WorkflowInstance instance() { return instance; } - public JsonNode context() { + public WorkflowModel context() { return context; } - public void context(JsonNode context) { + public void context(WorkflowModel context) { this.context = context; } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java index 1a789616..639f368d 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java @@ -22,7 +22,6 @@ import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.executors.TaskExecutor; import io.serverlessworkflow.impl.executors.TaskExecutorHelper; -import io.serverlessworkflow.impl.json.JsonUtils; import io.serverlessworkflow.impl.jsonschema.SchemaValidator; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.nio.file.Path; @@ -47,13 +46,13 @@ private WorkflowDefinition( Input input = workflow.getInput(); this.inputSchemaValidator = getSchemaValidator(application.validatorFactory(), resourceLoader, input.getSchema()); - this.inputFilter = buildWorkflowFilter(application.expressionFactory(), input.getFrom()); + this.inputFilter = buildWorkflowFilter(application, input.getFrom()); } if (workflow.getOutput() != null) { Output output = workflow.getOutput(); this.outputSchemaValidator = getSchemaValidator(application.validatorFactory(), resourceLoader, output.getSchema()); - this.outputFilter = buildWorkflowFilter(application.expressionFactory(), output.getAs()); + this.outputFilter = buildWorkflowFilter(application, output.getAs()); } this.taskExecutor = TaskExecutorHelper.createExecutorList( @@ -74,7 +73,7 @@ static WorkflowDefinition of(WorkflowApplication application, Workflow workflow, } public WorkflowInstance instance(Object input) { - return new WorkflowInstance(this, JsonUtils.fromValue(input)); + return new WorkflowInstance(this, application.modelFactory().fromAny(input)); } Optional inputSchemaValidator() { diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowFilter.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowFilter.java index 4475cacd..04c34d29 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowFilter.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowFilter.java @@ -15,9 +15,7 @@ */ package io.serverlessworkflow.impl; -import com.fasterxml.jackson.databind.JsonNode; - @FunctionalInterface public interface WorkflowFilter { - JsonNode apply(WorkflowContext workflow, TaskContext task, JsonNode node); + WorkflowModel apply(WorkflowContext workflow, TaskContext task, WorkflowModel node); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java index 2e55c484..88269082 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java @@ -15,9 +15,7 @@ */ package io.serverlessworkflow.impl; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.impl.executors.TaskExecutorHelper; -import io.serverlessworkflow.impl.json.JsonUtils; import java.time.Instant; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -26,16 +24,16 @@ public class WorkflowInstance { private final AtomicReference status; private final String id; - private final JsonNode input; + private final WorkflowModel input; private WorkflowContext workflowContext; private WorkflowDefinition definition; private Instant startedAt; private Instant completedAt; - private volatile JsonNode output; - private CompletableFuture completableFuture; + private volatile WorkflowModel output; + private CompletableFuture completableFuture; - WorkflowInstance(WorkflowDefinition definition, JsonNode input) { + WorkflowInstance(WorkflowDefinition definition, WorkflowModel input) { this.id = definition.application().idFactory().get(); this.input = input; this.definition = definition; @@ -43,7 +41,7 @@ public class WorkflowInstance { definition.inputSchemaValidator().ifPresent(v -> v.validate(input)); } - public CompletableFuture start() { + public CompletableFuture start() { this.startedAt = Instant.now(); this.workflowContext = new WorkflowContext(definition, this); this.status.set(WorkflowStatus.RUNNING); @@ -60,7 +58,7 @@ public CompletableFuture start() { return completableFuture; } - private JsonNode whenCompleted(JsonNode node) { + private WorkflowModel whenCompleted(WorkflowModel node) { output = workflowContext .definition() @@ -85,7 +83,7 @@ public Instant completedAt() { return completedAt; } - public JsonNode input() { + public WorkflowModel input() { return input; } @@ -97,11 +95,16 @@ public void status(WorkflowStatus state) { this.status.set(state); } - public Object output() { - return JsonUtils.toJavaValue(outputAsJsonNode()); + public WorkflowModel output() { + return output; } - public JsonNode outputAsJsonNode() { - return output; + public T outputAs(Class clazz) { + return output + .as(clazz) + .orElseThrow( + () -> + new IllegalArgumentException( + "Output " + output + " cannot be converted to class " + clazz)); } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java new file mode 100644 index 00000000..dc45896f --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java @@ -0,0 +1,50 @@ +/* + * 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.cloudevents.CloudEventData; +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; + +public interface WorkflowModel { + + void forEach(BiConsumer consumer); + + Optional asBoolean(); + + Collection asCollection(); + + Optional asText(); + + Optional asDate(); + + Optional asNumber(); + + Optional asCloudEventData(); + + Optional> asMap(); + + Object asJavaObject(); + + Object asIs(); + + Class objectClass(); + + Optional as(Class clazz); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelCollection.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelCollection.java new file mode 100644 index 00000000..09f1dd75 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelCollection.java @@ -0,0 +1,61 @@ +/* + * 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.cloudevents.CloudEventData; +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; + +public interface WorkflowModelCollection extends WorkflowModel, Collection { + + default void forEach(BiConsumer consumer) {} + + @Override + default Collection asCollection() { + return this; + } + + @Override + default Optional asBoolean() { + return Optional.empty(); + } + + @Override + default Optional asText() { + return Optional.empty(); + } + + @Override + default Optional asNumber() { + return Optional.empty(); + } + + @Override + public default Optional asDate() { + return Optional.empty(); + } + + default Optional asCloudEventData() { + return Optional.empty(); + } + + default Optional> asMap() { + return Optional.empty(); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java new file mode 100644 index 00000000..f8cf7278 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java @@ -0,0 +1,67 @@ +/* + * 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.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import java.time.OffsetDateTime; +import java.util.Map; + +public interface WorkflowModelFactory { + + WorkflowModel combine(Map workflowVariables); + + WorkflowModelCollection createCollection(); + + WorkflowModel from(boolean value); + + WorkflowModel from(Number value); + + WorkflowModel from(String value); + + WorkflowModel from(CloudEvent ce); + + WorkflowModel from(CloudEventData ce); + + WorkflowModel from(OffsetDateTime value); + + WorkflowModel from(Map map); + + WorkflowModel fromNull(); + + default WorkflowModel fromAny(Object obj) { + if (obj == null) { + return fromNull(); + } else if (obj instanceof Boolean value) { + return from(value); + } else if (obj instanceof Number value) { + return from(value); + } else if (obj instanceof String value) { + return from(value); + } else if (obj instanceof CloudEvent value) { + return from(value); + } else if (obj instanceof CloudEventData value) { + return from(value); + } else if (obj instanceof OffsetDateTime value) { + return from(value); + } else if (obj instanceof Map) { + return from((Map) obj); + } else { + throw new IllegalArgumentException( + "Unsopported conversion for object " + obj + " of type" + obj.getClass()); + } + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java index 5feaf04e..069986df 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -15,29 +15,16 @@ */ package io.serverlessworkflow.impl; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.serverlessworkflow.api.WorkflowFormat; import io.serverlessworkflow.api.types.ExportAs; 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.UriTemplate; -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.impl.resources.ResourceLoader; -import io.serverlessworkflow.impl.resources.StaticResource; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; import java.net.URI; -import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -47,55 +34,41 @@ private WorkflowUtils() {} public static Optional getSchemaValidator( SchemaValidatorFactory validatorFactory, ResourceLoader resourceLoader, SchemaUnion schema) { - return schemaToNode(resourceLoader, schema).map(n -> validatorFactory.getValidator(n)); - } - - private 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)); + return Optional.of(validatorFactory.getValidator(schema.getSchemaInline())); } else if (schema.getSchemaExternal() != null) { - SchemaExternal external = schema.getSchemaExternal(); - StaticResource resource = resourceLoader.loadStatic(external.getResource()); - ObjectMapper mapper = WorkflowFormat.fromFileName(resource.name()).mapper(); - try (InputStream in = resource.open()) { - return Optional.of(mapper.readTree(in)); - } catch (IOException io) { - throw new UncheckedIOException(io); - } + return Optional.of( + validatorFactory.getValidator( + resourceLoader.loadStatic(schema.getSchemaExternal().getResource()))); } } return Optional.empty(); } public static Optional buildWorkflowFilter( - ExpressionFactory exprFactory, InputFrom from) { + WorkflowApplication app, InputFrom from) { return from != null - ? Optional.of(buildWorkflowFilter(exprFactory, from.getString(), from.getObject())) + ? Optional.of(buildWorkflowFilter(app, from.getString(), from.getObject())) : Optional.empty(); } - public static Optional buildWorkflowFilter( - ExpressionFactory exprFactory, OutputAs as) { + public static Optional buildWorkflowFilter(WorkflowApplication app, OutputAs as) { return as != null - ? Optional.of(buildWorkflowFilter(exprFactory, as.getString(), as.getObject())) + ? Optional.of(buildWorkflowFilter(app, as.getString(), as.getObject())) : Optional.empty(); } public static ExpressionHolder buildExpressionHolder( - ExpressionFactory exprFactory, - String expression, - T literal, - Function converter) { + WorkflowApplication app, String expression, T literal, Function converter) { return expression != null - ? buildExpressionHolder(buildWorkflowFilter(exprFactory, expression), converter) + ? buildExpressionHolder(buildWorkflowFilter(app, expression), converter) : buildExpressionHolder(literal); } private static ExpressionHolder buildExpressionHolder( - WorkflowFilter filter, Function converter) { + WorkflowFilter filter, Function converter) { return (w, t) -> converter.apply(filter.apply(w, t, t.input())); } @@ -103,28 +76,27 @@ private static ExpressionHolder buildExpressionHolder(T literal) { return (w, t) -> literal; } - public static Optional buildWorkflowFilter( - ExpressionFactory exprFactory, ExportAs as) { + public static Optional buildWorkflowFilter(WorkflowApplication app, ExportAs as) { return as != null - ? Optional.of(buildWorkflowFilter(exprFactory, as.getString(), as.getObject())) + ? Optional.of(buildWorkflowFilter(app, as.getString(), as.getObject())) : Optional.empty(); } public static StringFilter buildStringFilter( - ExpressionFactory exprFactory, String expression, String literal) { - return expression != null - ? toString(buildWorkflowFilter(exprFactory, expression)) - : toString(literal); + WorkflowApplication app, String expression, String literal) { + return expression != null ? toString(buildWorkflowFilter(app, expression)) : toString(literal); } - public static StringFilter buildStringFilter(ExpressionFactory exprFactory, String str) { - return ExpressionUtils.isExpr(str) - ? toString(buildWorkflowFilter(exprFactory, str)) - : toString(str); + public static StringFilter buildStringFilter(WorkflowApplication app, String str) { + return ExpressionUtils.isExpr(str) ? toString(buildWorkflowFilter(app, str)) : toString(str); } private static StringFilter toString(WorkflowFilter filter) { - return (w, t) -> filter.apply(w, t, t.input()).asText(); + return (w, t) -> + filter + .apply(w, t, t.input()) + .asText() + .orElseThrow(() -> new IllegalArgumentException("Result is not an string")); } private static StringFilter toString(String literal) { @@ -132,43 +104,16 @@ private static StringFilter toString(String literal) { } public static WorkflowFilter buildWorkflowFilter( - ExpressionFactory exprFactory, String str, Object object) { - if (str != null) { - return buildWorkflowFilter(exprFactory, str); - } else if (object != null) { - Object exprObj = ExpressionUtils.buildExpressionObject(object, exprFactory); - return exprObj instanceof Map - ? (w, t, n) -> - JsonUtils.fromValue( - ExpressionUtils.evaluateExpressionMap((Map) exprObj, w, t, n)) - : (w, t, n) -> JsonUtils.fromValue(object); - } - throw new IllegalStateException("Both object and str are null"); - } - - public static LongFilter buildLongFilter( - ExpressionFactory exprFactory, String expression, Long literal) { - return expression != null - ? toLong(buildWorkflowFilter(exprFactory, expression)) - : toLong(literal); - } - - private static LongFilter toLong(WorkflowFilter filter) { - return (w, t) -> filter.apply(w, t, t.input()).asLong(); - } - - private static LongFilter toLong(Long literal) { - return (w, t) -> literal; + WorkflowApplication app, String str, Object object) { + return app.expressionFactory().buildFilter(str, object); } - public static WorkflowFilter buildWorkflowFilter(ExpressionFactory exprFactory, String str) { - assert str != null; - Expression expression = exprFactory.getExpression(str); - return expression::eval; + public static WorkflowFilter buildWorkflowFilter(WorkflowApplication app, String str) { + return app.expressionFactory().buildFilter(str, null); } - public static Optional optionalFilter(ExpressionFactory exprFactory, String str) { - return str != null ? Optional.of(buildWorkflowFilter(exprFactory, str)) : Optional.empty(); + public static Optional optionalFilter(WorkflowApplication app, String str) { + return str != null ? Optional.of(buildWorkflowFilter(app, str)) : Optional.empty(); } public static String toString(UriTemplate template) { diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/AbstractTypeConsumer.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/AbstractTypeConsumer.java index a3222342..d4a613d2 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/events/AbstractTypeConsumer.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/AbstractTypeConsumer.java @@ -51,7 +51,7 @@ public TypeEventRegistrationBuilder listen( EventProperties properties = register.getWith(); String type = properties.getType(); return new TypeEventRegistrationBuilder( - type, new DefaultCloudEventPredicate(properties, application.expressionFactory())); + type, new DefaultCloudEventPredicate(properties, application)); } @Override diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java index 1b2709b8..a5a5e04c 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import io.cloudevents.CloudEvent; import io.cloudevents.CloudEventData; -import io.cloudevents.core.builder.CloudEventBuilder; import io.cloudevents.jackson.JsonCloudEventData; import io.serverlessworkflow.impl.json.JsonUtils; import java.io.IOException; @@ -64,18 +63,6 @@ public static OffsetDateTime toOffset(Date date) { return date.toInstant().atOffset(ZoneOffset.UTC); } - public static CloudEventBuilder addExtension( - CloudEventBuilder builder, String name, JsonNode value) { - if (value.isTextual()) { - builder.withExtension(name, value.asText()); - } else if (value.isBoolean()) { - builder.withExtension(name, value.isBoolean()); - } else if (value.isNumber()) { - builder.withExtension(name, value.numberValue()); - } - return builder; - } - public static JsonNode toJsonNode(CloudEventData data) { if (data == null) { return NullNode.instance; diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/DefaultCloudEventPredicate.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/DefaultCloudEventPredicate.java index 6eb35995..ee319727 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/events/DefaultCloudEventPredicate.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/DefaultCloudEventPredicate.java @@ -15,19 +15,19 @@ */ package io.serverlessworkflow.impl.events; -import com.fasterxml.jackson.databind.JsonNode; import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; import io.serverlessworkflow.api.types.EventData; import io.serverlessworkflow.api.types.EventDataschema; import io.serverlessworkflow.api.types.EventProperties; import io.serverlessworkflow.api.types.EventSource; import io.serverlessworkflow.api.types.EventTime; import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModelFactory; import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.expressions.Expression; -import io.serverlessworkflow.impl.expressions.ExpressionFactory; -import io.serverlessworkflow.impl.json.JsonUtils; import java.net.URI; import java.time.OffsetDateTime; import java.util.Map; @@ -42,51 +42,59 @@ public class DefaultCloudEventPredicate implements CloudEventPredicate { private final CloudEventAttrPredicate typeFilter; private final CloudEventAttrPredicate dataSchemaFilter; private final CloudEventAttrPredicate timeFilter; - private final CloudEventAttrPredicate dataFilter; - private final CloudEventAttrPredicate additionalFilter; + private final CloudEventAttrPredicate dataFilter; + private final CloudEventAttrPredicate> additionalFilter; private static final CloudEventAttrPredicate isTrue() { return x -> true; } - public DefaultCloudEventPredicate(EventProperties properties, ExpressionFactory exprFactory) { + public DefaultCloudEventPredicate(EventProperties properties, WorkflowApplication app) { idFilter = stringFilter(properties.getId()); subjectFilter = stringFilter(properties.getSubject()); typeFilter = stringFilter(properties.getType()); contentTypeFilter = stringFilter(properties.getDatacontenttype()); - sourceFilter = sourceFilter(properties.getSource(), exprFactory); - dataSchemaFilter = dataSchemaFilter(properties.getDataschema(), exprFactory); - timeFilter = offsetTimeFilter(properties.getTime(), exprFactory); - dataFilter = dataFilter(properties.getData(), exprFactory); - additionalFilter = additionalFilter(properties.getAdditionalProperties(), exprFactory); + sourceFilter = sourceFilter(properties.getSource(), app); + dataSchemaFilter = dataSchemaFilter(properties.getDataschema(), app); + timeFilter = offsetTimeFilter(properties.getTime(), app); + dataFilter = dataFilter(properties.getData(), app); + additionalFilter = additionalFilter(properties.getAdditionalProperties(), app); } - private CloudEventAttrPredicate additionalFilter( - Map additionalProperties, ExpressionFactory exprFactory) { + private CloudEventAttrPredicate> additionalFilter( + Map additionalProperties, WorkflowApplication app) { return additionalProperties != null && !additionalProperties.isEmpty() - ? from(WorkflowUtils.buildWorkflowFilter(exprFactory, null, additionalProperties)) + ? fromMap( + app.modelFactory(), WorkflowUtils.buildWorkflowFilter(app, null, additionalProperties)) : isTrue(); } - private CloudEventAttrPredicate from(WorkflowFilter filter) { - return d -> filter.apply(null, null, d).asBoolean(); + private CloudEventAttrPredicate fromCloudEvent( + WorkflowModelFactory workflowModelFactory, WorkflowFilter filter) { + return d -> filter.apply(null, null, workflowModelFactory.from(d)).asBoolean().orElse(false); } - private CloudEventAttrPredicate dataFilter( - EventData data, ExpressionFactory exprFactory) { + private CloudEventAttrPredicate> fromMap( + WorkflowModelFactory workflowModelFactory, WorkflowFilter filter) { + return d -> filter.apply(null, null, workflowModelFactory.from(d)).asBoolean().orElse(false); + } + + private CloudEventAttrPredicate dataFilter( + EventData data, WorkflowApplication app) { return data != null - ? from( - WorkflowUtils.buildWorkflowFilter( - exprFactory, data.getRuntimeExpression(), data.getObject())) + ? fromCloudEvent( + app.modelFactory(), + WorkflowUtils.buildWorkflowFilter(app, data.getRuntimeExpression(), data.getObject())) : isTrue(); } private CloudEventAttrPredicate offsetTimeFilter( - EventTime time, ExpressionFactory exprFactory) { + EventTime time, WorkflowApplication app) { if (time != null) { if (time.getRuntimeExpression() != null) { - final Expression expr = exprFactory.getExpression(time.getRuntimeExpression()); - return s -> evalExpr(expr, toString(s)); + final Expression expr = + app.expressionFactory().buildExpression(time.getRuntimeExpression()); + return s -> evalExpr(app.modelFactory(), expr, s); } else if (time.getLiteralTime() != null) { return s -> Objects.equals(s, CloudEventUtils.toOffset(time.getLiteralTime())); } @@ -95,11 +103,12 @@ private CloudEventAttrPredicate offsetTimeFilter( } private CloudEventAttrPredicate dataSchemaFilter( - EventDataschema dataSchema, ExpressionFactory exprFactory) { + EventDataschema dataSchema, WorkflowApplication app) { if (dataSchema != null) { if (dataSchema.getExpressionDataSchema() != null) { - final Expression expr = exprFactory.getExpression(dataSchema.getExpressionDataSchema()); - return s -> evalExpr(expr, toString(s)); + final Expression expr = + app.expressionFactory().buildExpression(dataSchema.getExpressionDataSchema()); + return s -> evalExpr(app.modelFactory(), expr, toString(s)); } else if (dataSchema.getLiteralDataSchema() != null) { return templateFilter(dataSchema.getLiteralDataSchema()); } @@ -111,12 +120,12 @@ private CloudEventAttrPredicate stringFilter(String str) { return str == null ? isTrue() : x -> x.equals(str); } - private CloudEventAttrPredicate sourceFilter( - EventSource source, ExpressionFactory exprFactory) { + private CloudEventAttrPredicate sourceFilter(EventSource source, WorkflowApplication app) { if (source != null) { if (source.getRuntimeExpression() != null) { - final Expression expr = exprFactory.getExpression(source.getRuntimeExpression()); - return s -> evalExpr(expr, toString(s)); + final Expression expr = + app.expressionFactory().buildExpression(source.getRuntimeExpression()); + return s -> evalExpr(app.modelFactory(), expr, toString(s)); } else if (source.getUriTemplate() != null) { return templateFilter(source.getUriTemplate()); } @@ -128,15 +137,20 @@ private CloudEventAttrPredicate templateFilter(UriTemplate template) { if (template.getLiteralUri() != null) { return u -> Objects.equals(u, template.getLiteralUri()); } - throw new UnsupportedOperationException("Template not supporte here yet"); + throw new UnsupportedOperationException("Template not supported here yet"); } private String toString(T uri) { return uri != null ? uri.toString() : null; } - private boolean evalExpr(Expression expr, T value) { - return expr.eval(null, null, JsonUtils.fromValue(value)).asBoolean(); + private boolean evalExpr(WorkflowModelFactory modelFactory, Expression expr, String value) { + return expr.eval(null, null, modelFactory.from(value)).asBoolean().orElse(false); + } + + private boolean evalExpr( + WorkflowModelFactory modelFactory, Expression expr, OffsetDateTime value) { + return expr.eval(null, null, modelFactory.from(value)).asBoolean().orElse(false); } @Override @@ -148,7 +162,7 @@ public boolean test(CloudEvent event) { && typeFilter.test(event.getType()) && dataSchemaFilter.test(event.getDataSchema()) && timeFilter.test(event.getTime()) - && dataFilter.test(CloudEventUtils.toJsonNode(event.getData())) - && additionalFilter.test(JsonUtils.fromValue(CloudEventUtils.extensions(event))); + && dataFilter.test(event.getData()) + && additionalFilter.test(CloudEventUtils.extensions(event)); } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java index 714d89d0..edd2dad7 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java @@ -25,7 +25,7 @@ /* * Straightforward implementation of in memory event broker. - * User might invoke notifyCE to simulate event reception. + * User might invoke publish to simulate event reception. */ public class InMemoryEvents extends AbstractTypeConsumer implements EventPublisher { diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java index 23ca9a22..18d23d85 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java @@ -17,7 +17,6 @@ import static io.serverlessworkflow.impl.WorkflowUtils.*; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.Export; import io.serverlessworkflow.api.types.FlowDirective; import io.serverlessworkflow.api.types.Input; @@ -28,6 +27,7 @@ import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.jsonschema.SchemaValidator; @@ -83,26 +83,25 @@ protected AbstractTaskExecutorBuilder( this.resourceLoader = resourceLoader; if (task.getInput() != null) { Input input = task.getInput(); - this.inputProcessor = buildWorkflowFilter(application.expressionFactory(), input.getFrom()); + this.inputProcessor = buildWorkflowFilter(application, input.getFrom()); this.inputSchemaValidator = getSchemaValidator(application.validatorFactory(), resourceLoader, input.getSchema()); } if (task.getOutput() != null) { Output output = task.getOutput(); - this.outputProcessor = buildWorkflowFilter(application.expressionFactory(), output.getAs()); + this.outputProcessor = buildWorkflowFilter(application, output.getAs()); this.outputSchemaValidator = getSchemaValidator(application.validatorFactory(), resourceLoader, output.getSchema()); } if (task.getExport() != null) { Export export = task.getExport(); if (export.getAs() != null) { - this.contextProcessor = - buildWorkflowFilter(application.expressionFactory(), export.getAs()); + this.contextProcessor = buildWorkflowFilter(application, export.getAs()); } this.contextSchemaValidator = getSchemaValidator(application.validatorFactory(), resourceLoader, export.getSchema()); } - this.ifFilter = optionalFilter(application.expressionFactory(), task.getIf()); + this.ifFilter = optionalFilter(application, task.getIf()); } protected final TransitionInfoBuilder next( @@ -175,14 +174,14 @@ protected final CompletableFuture executeNext( @Override public CompletableFuture apply( - WorkflowContext workflowContext, Optional parentContext, JsonNode input) { + WorkflowContext workflowContext, Optional parentContext, WorkflowModel input) { TaskContext taskContext = new TaskContext(input, position, parentContext, taskName, task); CompletableFuture completable = CompletableFuture.completedFuture(taskContext); if (!TaskExecutorHelper.isActive(workflowContext)) { return completable; } if (ifFilter - .map(f -> f.apply(workflowContext, taskContext, input).asBoolean(true)) + .flatMap(f -> f.apply(workflowContext, taskContext, input).asBoolean()) .orElse(true)) { return executeNext( completable diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallTaskExecutor.java index 2a3d1ae9..56545b68 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallTaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallTaskExecutor.java @@ -15,14 +15,13 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.TaskBase; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; -import io.serverlessworkflow.impl.executors.RegularTaskExecutor.RegularTaskExecutorBuilder; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.concurrent.CompletableFuture; @@ -48,7 +47,7 @@ protected CallTaskExecutorBuilder( @Override public TaskExecutor buildInstance() { - return new CallTaskExecutor(this); + return new CallTaskExecutor<>(this); } } @@ -58,7 +57,7 @@ protected CallTaskExecutor(CallTaskExecutorBuilder builder) { } @Override - protected CompletableFuture internalExecute( + protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { return callable.apply(workflow, taskContext, taskContext.input()); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallableTask.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallableTask.java index ecff0662..e391dae6 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallableTask.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallableTask.java @@ -15,19 +15,19 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.TaskBase; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.concurrent.CompletableFuture; public interface CallableTask { void init(T task, WorkflowApplication application, ResourceLoader loader); - CompletableFuture apply( - WorkflowContext workflowContext, TaskContext taskContext, JsonNode input); + CompletableFuture apply( + WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input); boolean accept(Class clazz); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java index a35e4a87..65e3469b 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java @@ -15,12 +15,12 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.DoTask; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.Optional; @@ -57,7 +57,7 @@ private DoExecutor(DoExecutorBuilder builder) { } @Override - protected CompletableFuture internalExecute( + protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { return TaskExecutorHelper.processTaskList( taskExecutor, workflow, Optional.of(taskContext), taskContext.input()); diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java index 7a8eb09d..1c7f99df 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java @@ -15,10 +15,8 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.cloudevents.CloudEvent; import io.cloudevents.core.builder.CloudEventBuilder; -import io.cloudevents.jackson.JsonCloudEventData; import io.serverlessworkflow.api.types.EmitTask; import io.serverlessworkflow.api.types.EventData; import io.serverlessworkflow.api.types.EventDataschema; @@ -32,11 +30,10 @@ import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.events.CloudEventUtils; -import io.serverlessworkflow.impl.expressions.ExpressionFactory; -import io.serverlessworkflow.impl.json.JsonUtils; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.net.URI; import java.time.OffsetDateTime; @@ -61,8 +58,7 @@ protected EmitExecutorBuilder( ResourceLoader resourceLoader) { super(position, task, workflow, application, resourceLoader); this.eventBuilder = - EventPropertiesBuilder.build( - task.getEmit().getEvent().getWith(), application.expressionFactory()); + EventPropertiesBuilder.build(task.getEmit().getEvent().getWith(), application); } @Override @@ -77,7 +73,7 @@ private EmitExecutor(EmitExecutorBuilder builder) { } @Override - protected CompletableFuture internalExecute( + protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { return workflow .definition() @@ -125,19 +121,41 @@ private CloudEvent buildCloudEvent(WorkflowContext workflow, TaskContext taskCon props .dataFilter() .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) - .ifPresent(value -> ceBuilder.withData(JsonCloudEventData.wrap(value))); + .ifPresent( + value -> + ceBuilder.withData( + value + .asCloudEventData() + .orElseThrow( + () -> + new IllegalArgumentException( + "Workflow model " + + value + + " cannot be converted to CloudEvent")))); + // TODO JsonCloudEventData.wrap(value) props .additionalFilter() .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) - .ifPresent( - value -> - value - .fields() - .forEachRemaining( - e -> CloudEventUtils.addExtension(ceBuilder, e.getKey(), e.getValue()))); + .ifPresent(value -> value.forEach((k, v) -> addExtension(ceBuilder, k, v))); + return ceBuilder.build(); } + private static CloudEventBuilder addExtension( + CloudEventBuilder builder, String name, WorkflowModel value) { + value + .asText() + .ifPresentOrElse( + v -> builder.withExtension(name, v), + () -> + value + .asBoolean() + .ifPresentOrElse( + v -> builder.withExtension(name, v), + () -> value.asNumber().ifPresent(v -> builder.withExtension(name, v)))); + return builder; + } + private static record EventPropertiesBuilder( Optional idFilter, Optional sourceFilter, @@ -150,28 +168,27 @@ private static record EventPropertiesBuilder( Optional additionalFilter) { public static EventPropertiesBuilder build( - EventProperties properties, ExpressionFactory exprFactory) { - Optional idFilter = buildFilter(exprFactory, properties.getId()); + EventProperties properties, WorkflowApplication app) { + Optional idFilter = buildFilter(app, properties.getId()); EventSource source = properties.getSource(); Optional sourceFilter = source == null ? Optional.empty() : Optional.of( WorkflowUtils.buildStringFilter( - exprFactory, + app, source.getRuntimeExpression(), WorkflowUtils.toString(source.getUriTemplate()))); - Optional subjectFilter = buildFilter(exprFactory, properties.getSubject()); - Optional contentTypeFilter = - buildFilter(exprFactory, properties.getDatacontenttype()); - Optional typeFilter = buildFilter(exprFactory, properties.getType()); + Optional subjectFilter = buildFilter(app, properties.getSubject()); + Optional contentTypeFilter = buildFilter(app, properties.getDatacontenttype()); + Optional typeFilter = buildFilter(app, properties.getType()); EventDataschema dataSchema = properties.getDataschema(); Optional dataSchemaFilter = dataSchema == null ? Optional.empty() : Optional.of( WorkflowUtils.buildStringFilter( - exprFactory, + app, dataSchema.getExpressionDataSchema(), WorkflowUtils.toString(dataSchema.getLiteralDataSchema()))); EventTime time = properties.getTime(); @@ -180,22 +197,27 @@ public static EventPropertiesBuilder build( ? Optional.empty() : Optional.of( WorkflowUtils.buildExpressionHolder( - exprFactory, + app, time.getRuntimeExpression(), CloudEventUtils.toOffset(time.getLiteralTime()), - JsonUtils::toOffsetDateTime)); + v -> + v.asDate() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expression does not generate a valid date")))); EventData data = properties.getData(); Optional dataFilter = properties.getData() == null ? Optional.empty() : Optional.of( WorkflowUtils.buildWorkflowFilter( - exprFactory, data.getRuntimeExpression(), data.getObject())); + app, data.getRuntimeExpression(), data.getObject())); Map ceAttrs = properties.getAdditionalProperties(); Optional additionalFilter = ceAttrs == null || ceAttrs.isEmpty() ? Optional.empty() - : Optional.of(WorkflowUtils.buildWorkflowFilter(exprFactory, null, ceAttrs)); + : Optional.of(WorkflowUtils.buildWorkflowFilter(app, null, ceAttrs)); return new EventPropertiesBuilder( idFilter, sourceFilter, @@ -208,10 +230,10 @@ public static EventPropertiesBuilder build( additionalFilter); } - private static Optional buildFilter(ExpressionFactory exprFactory, String str) { + private static Optional buildFilter(WorkflowApplication appl, String str) { return str == null ? Optional.empty() - : Optional.of(WorkflowUtils.buildStringFilter(exprFactory, str)); + : Optional.of(WorkflowUtils.buildStringFilter(appl, str)); } } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java index 8f7e04f1..15b5e744 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java @@ -15,7 +15,6 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.ForTask; import io.serverlessworkflow.api.types.ForTaskConfiguration; import io.serverlessworkflow.api.types.Workflow; @@ -23,6 +22,7 @@ import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.executors.RegularTaskExecutor.RegularTaskExecutorBuilder; @@ -50,10 +50,8 @@ protected ForExecutorBuilder( ResourceLoader resourceLoader) { super(position, task, workflow, application, resourceLoader); ForTaskConfiguration forConfig = task.getFor(); - this.collectionExpr = - WorkflowUtils.buildWorkflowFilter(application.expressionFactory(), forConfig.getIn()); - this.whileExpr = - WorkflowUtils.optionalFilter(application.expressionFactory(), task.getWhile()); + this.collectionExpr = WorkflowUtils.buildWorkflowFilter(application, forConfig.getIn()); + this.whileExpr = WorkflowUtils.optionalFilter(application, task.getWhile()); this.taskExecutor = TaskExecutorHelper.createExecutorList( position, task.getDo(), workflow, application, resourceLoader); @@ -73,18 +71,19 @@ protected ForExecutor(ForExecutorBuilder builder) { } @Override - protected CompletableFuture internalExecute( + protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { - Iterator iter = - collectionExpr.apply(workflow, taskContext, taskContext.input()).iterator(); + Iterator iter = + collectionExpr.apply(workflow, taskContext, taskContext.input()).asCollection().iterator(); int i = 0; - CompletableFuture future = CompletableFuture.completedFuture(taskContext.input()); + CompletableFuture future = + CompletableFuture.completedFuture(taskContext.input()); while (iter.hasNext() && whileExpr - .map(w -> w.apply(workflow, taskContext, taskContext.rawOutput())) - .map(n -> n.asBoolean(true)) + .map(w -> w.apply(workflow, taskContext, taskContext.rawOutput())) + .map(n -> n.asBoolean().orElse(true)) .orElse(true)) { - JsonNode item = iter.next(); + WorkflowModel item = iter.next(); taskContext.variables().put(task.getFor().getEach(), item); taskContext.variables().put(task.getFor().getAt(), i++); future = diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForkExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForkExecutor.java index 85bd3f22..d92eb1a6 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForkExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForkExecutor.java @@ -15,16 +15,15 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.ForkTask; import io.serverlessworkflow.api.types.ForkTaskConfiguration; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.executors.RegularTaskExecutor.RegularTaskExecutorBuilder; -import io.serverlessworkflow.impl.json.JsonUtils; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.HashMap; import java.util.Map; @@ -39,6 +38,7 @@ public class ForkExecutor extends RegularTaskExecutor { private final ExecutorService service; private final Map> taskExecutors; + private final boolean compete; public static class ForkExecutorBuilder extends RegularTaskExecutorBuilder { @@ -74,7 +74,7 @@ protected ForkExecutor(ForkExecutorBuilder builder) { } @Override - protected CompletableFuture internalExecute( + protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { Map> futures = new HashMap<>(); CompletableFuture initial = CompletableFuture.completedFuture(taskContext); @@ -89,11 +89,12 @@ protected CompletableFuture internalExecute( .thenApply( i -> combine( + workflow, futures.entrySet().stream() .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().join())))); } - private JsonNode combine(Map futures) { + private WorkflowModel combine(WorkflowContext context, Map futures) { Stream> sortedStream = futures.entrySet().stream() @@ -102,9 +103,11 @@ private JsonNode combine(Map futures) { arg1.getValue().completedAt().compareTo(arg2.getValue().completedAt())); return compete ? sortedStream.map(e -> e.getValue().output()).findFirst().orElseThrow() - : sortedStream - .map( - e -> JsonUtils.mapper().createObjectNode().set(e.getKey(), e.getValue().output())) - .collect(JsonUtils.arrayNodeCollector()); + : context + .definition() + .application() + .modelFactory() + .combine( + sortedStream.collect(Collectors.toMap(Entry::getKey, e -> e.getValue().output()))); } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java index e351bae2..ebede8c1 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java @@ -15,8 +15,6 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import io.cloudevents.CloudEvent; import io.serverlessworkflow.api.types.AllEventConsumptionStrategy; import io.serverlessworkflow.api.types.AnyEventConsumptionStrategy; @@ -34,14 +32,14 @@ import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelCollection; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.WorkflowUtils; -import io.serverlessworkflow.impl.events.CloudEventUtils; import io.serverlessworkflow.impl.events.EventConsumer; import io.serverlessworkflow.impl.events.EventRegistration; import io.serverlessworkflow.impl.events.EventRegistrationBuilder; -import io.serverlessworkflow.impl.json.JsonUtils; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.ArrayList; import java.util.Collection; @@ -56,7 +54,7 @@ public abstract class ListenExecutor extends RegularTaskExecutor { protected final EventRegistrationBuilderCollection regBuilders; protected final Optional> loop; - protected final Function converter; + protected final Function converter; protected final EventConsumer eventConsumer; private static record EventRegistrationBuilderCollection( @@ -68,7 +66,8 @@ public static class ListenExecutorBuilder extends RegularTaskExecutorBuilder loop; - private Function converter = this::defaultCEConverter; + private Function converter = + ce -> application.modelFactory().from(ce.getData()); private EventRegistrationBuilderCollection allEvents(AllEventConsumptionStrategy allStrategy) { return new EventRegistrationBuilderCollection(from(allStrategy.getAll()), true); @@ -103,7 +102,7 @@ protected ListenExecutorBuilder( if (untilDesc.getAnyEventUntilCondition() != null) { until = WorkflowUtils.buildWorkflowFilter( - application.expressionFactory(), untilDesc.getAnyEventUntilCondition()); + application, untilDesc.getAnyEventUntilCondition()); } else if (untilDesc.getAnyEventUntilConsumed() != null) { EventConsumptionStrategy strategy = untilDesc.getAnyEventUntilConsumed(); if (strategy.getAllEventConsumptionStrategy() != null) { @@ -128,10 +127,10 @@ protected ListenExecutorBuilder( if (readAs != null) { switch (readAs) { case ENVELOPE: - converter = CloudEventUtils::toJsonNode; + converter = ce -> application.modelFactory().from(ce); default: case DATA: - converter = this::defaultCEConverter; + converter = ce -> application.modelFactory().from(ce.getData()); break; } } @@ -141,10 +140,6 @@ private Collection registerToAll() { return application.eventConsumer().listenToAll(application); } - private JsonNode defaultCEConverter(CloudEvent ce) { - return CloudEventUtils.toJsonNode(ce.getData()); - } - private Collection from(List filters) { return filters.stream().map(this::from).collect(Collectors.toList()); } @@ -166,11 +161,11 @@ public AndListenExecutor(ListenExecutorBuilder builder) { } protected void internalProcessCe( - JsonNode node, - ArrayNode arrayNode, + WorkflowModel node, + WorkflowModelCollection arrayNode, WorkflowContext workflow, TaskContext taskContext, - CompletableFuture future) { + CompletableFuture future) { arrayNode.add(node); future.complete(node); } @@ -208,16 +203,14 @@ protected CompletableFuture buildFuture( } protected void internalProcessCe( - JsonNode node, - ArrayNode arrayNode, + WorkflowModel node, + WorkflowModelCollection arrayNode, WorkflowContext workflow, TaskContext taskContext, - CompletableFuture future) { + CompletableFuture future) { arrayNode.add(node); if ((until.isEmpty() - || until - .filter(u -> u.apply(workflow, taskContext, arrayNode).asBoolean()) - .isPresent()) + || until.map(u -> u.apply(workflow, taskContext, arrayNode).asBoolean()).isPresent()) && untilRegBuilders == null) { future.complete(node); } @@ -225,22 +218,23 @@ protected void internalProcessCe( } protected abstract void internalProcessCe( - JsonNode node, - ArrayNode arrayNode, + WorkflowModel node, + WorkflowModelCollection arrayNode, WorkflowContext workflow, TaskContext taskContext, - CompletableFuture future); + CompletableFuture future); @Override - protected CompletableFuture internalExecute( + protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { - ArrayNode output = JsonUtils.mapper().createArrayNode(); + WorkflowModelCollection output = + workflow.definition().application().modelFactory().createCollection(); Collection registrations = new ArrayList<>(); workflow.instance().status(WorkflowStatus.WAITING); return buildFuture( regBuilders, registrations, - (BiConsumer>) + (BiConsumer>) ((ce, future) -> processCe(converter.apply(ce), output, workflow, taskContext, future))) .thenApply( @@ -282,11 +276,11 @@ private CompletableFuture toCompletable( } private void processCe( - JsonNode node, - ArrayNode arrayNode, + WorkflowModel node, + WorkflowModelCollection arrayNode, WorkflowContext workflow, TaskContext taskContext, - CompletableFuture future) { + CompletableFuture future) { loop.ifPresentOrElse( t -> { SubscriptionIterator forEach = task.getForeach(); diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java index 7a2c4025..27c9018f 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java @@ -15,7 +15,6 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.Error; import io.serverlessworkflow.api.types.ErrorInstance; import io.serverlessworkflow.api.types.ErrorType; @@ -28,9 +27,9 @@ import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowError; import io.serverlessworkflow.impl.WorkflowException; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowUtils; -import io.serverlessworkflow.impl.expressions.ExpressionFactory; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.Map; import java.util.Optional; @@ -61,17 +60,16 @@ protected RaiseExecutorBuilder( raiseError.getRaiseErrorDefinition() != null ? raiseError.getRaiseErrorDefinition() : findError(raiseError.getRaiseErrorReference()); - this.typeFilter = getTypeFunction(application.expressionFactory(), error.getType()); - this.instanceFilter = - getInstanceFunction(application.expressionFactory(), error.getInstance()); + this.typeFilter = getTypeFunction(application, error.getType()); + this.instanceFilter = getInstanceFunction(application, error.getInstance()); this.titleFilter = WorkflowUtils.buildStringFilter( - application.expressionFactory(), + application, error.getTitle().getExpressionErrorTitle(), error.getTitle().getLiteralErrorTitle()); this.detailFilter = WorkflowUtils.buildStringFilter( - application.expressionFactory(), + application, error.getDetail().getExpressionErrorDetails(), error.getTitle().getExpressionErrorTitle()); this.errorBuilder = (w, t) -> buildError(error, w, t); @@ -90,21 +88,19 @@ private WorkflowError buildError( } private Optional getInstanceFunction( - ExpressionFactory expressionFactory, ErrorInstance errorInstance) { + WorkflowApplication app, ErrorInstance errorInstance) { return errorInstance != null ? Optional.of( WorkflowUtils.buildStringFilter( - expressionFactory, + app, errorInstance.getExpressionErrorInstance(), errorInstance.getLiteralErrorInstance())) : Optional.empty(); } - private StringFilter getTypeFunction(ExpressionFactory expressionFactory, ErrorType type) { + private StringFilter getTypeFunction(WorkflowApplication app, ErrorType type) { return WorkflowUtils.buildStringFilter( - expressionFactory, - type.getExpressionErrorType(), - type.getLiteralErrorType().get().toString()); + app, type.getExpressionErrorType(), type.getLiteralErrorType().get().toString()); } private Error findError(String raiseErrorReference) { @@ -128,7 +124,7 @@ protected RaiseExecutor(RaiseExecutorBuilder builder) { } @Override - protected CompletableFuture internalExecute( + protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { throw new WorkflowException(errorBuilder.apply(workflow, taskContext)); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java index 7cac9a8e..c4a716c9 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java @@ -15,12 +15,12 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.TaskBase; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.Map; @@ -67,6 +67,6 @@ protected CompletableFuture execute( return future; } - protected abstract CompletableFuture internalExecute( + protected abstract CompletableFuture internalExecute( WorkflowContext workflow, TaskContext task); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java index f8373d39..9dc8c0a5 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java @@ -15,7 +15,6 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.Set; import io.serverlessworkflow.api.types.SetTask; import io.serverlessworkflow.api.types.SetTaskConfiguration; @@ -24,6 +23,7 @@ import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.resources.ResourceLoader; @@ -48,7 +48,7 @@ protected SetExecutorBuilder( SetTaskConfiguration setConfig = setInfo.getSetTaskConfiguration(); this.setFilter = WorkflowUtils.buildWorkflowFilter( - application.expressionFactory(), + application, setInfo.getString(), setConfig != null ? setConfig.getAdditionalProperties() : null); } @@ -65,7 +65,7 @@ private SetExecutor(SetExecutorBuilder builder) { } @Override - protected CompletableFuture internalExecute( + protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { return CompletableFuture.completedFuture( setFilter.apply(workflow, taskContext, taskContext.input())); diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java index 9bd3a74a..424d4c97 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java @@ -55,9 +55,7 @@ public SwitchExecutorBuilder( SwitchCase switchCase = item.getSwitchCase(); if (switchCase.getWhen() != null) { workflowFilters.put( - switchCase, - WorkflowUtils.buildWorkflowFilter( - application.expressionFactory(), switchCase.getWhen())); + switchCase, WorkflowUtils.buildWorkflowFilter(application, switchCase.getWhen())); } else { defaultDirective = switchCase.getThen(); } @@ -97,7 +95,11 @@ protected CompletableFuture execute( WorkflowContext workflow, TaskContext taskContext) { CompletableFuture future = CompletableFuture.completedFuture(taskContext); for (Entry entry : workflowFilters.entrySet()) { - if (entry.getKey().apply(workflow, taskContext, taskContext.input()).asBoolean()) { + if (entry + .getKey() + .apply(workflow, taskContext, taskContext.input()) + .asBoolean() + .orElse(false)) { return future.thenApply(t -> t.transition(entry.getValue())); } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java index b77398c3..ebf492f3 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java @@ -15,15 +15,15 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.TaskBase; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; import java.util.Optional; import java.util.concurrent.CompletableFuture; @FunctionalInterface public interface TaskExecutor { CompletableFuture apply( - WorkflowContext workflowContext, Optional parentContext, JsonNode input); + WorkflowContext workflowContext, Optional parentContext, WorkflowModel input); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java index 3fa77f5f..646a16f4 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java @@ -15,12 +15,12 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.resources.ResourceLoader; @@ -35,11 +35,11 @@ public class TaskExecutorHelper { private TaskExecutorHelper() {} - public static CompletableFuture processTaskList( + public static CompletableFuture processTaskList( TaskExecutor taskExecutor, WorkflowContext context, Optional parentTask, - JsonNode input) { + WorkflowModel input) { return taskExecutor .apply(context, parentTask, input) .thenApply( diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java index a4442bf2..b3efca9e 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java @@ -15,7 +15,6 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.CatchErrors; import io.serverlessworkflow.api.types.ErrorFilter; import io.serverlessworkflow.api.types.TaskItem; @@ -28,6 +27,7 @@ import io.serverlessworkflow.impl.WorkflowError; import io.serverlessworkflow.impl.WorkflowException; import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.resources.ResourceLoader; @@ -62,10 +62,8 @@ protected TryExecutorBuilder( super(position, task, workflow, application, resourceLoader); TryTaskCatch catchInfo = task.getCatch(); this.errorFilter = buildErrorFilter(catchInfo.getErrors()); - this.whenFilter = - WorkflowUtils.optionalFilter(application.expressionFactory(), catchInfo.getWhen()); - this.exceptFilter = - WorkflowUtils.optionalFilter(application.expressionFactory(), catchInfo.getExceptWhen()); + this.whenFilter = WorkflowUtils.optionalFilter(application, catchInfo.getWhen()); + this.exceptFilter = WorkflowUtils.optionalFilter(application, catchInfo.getExceptWhen()); this.taskExecutor = TaskExecutorHelper.createExecutorList( position, task.getTry(), workflow, application, resourceLoader); @@ -94,14 +92,14 @@ protected TryExecutor(TryExecutorBuilder builder) { } @Override - protected CompletableFuture internalExecute( + protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { return TaskExecutorHelper.processTaskList( taskExecutor, workflow, Optional.of(taskContext), taskContext.input()) .exceptionallyCompose(e -> handleException(e, workflow, taskContext)); } - private CompletableFuture handleException( + private CompletableFuture handleException( Throwable e, WorkflowContext workflow, TaskContext taskContext) { if (e instanceof CompletionException) { return handleException(e.getCause(), workflow, taskContext); @@ -110,10 +108,14 @@ private CompletableFuture handleException( WorkflowException exception = (WorkflowException) e; if (errorFilter.map(f -> f.test(exception.getWorflowError())).orElse(true) && whenFilter - .map(w -> w.apply(workflow, taskContext, taskContext.input()).asBoolean()) + .flatMap(w -> w.apply(workflow, taskContext, taskContext.input()).asBoolean()) .orElse(true) && exceptFilter - .map(w -> !w.apply(workflow, taskContext, taskContext.input()).asBoolean()) + .map( + w -> + !w.apply(workflow, taskContext, taskContext.input()) + .asBoolean() + .orElse(false)) .orElse(true)) { if (catchTaskExecutor.isPresent()) { return TaskExecutorHelper.processTaskList( diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java index 42e648aa..64ecde23 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java @@ -15,16 +15,15 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.DurationInline; import io.serverlessworkflow.api.types.WaitTask; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowStatus; -import io.serverlessworkflow.impl.executors.RegularTaskExecutor.RegularTaskExecutorBuilder; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.time.Duration; import java.util.concurrent.CompletableFuture; @@ -71,10 +70,10 @@ protected WaitExecutor(WaitExecutorBuilder builder) { } @Override - protected CompletableFuture internalExecute( + protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { workflow.instance().status(WorkflowStatus.WAITING); - return new CompletableFuture() + return new CompletableFuture() .completeOnTimeout(taskContext.output(), millisToWait.toMillis(), TimeUnit.MILLISECONDS) .thenApply( node -> { diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/Expression.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/Expression.java index 122fc6d8..f2e91ace 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/Expression.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/Expression.java @@ -15,10 +15,10 @@ */ package io.serverlessworkflow.impl.expressions; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; public interface Expression { - JsonNode eval(WorkflowContext workflowContext, TaskContext context, JsonNode node); + WorkflowModel eval(WorkflowContext workflowContext, TaskContext context, WorkflowModel model); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionFactory.java index 4d07d5af..696e4fda 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionFactory.java @@ -15,11 +15,18 @@ */ package io.serverlessworkflow.impl.expressions; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModelFactory; + public interface ExpressionFactory { /** * @throws ExpressionValidationException * @param expression * @return */ - Expression getExpression(String expression); + Expression buildExpression(String expression); + + WorkflowFilter buildFilter(String expr, Object value); + + WorkflowModelFactory modelFactory(); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionUtils.java index c91ef3a2..83f6fe1c 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionUtils.java @@ -15,10 +15,9 @@ */ package io.serverlessworkflow.impl.expressions; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.json.JsonUtils; +import io.serverlessworkflow.impl.WorkflowModel; import java.util.Map; public class ExpressionUtils { @@ -30,30 +29,17 @@ private ExpressionUtils() {} public static Map buildExpressionMap( Map origMap, ExpressionFactory factory) { - return new ProxyMap(origMap, o -> isExpr(o) ? factory.getExpression(o.toString()) : o); + return new ProxyMap(origMap, o -> isExpr(o) ? factory.buildExpression(o.toString()) : o); } public static Map evaluateExpressionMap( - Map origMap, WorkflowContext workflow, TaskContext task, JsonNode n) { + Map origMap, WorkflowContext workflow, TaskContext task, WorkflowModel n) { return new ProxyMap( - origMap, - o -> - o instanceof Expression - ? JsonUtils.toJavaValue(((Expression) o).eval(workflow, task, n)) - : o); + origMap, o -> o instanceof Expression ? ((Expression) o).eval(workflow, task, n) : o); } public static Object buildExpressionObject(Object obj, ExpressionFactory factory) { - return obj instanceof Map - ? ExpressionUtils.buildExpressionMap((Map) obj, factory) - : obj; - } - - public static Object evaluateExpressionObject( - Object obj, WorkflowContext workflow, TaskContext task, JsonNode node) { - return obj instanceof Map - ? ExpressionUtils.evaluateExpressionMap((Map) obj, workflow, task, node) - : obj; + return obj instanceof Map map ? ExpressionUtils.buildExpressionMap(map, factory) : obj; } public static boolean isExpr(Object expr) { diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpression.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpression.java index 6041515a..e55f9877 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpression.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpression.java @@ -15,10 +15,14 @@ */ package io.serverlessworkflow.impl.expressions; +import static io.serverlessworkflow.impl.json.JsonUtils.modelToJson; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; import io.serverlessworkflow.impl.json.JsonUtils; import java.util.function.Supplier; import net.thisptr.jackson.jq.Output; @@ -32,20 +36,24 @@ public class JQExpression implements Expression { private final Supplier scope; private final String expr; private final net.thisptr.jackson.jq.Expression internalExpr; + private final WorkflowModelFactory modelFactory; - public JQExpression(Supplier scope, String expr, Version version) + public JQExpression( + Supplier scope, String expr, Version version, WorkflowModelFactory modelFactory) throws JsonQueryException { this.expr = expr; this.scope = scope; this.internalExpr = ExpressionParser.compile(expr, version); + this.modelFactory = modelFactory; } @Override - public JsonNode eval(WorkflowContext workflow, TaskContext task, JsonNode node) { + public WorkflowModel eval(WorkflowContext workflow, TaskContext task, WorkflowModel model) { JsonNodeOutput output = new JsonNodeOutput(); + JsonNode node = modelToJson(model); try { internalExpr.apply(createScope(workflow, task), node, output); - return output.getResult(); + return modelFactory.fromAny(output.getResult()); } catch (JsonQueryException e) { throw new IllegalArgumentException( "Unable to evaluate content " + node + " using expr " + expr, e); @@ -78,13 +86,13 @@ public JsonNode getResult() { private Scope createScope(WorkflowContext workflow, TaskContext task) { Scope childScope = Scope.newChildScope(scope.get()); if (task != null) { - childScope.setValue("input", task.input()); - childScope.setValue("output", task.output()); + childScope.setValue("input", modelToJson(task.input())); + childScope.setValue("output", modelToJson(task.output())); childScope.setValue("task", () -> JsonUtils.fromValue(TaskDescriptor.of(task))); task.variables().forEach((k, v) -> childScope.setValue(k, JsonUtils.fromValue(v))); } if (workflow != null) { - childScope.setValue("context", workflow.context()); + childScope.setValue("context", modelToJson(workflow.context())); childScope.setValue( "runtime", () -> diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java index 0375224a..5d552934 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java @@ -15,18 +15,21 @@ */ package io.serverlessworkflow.impl.expressions; +import io.serverlessworkflow.impl.WorkflowModelFactory; import java.util.function.Supplier; import net.thisptr.jackson.jq.BuiltinFunctionLoader; import net.thisptr.jackson.jq.Scope; import net.thisptr.jackson.jq.Versions; import net.thisptr.jackson.jq.exception.JsonQueryException; -public class JQExpressionFactory implements ExpressionFactory { +public class JQExpressionFactory extends ObjectExpressionFactory { private JQExpressionFactory() {} private static final JQExpressionFactory instance = new JQExpressionFactory(); + private WorkflowModelFactory modelFactory = new JacksonModelFactory(); + public static JQExpressionFactory get() { return instance; } @@ -50,11 +53,17 @@ public Scope get() { } @Override - public Expression getExpression(String expression) { + public Expression buildExpression(String expression) { try { - return new JQExpression(scopeSupplier, ExpressionUtils.trimExpr(expression), Versions.JQ_1_6); + return new JQExpression( + scopeSupplier, ExpressionUtils.trimExpr(expression), Versions.JQ_1_6, modelFactory); } catch (JsonQueryException e) { throw new IllegalArgumentException(e); } } + + @Override + public WorkflowModelFactory modelFactory() { + return modelFactory; + } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java new file mode 100644 index 00000000..1deb520e --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java @@ -0,0 +1,125 @@ +/* + * 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.expressions; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.BooleanNode; +import com.fasterxml.jackson.databind.node.NullNode; +import io.cloudevents.CloudEventData; +import io.cloudevents.jackson.JsonCloudEventData; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.json.JsonUtils; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; + +@JsonSerialize(using = JacksonModelSerializer.class) +public class JacksonModel implements WorkflowModel { + + protected JsonNode node; + + public static final JacksonModel TRUE = new JacksonModel(BooleanNode.TRUE); + public static final JacksonModel FALSE = new JacksonModel(BooleanNode.FALSE); + public static final JacksonModel NULL = new JacksonModel(NullNode.instance); + + JacksonModel(JsonNode node) { + this.node = node; + } + + @Override + public void forEach(BiConsumer consumer) { + node.forEachEntry((k, v) -> consumer.accept(k, new JacksonModel(v))); + } + + @Override + public Optional asBoolean() { + return node.isBoolean() ? Optional.of(node.asBoolean()) : Optional.empty(); + } + + @Override + public Collection asCollection() { + return node.isArray() ? new JacksonModelCollection((ArrayNode) node) : Collections.emptyList(); + } + + @Override + public Optional asText() { + return node.isTextual() ? Optional.of(node.asText()) : Optional.empty(); + } + + @Override + public Optional asDate() { + if (node.isTextual()) { + return Optional.of(OffsetDateTime.parse(node.asText())); + } else if (node.isNumber()) { + return Optional.of( + OffsetDateTime.ofInstant(Instant.ofEpochMilli(node.asLong()), ZoneOffset.UTC)); + } else { + return Optional.empty(); + } + } + + @Override + public Optional asNumber() { + return node.isNumber() ? Optional.of(node.asLong()) : Optional.empty(); + } + + @Override + public Optional asCloudEventData() { + return node.isObject() ? Optional.of(JsonCloudEventData.wrap(node)) : Optional.empty(); + } + + @Override + public Optional as(Class clazz) { + return clazz.isAssignableFrom(node.getClass()) + ? Optional.of(clazz.cast(node)) + : Optional.of(JsonUtils.convertValue(node, clazz)); + } + + @Override + public String toString() { + return node.toPrettyString(); + } + + @Override + public Optional> asMap() { + // TODO use generic to avoid warning + return node.isObject() + ? Optional.of(JsonUtils.convertValue(node, Map.class)) + : Optional.empty(); + } + + @Override + public Object asJavaObject() { + return JsonUtils.toJavaValue(node); + } + + @Override + public Object asIs() { + return node; + } + + @Override + public Class objectClass() { + return node.getClass(); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java new file mode 100644 index 00000000..43da790a --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java @@ -0,0 +1,157 @@ +/* + * 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.expressions; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelCollection; +import io.serverlessworkflow.impl.json.JsonUtils; +import java.util.Collection; +import java.util.Iterator; +import java.util.Optional; + +public class JacksonModelCollection implements WorkflowModelCollection { + + private ArrayNode node; + + JacksonModelCollection() { + this.node = JsonUtils.array(); + } + + JacksonModelCollection(ArrayNode node) { + this.node = node; + } + + @Override + public Optional as(Class clazz) { + return clazz.isAssignableFrom(ArrayNode.class) + ? Optional.of(clazz.cast(node)) + : Optional.empty(); + } + + @Override + public int size() { + return node.size(); + } + + @Override + public boolean isEmpty() { + return node.isEmpty(); + } + + @Override + public boolean contains(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + return new WrapperIterator(node.iterator()); + } + + private class WrapperIterator implements Iterator { + + private Iterator iterator; + + public WrapperIterator(Iterator iterator) { + this.iterator = iterator; + } + + @Override + public boolean hasNext() { + + return iterator.hasNext(); + } + + @Override + public WorkflowModel next() { + return new JacksonModel(iterator.next()); + } + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public T[] toArray(T[] a) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean add(WorkflowModel e) { + node.add( + e.as(JsonNode.class).orElseThrow(() -> new IllegalArgumentException("Not a json node"))); + return true; + } + + @Override + public boolean remove(Object o) { + int size = node.size(); + node.removeIf(i -> i.equals(o)); + return node.size() < size; + } + + @Override + public boolean containsAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + c.forEach(this::add); + return true; + } + + @Override + public boolean removeAll(Collection c) { + int size = node.size(); + c.forEach(o -> node.removeIf(i -> i.equals(o))); + return node.size() < size; + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + node.removeAll(); + } + + @Override + public String toString() { + return node.toPrettyString(); + } + + @Override + public Object asJavaObject() { + return JsonUtils.toJavaValue(node); + } + + @Override + public Object asIs() { + return node; + } + + @Override + public Class objectClass() { + return ArrayNode.class; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java new file mode 100644 index 00000000..2c488707 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java @@ -0,0 +1,125 @@ +/* + * 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.expressions; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.DoubleNode; +import com.fasterxml.jackson.databind.node.FloatNode; +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.LongNode; +import com.fasterxml.jackson.databind.node.ShortNode; +import com.fasterxml.jackson.databind.node.TextNode; +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelCollection; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.events.CloudEventUtils; +import io.serverlessworkflow.impl.json.JsonUtils; +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.util.Map; + +public class JacksonModelFactory implements WorkflowModelFactory { + + @Override + public WorkflowModel combine(Map workflowVariables) { + return new JacksonModelCollection( + workflowVariables.entrySet().stream() + .map( + e -> + JsonUtils.object() + .set( + e.getKey(), + e.getValue() + .as(JsonNode.class) + .orElseThrow( + () -> + new IllegalArgumentException( + "Model cannot be converted ")))) + .collect(JsonUtils.arrayNodeCollector())); + } + + @Override + public WorkflowModelCollection createCollection() { + return new JacksonModelCollection(); + } + + @Override + public WorkflowModel from(boolean value) { + return value ? JacksonModel.TRUE : JacksonModel.FALSE; + } + + @Override + public WorkflowModel from(Number number) { + if (number instanceof Double value) { + return new JacksonModel(new DoubleNode(value)); + } else if (number instanceof BigDecimal) { + return new JacksonModel(new DoubleNode(number.doubleValue())); + } else if (number instanceof Float value) { + return new JacksonModel(new FloatNode(value)); + } else if (number instanceof Long value) { + return new JacksonModel(new LongNode(value)); + } else if (number instanceof Integer value) { + return new JacksonModel(new IntNode(value)); + } else if (number instanceof Short value) { + return new JacksonModel(new ShortNode(value)); + } else if (number instanceof Byte value) { + return new JacksonModel(new ShortNode(value)); + } else { + return new JacksonModel(new LongNode(number.longValue())); + } + } + + @Override + public WorkflowModel from(String value) { + return new JacksonModel(new TextNode(value)); + } + + @Override + public WorkflowModel from(CloudEvent ce) { + return new JacksonModel(CloudEventUtils.toJsonNode(ce)); + } + + @Override + public WorkflowModel from(CloudEventData ce) { + return new JacksonModel(CloudEventUtils.toJsonNode(ce)); + } + + @Override + public WorkflowModel from(OffsetDateTime value) { + return new JacksonModel(JsonUtils.fromValue(new TextNode(value.toString()))); + } + + @Override + public WorkflowModel from(Map map) { + return new JacksonModel(JsonUtils.fromValue(map)); + } + + @Override + public WorkflowModel fromAny(Object obj) { + if (obj instanceof JsonNode node) { + return new JacksonModel(node); + } else { + return WorkflowModelFactory.super.fromAny(obj); + } + } + + @Override + public WorkflowModel fromNull() { + return JacksonModel.NULL; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java new file mode 100644 index 00000000..874bd674 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java @@ -0,0 +1,36 @@ +/* + * 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.expressions; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; + +public class JacksonModelSerializer extends StdSerializer { + + private static final long serialVersionUID = 1L; + + protected JacksonModelSerializer() { + super(JacksonModel.class); + } + + @Override + public void serialize(JacksonModel value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeTree(value.node); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpressionFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpressionFactory.java new file mode 100644 index 00000000..f96894cc --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpressionFactory.java @@ -0,0 +1,37 @@ +/* + * 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.expressions; + +import io.serverlessworkflow.impl.WorkflowFilter; +import java.util.Map; + +public abstract class ObjectExpressionFactory implements ExpressionFactory { + + @Override + public WorkflowFilter buildFilter(String str, Object object) { + if (str != null) { + assert str != null; + Expression expression = buildExpression(str); + return expression::eval; + } else if (object != null) { + Object exprObj = ExpressionUtils.buildExpressionObject(object, this); + return exprObj instanceof Map map + ? (w, t, n) -> modelFactory().from(ExpressionUtils.evaluateExpressionMap(map, w, t, n)) + : (w, t, n) -> modelFactory().fromAny(object); + } + throw new IllegalArgumentException("Both object and str are null"); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/TaskDescriptor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/TaskDescriptor.java index f1e04cba..a4919002 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/TaskDescriptor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/TaskDescriptor.java @@ -15,16 +15,16 @@ */ package io.serverlessworkflow.impl.expressions; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.TaskBase; import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowModel; public record TaskDescriptor( String name, String reference, TaskBase definition, - JsonNode rawInput, - JsonNode rawOutput, + WorkflowModel rawInput, + WorkflowModel rawOutput, DateTimeDescriptor startedAt) { public static TaskDescriptor of(TaskContext context) { diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/WorkflowDescriptor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/WorkflowDescriptor.java index f6b906fb..cf87cfbb 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/WorkflowDescriptor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/WorkflowDescriptor.java @@ -15,13 +15,13 @@ */ package io.serverlessworkflow.impl.expressions; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.TaskBase; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; public record WorkflowDescriptor( - String id, Workflow definition, JsonNode input, DateTimeDescriptor startedAt) { + String id, Workflow definition, WorkflowModel input, DateTimeDescriptor startedAt) { public static WorkflowDescriptor of(WorkflowContext context) { return new WorkflowDescriptor( diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java index 37d5c668..42a32b7d 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ShortNode; import com.fasterxml.jackson.databind.node.TextNode; +import io.serverlessworkflow.impl.WorkflowModel; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; @@ -130,11 +131,24 @@ public static JsonNode fromValue(Object value) { return mapToArray((Collection) value); } else if (value instanceof Map) { return mapToNode((Map) value); + } else if (value instanceof WorkflowModel model) { + return modelToJson(model); } else { return mapper.convertValue(value, JsonNode.class); } } + public static JsonNode modelToJson(WorkflowModel model) { + return model == null + ? NullNode.instance + : model + .as(JsonNode.class) + .orElseThrow( + () -> + new IllegalArgumentException( + "Unable to convert model " + model + " to JsonNode")); + } + public static Object toJavaValue(Object object) { return object instanceof JsonNode ? toJavaValue((JsonNode) object) : object; } @@ -257,5 +271,9 @@ public static ObjectNode object() { return mapper.createObjectNode(); } + public static ArrayNode array() { + return mapper.createArrayNode(); + } + private JsonUtils() {} } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidator.java b/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidator.java index 8982908f..1530757c 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidator.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidator.java @@ -20,6 +20,7 @@ import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion.VersionFlag; import com.networknt.schema.ValidationMessage; +import io.serverlessworkflow.impl.WorkflowModel; import java.util.Set; public class DefaultSchemaValidator implements SchemaValidator { @@ -31,8 +32,14 @@ public DefaultSchemaValidator(JsonNode jsonNode) { } @Override - public void validate(JsonNode node) { - Set report = schemaObject.validate(node); + public void validate(WorkflowModel node) { + Set report = + schemaObject.validate( + node.as(JsonNode.class) + .orElseThrow( + () -> + new IllegalArgumentException( + "Default schema validator requires WorkflowModel to support conversion to json node"))); if (!report.isEmpty()) { StringBuilder sb = new StringBuilder("There are JsonSchema validation errors:"); report.forEach(m -> sb.append(System.lineSeparator()).append(m.getMessage())); diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidatorFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidatorFactory.java index 0f74e433..cc14b265 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidatorFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidatorFactory.java @@ -15,7 +15,14 @@ */ package io.serverlessworkflow.impl.jsonschema; -import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.serverlessworkflow.api.WorkflowFormat; +import io.serverlessworkflow.api.types.SchemaInline; +import io.serverlessworkflow.impl.json.JsonUtils; +import io.serverlessworkflow.impl.resources.StaticResource; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; public class DefaultSchemaValidatorFactory implements SchemaValidatorFactory { @@ -28,7 +35,17 @@ public static DefaultSchemaValidatorFactory get() { } @Override - public SchemaValidator getValidator(JsonNode node) { - return new DefaultSchemaValidator(node); + public SchemaValidator getValidator(SchemaInline inline) { + return new DefaultSchemaValidator(JsonUtils.fromValue(inline.getDocument())); + } + + @Override + public SchemaValidator getValidator(StaticResource resource) { + ObjectMapper mapper = WorkflowFormat.fromFileName(resource.name()).mapper(); + try (InputStream in = resource.open()) { + return new DefaultSchemaValidator(mapper.readTree(in)); + } catch (IOException io) { + throw new UncheckedIOException(io); + } } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidator.java b/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidator.java index d86a582f..9cce324e 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidator.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidator.java @@ -15,8 +15,8 @@ */ package io.serverlessworkflow.impl.jsonschema; -import com.fasterxml.jackson.databind.JsonNode; +import io.serverlessworkflow.impl.WorkflowModel; public interface SchemaValidator { - void validate(JsonNode node); + void validate(WorkflowModel node); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidatorFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidatorFactory.java index 52c29584..1581ecf3 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidatorFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidatorFactory.java @@ -15,8 +15,11 @@ */ package io.serverlessworkflow.impl.jsonschema; -import com.fasterxml.jackson.databind.JsonNode; +import io.serverlessworkflow.api.types.SchemaInline; +import io.serverlessworkflow.impl.resources.StaticResource; public interface SchemaValidatorFactory { - SchemaValidator getValidator(JsonNode node); + SchemaValidator getValidator(SchemaInline inline); + + SchemaValidator getValidator(StaticResource resource); } diff --git a/impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java b/impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java index 981b149d..f76dfd30 100644 --- a/impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java +++ b/impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java @@ -50,12 +50,12 @@ void testEventListened(String listen, String emit, JsonNode expectedResult, Obje WorkflowDefinition emitDefinition = appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath(emit)); WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); - CompletableFuture future = waitingInstance.start(); + CompletableFuture future = waitingInstance.start(); assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); emitDefinition.instance(emitInput).start().join(); assertThat(future).isCompleted(); assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.COMPLETED); - assertThat(waitingInstance.outputAsJsonNode()).isEqualTo(expectedResult); + assertThat(waitingInstance.outputAs(JsonNode.class)).isEqualTo(expectedResult); } @ParameterizedTest @@ -69,7 +69,7 @@ void testEventsListened(String listen, String emit1, String emit2, JsonNode expe WorkflowDefinition emitOutDefinition = appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath(emit2)); WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); - CompletableFuture future = waitingInstance.start(); + CompletableFuture future = waitingInstance.start(); assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); emitDoctorDefinition.instance(Map.of("temperature", 35)).start().join(); assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); @@ -78,7 +78,7 @@ void testEventsListened(String listen, String emit1, String emit2, JsonNode expe emitOutDefinition.instance(Map.of()).start().join(); assertThat(future).isCompleted(); assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.COMPLETED); - assertThat(waitingInstance.outputAsJsonNode()).isEqualTo(expectedResult); + assertThat(waitingInstance.outputAs(JsonNode.class)).isEqualTo(expectedResult); } private static Stream eventListenerParameters() { diff --git a/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java b/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java index 4a0a073f..bb600990 100644 --- a/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java +++ b/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java @@ -111,11 +111,14 @@ private static Arguments args( (Consumer) d -> instance.accept( - d.instance(input).start().thenApply(JsonUtils::toJavaValue).join())); + d.instance(input) + .start() + .thenApply(model -> JsonUtils.toJavaValue(JsonUtils.modelToJson(model))) + .join())); } private static Arguments argsJson( - String fileName, Map input, Consumer instance) { + String fileName, Map input, Consumer instance) { return Arguments.of( fileName, (Consumer) d -> instance.accept(d.instance(input).start().join())); @@ -140,7 +143,12 @@ private static void checkWorkflowException( consumer.accept(clazz.cast(ex.getCause())); } - private static void checkNotCompeteOuput(JsonNode out) { + private static void checkNotCompeteOuput(WorkflowModel model) { + JsonNode out = + model + .as(JsonNode.class) + .orElseThrow( + () -> new IllegalArgumentException("Model cannot be converted to json node")); assertThat(out).isInstanceOf(ArrayNode.class); assertThat(out).hasSize(2); ArrayNode array = (ArrayNode) out; diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java index d60c4655..cede1880 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java @@ -15,8 +15,6 @@ */ package io.serverlessworkflow.impl.executors; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.CallHTTP; import io.serverlessworkflow.api.types.Endpoint; import io.serverlessworkflow.api.types.EndpointUri; @@ -29,24 +27,20 @@ import io.serverlessworkflow.impl.WorkflowError; import io.serverlessworkflow.impl.WorkflowException; import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowUtils; 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.resources.ResourceLoader; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Invocation.Builder; import jakarta.ws.rs.client.WebTarget; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; public class HttpExecutor implements CallableTask { @@ -56,15 +50,17 @@ public class HttpExecutor implements CallableTask { private Optional headersMap; private Optional queryMap; private RequestSupplier requestFunction; + private static HttpModelConverter converter = new HttpModelConverter() {}; @FunctionalInterface private interface TargetSupplier { - WebTarget apply(WorkflowContext workflow, TaskContext task, JsonNode node); + WebTarget apply(WorkflowContext workflow, TaskContext task, WorkflowModel node); } @FunctionalInterface private interface RequestSupplier { - JsonNode apply(Builder request, WorkflowContext workflow, TaskContext task, JsonNode node); + WorkflowModel apply( + Builder request, WorkflowContext workflow, TaskContext task, WorkflowModel node); } @Override @@ -76,7 +72,7 @@ public void init(CallHTTP task, WorkflowApplication application, ResourceLoader httpArgs.getHeaders() != null ? Optional.of( WorkflowUtils.buildWorkflowFilter( - application.expressionFactory(), + application, httpArgs.getHeaders().getRuntimeExpression(), httpArgs.getHeaders().getHTTPHeaders() != null ? httpArgs.getHeaders().getHTTPHeaders().getAdditionalProperties() @@ -86,7 +82,7 @@ public void init(CallHTTP task, WorkflowApplication application, ResourceLoader httpArgs.getQuery() != null ? Optional.of( WorkflowUtils.buildWorkflowFilter( - application.expressionFactory(), + application, httpArgs.getQuery().getRuntimeExpression(), httpArgs.getQuery().getHTTPQuery() != null ? httpArgs.getQuery().getHTTPQuery().getAdditionalProperties() @@ -94,42 +90,55 @@ public void init(CallHTTP task, WorkflowApplication application, ResourceLoader : Optional.empty(); switch (httpArgs.getMethod().toUpperCase()) { case HttpMethod.POST: - Object body = - ExpressionUtils.buildExpressionObject( - httpArgs.getBody(), application.expressionFactory()); + WorkflowFilter bodyFilter = + WorkflowUtils.buildWorkflowFilter(application, null, httpArgs.getBody()); this.requestFunction = (request, workflow, context, node) -> - request.post( - Entity.json( - ExpressionUtils.evaluateExpressionObject(body, workflow, context, node)), - JsonNode.class); + converter.toModel( + application.modelFactory(), + request.post( + converter.toEntity(bodyFilter.apply(workflow, context, node)), + node.objectClass())); break; case HttpMethod.GET: default: - this.requestFunction = (request, w, t, n) -> request.get(JsonNode.class); + this.requestFunction = + (request, w, t, n) -> + converter.toModel(application.modelFactory(), request.get(n.objectClass())); } } - @Override - public CompletableFuture apply( - WorkflowContext workflow, TaskContext taskContext, JsonNode input) { - WebTarget target = targetSupplier.apply(workflow, taskContext, input); - Optional queryJson = queryMap.map(q -> q.apply(workflow, taskContext, input)); - if (queryJson.isPresent()) { - Iterator> iter = queryJson.orElseThrow().fields(); - while (iter.hasNext()) { - Entry item = iter.next(); - target = target.queryParam(item.getKey(), JsonUtils.toJavaValue(item.getValue())); - } + private static class TargetQuerySupplier implements Supplier { + + private WebTarget target; + + public TargetQuerySupplier(WebTarget original) { + this.target = original; } - Builder request = target.request(); + public void addQuery(String key, Object value) { + target = target.queryParam(key, value); + } + + public WebTarget get() { + return target; + } + } + + @Override + public CompletableFuture apply( + WorkflowContext workflow, TaskContext taskContext, WorkflowModel input) { + TargetQuerySupplier supplier = + new TargetQuerySupplier(targetSupplier.apply(workflow, taskContext, input)); + queryMap.ifPresent( + q -> + q.apply(workflow, taskContext, input) + .forEach((k, v) -> supplier.addQuery(k, v.asJavaObject()))); + Builder request = supplier.get().request(); headersMap.ifPresent( h -> h.apply(workflow, taskContext, input) - .fields() - .forEachRemaining( - e -> request.header(e.getKey(), JsonUtils.toJavaValue(e.getValue())))); + .forEach((k, v) -> request.header(k, v.asJavaObject()))); return CompletableFuture.supplyAsync( () -> { try { @@ -157,11 +166,11 @@ private static TargetSupplier getTargetSupplier( return getURISupplier(uri.getLiteralEndpointURI()); } else if (uri.getExpressionEndpointURI() != null) { return new ExpressionURISupplier( - expressionFactory.getExpression(uri.getExpressionEndpointURI())); + expressionFactory.buildExpression(uri.getExpressionEndpointURI())); } } else if (endpoint.getRuntimeExpression() != null) { return new ExpressionURISupplier( - expressionFactory.getExpression(endpoint.getRuntimeExpression())); + expressionFactory.buildExpression(endpoint.getRuntimeExpression())); } else if (endpoint.getUriTemplate() != null) { return getURISupplier(endpoint.getUriTemplate()); } @@ -173,10 +182,7 @@ private static TargetSupplier getURISupplier(UriTemplate template) { return (w, t, n) -> client.target(template.getLiteralUri()); } else if (template.getLiteralUriTemplate() != null) { return (w, t, n) -> - client - .target(template.getLiteralUriTemplate()) - .resolveTemplates( - JsonUtils.mapper().convertValue(n, new TypeReference>() {})); + client.target(template.getLiteralUriTemplate()).resolveTemplates(n.asMap().orElseThrow()); } throw new IllegalArgumentException("Invalid uritemplate definition " + template); } @@ -189,8 +195,13 @@ public ExpressionURISupplier(Expression expr) { } @Override - public WebTarget apply(WorkflowContext workflow, TaskContext task, JsonNode node) { - return client.target(expr.eval(workflow, task, node).asText()); + public WebTarget apply(WorkflowContext workflow, TaskContext task, WorkflowModel node) { + return client.target( + expr.eval(workflow, task, node) + .asText() + .orElseThrow( + () -> + new IllegalArgumentException("Target expression requires a string result"))); } } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/LongFilter.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpModelConverter.java similarity index 59% rename from impl/core/src/main/java/io/serverlessworkflow/impl/LongFilter.java rename to impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpModelConverter.java index cf5598e7..a8c264dd 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/LongFilter.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpModelConverter.java @@ -13,7 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl; +package io.serverlessworkflow.impl.executors; -@FunctionalInterface -public interface LongFilter extends ExpressionHolder {} +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import jakarta.ws.rs.client.Entity; + +public interface HttpModelConverter { + + default WorkflowModel toModel(WorkflowModelFactory factory, Object entity) { + return factory.fromAny(entity); + } + + default Entity toEntity(WorkflowModel model) { + return Entity.json(model.asIs()); + } +} diff --git a/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java b/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java index badb6403..d490859f 100644 --- a/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java +++ b/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java @@ -68,20 +68,20 @@ void testWrongSchema(String fileName) { .hasMessageContaining("There are JsonSchema validation errors"); } - private static boolean httpCondition(Object obj) { - Map map = (Map) obj; + private static boolean httpCondition(WorkflowModel obj) { + Map map = obj.asMap().orElseThrow(); return map.containsKey("photoUrls") || map.containsKey("petId"); } private static Stream provideParameters() { Map petInput = Map.of("petId", 10); Map starTrekInput = Map.of("uid", "MOMA0000092393"); - Condition petCondition = + Condition petCondition = new Condition<>(HTTPWorkflowDefinitionTest::httpCondition, "callHttpCondition"); - Condition starTrekCondition = + Condition starTrekCondition = new Condition<>( o -> - ((Map) ((Map) o).get("movie")) + ((Map) o.asMap().orElseThrow().get("movie")) .get("title") .equals("Star Trek"), "StartTrek"); @@ -90,8 +90,8 @@ private static Stream provideParameters() { Arguments.of( "callGetHttp.yaml", Map.of("petId", "-1"), - new Condition<>( - o -> ((Map) o).containsKey("petId"), "notFoundCondition")), + new Condition( + o -> o.asMap().orElseThrow().containsKey("petId"), "notFoundCondition")), Arguments.of("call-http-endpoint-interpolation.yaml", petInput, petCondition), Arguments.of("call-http-query-parameters.yaml", starTrekInput, starTrekCondition), Arguments.of( @@ -99,6 +99,7 @@ private static Stream provideParameters() { Arguments.of( "callPostHttp.yaml", Map.of("name", "Javierito", "surname", "Unknown"), - new Condition<>(o -> o.equals("Javierito"), "CallHttpPostCondition"))); + new Condition( + o -> o.asText().orElseThrow().equals("Javierito"), "CallHttpPostCondition"))); } } From 225b29bd5f4f740fe08a9bf50646b9a5098ad742 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Thu, 17 Jul 2025 11:50:53 +0200 Subject: [PATCH 2/2] [Fix #636] Relocating dependencies Signed-off-by: fjtirado --- examples/events/pom.xml | 2 +- examples/pom.xml | 5 ++ examples/simpleGet/pom.xml | 2 +- impl/core/pom.xml | 28 ++----- .../impl/WorkflowApplication.java | 44 +++++++++-- .../impl/WorkflowDefinition.java | 2 +- .../impl/WorkflowUtils.java | 4 +- .../impl/events/CloudEventUtils.java | 50 +----------- .../impl/executors/AbstractTaskExecutor.java | 2 +- .../impl/expressions/DateTimeDescriptor.java | 7 +- .../impl/resources/DynamicResource.java | 4 +- .../SchemaValidator.java | 2 +- .../SchemaValidatorFactory.java | 2 +- impl/http/pom.xml | 11 +++ .../impl/HTTPWorkflowDefinitionTest.java | 2 - impl/jackson/pom.xml | 57 ++++++++++++++ .../impl/events/JacksonCloudEventUtils.java | 78 +++++++++++++++++++ .../impl/expressions/JQExpression.java | 0 .../impl/expressions/JQExpressionFactory.java | 8 -- .../impl/expressions/JacksonModel.java | 0 .../expressions/JacksonModelCollection.java | 0 .../impl/expressions/JacksonModelFactory.java | 6 +- .../expressions/JacksonModelSerializer.java | 0 .../impl/json/JsonUtils.java | 0 .../impl/json/MergeUtils.java | 0 .../impl/schema/JsonSchemaValidator.java} | 6 +- .../schema/JsonSchemaValidatorFactory.java} | 16 +--- ...orkflow.impl.expressions.ExpressionFactory | 1 + ...orkflow.impl.schema.SchemaValidatorFactory | 1 + .../impl/EventDefinitionTest.java | 0 .../impl/WorkflowDefinitionTest.java | 0 .../src/test/resources/conditional-set.yaml | 0 .../src/test/resources/emit-doctor.yaml | 0 .../src/test/resources/emit-out.yaml | 0 .../src/test/resources/emit.yaml | 0 .../src/test/resources/for-collect.yaml | 0 .../src/test/resources/for-sum.yaml | 0 .../src/test/resources/fork-no-compete.yaml | 0 .../src/test/resources/fork.yaml | 0 .../src/test/resources/listen-to-all.yaml | 0 .../test/resources/listen-to-any-filter.yaml | 0 .../listen-to-any-until-consumed.yaml | 0 .../src/test/resources/listen-to-any.yaml | 0 .../src/test/resources/raise-inline copy.yaml | 0 .../src/test/resources/raise-reusable.yaml | 0 .../src/test/resources/simple-expression.yaml | 0 .../test/resources/switch-then-string.yaml | 0 impl/pom.xml | 8 +- pom.xml | 7 +- 49 files changed, 235 insertions(+), 120 deletions(-) rename impl/core/src/main/java/io/serverlessworkflow/impl/{jsonschema => schema}/SchemaValidator.java (94%) rename impl/core/src/main/java/io/serverlessworkflow/impl/{jsonschema => schema}/SchemaValidatorFactory.java (95%) create mode 100644 impl/jackson/pom.xml create mode 100644 impl/jackson/src/main/java/io/serverlessworkflow/impl/events/JacksonCloudEventUtils.java rename impl/{core => jackson}/src/main/java/io/serverlessworkflow/impl/expressions/JQExpression.java (100%) rename impl/{core => jackson}/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java (91%) rename impl/{core => jackson}/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java (100%) rename impl/{core => jackson}/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java (100%) rename impl/{core => jackson}/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java (95%) rename impl/{core => jackson}/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java (100%) rename impl/{core => jackson}/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java (100%) rename impl/{core => jackson}/src/main/java/io/serverlessworkflow/impl/json/MergeUtils.java (100%) rename impl/{core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidator.java => jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidator.java} (91%) rename impl/{core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidatorFactory.java => jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidatorFactory.java} (73%) create mode 100644 impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory create mode 100644 impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.schema.SchemaValidatorFactory rename impl/{core => jackson}/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java (100%) rename impl/{core => jackson}/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java (100%) rename impl/{core => jackson}/src/test/resources/conditional-set.yaml (100%) rename impl/{core => jackson}/src/test/resources/emit-doctor.yaml (100%) rename impl/{core => jackson}/src/test/resources/emit-out.yaml (100%) rename impl/{core => jackson}/src/test/resources/emit.yaml (100%) rename impl/{core => jackson}/src/test/resources/for-collect.yaml (100%) rename impl/{core => jackson}/src/test/resources/for-sum.yaml (100%) rename impl/{core => jackson}/src/test/resources/fork-no-compete.yaml (100%) rename impl/{core => jackson}/src/test/resources/fork.yaml (100%) rename impl/{core => jackson}/src/test/resources/listen-to-all.yaml (100%) rename impl/{core => jackson}/src/test/resources/listen-to-any-filter.yaml (100%) rename impl/{core => jackson}/src/test/resources/listen-to-any-until-consumed.yaml (100%) rename impl/{core => jackson}/src/test/resources/listen-to-any.yaml (100%) rename impl/{core => jackson}/src/test/resources/raise-inline copy.yaml (100%) rename impl/{core => jackson}/src/test/resources/raise-reusable.yaml (100%) rename impl/{core => jackson}/src/test/resources/simple-expression.yaml (100%) rename impl/{core => jackson}/src/test/resources/switch-then-string.yaml (100%) diff --git a/examples/events/pom.xml b/examples/events/pom.xml index 143a7967..439b3a11 100644 --- a/examples/events/pom.xml +++ b/examples/events/pom.xml @@ -11,7 +11,7 @@ io.serverlessworkflow - serverlessworkflow-impl-core + serverlessworkflow-impl-jackson org.slf4j diff --git a/examples/pom.xml b/examples/pom.xml index 238ee4b1..e393cb8d 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -21,6 +21,11 @@ serverlessworkflow-impl-http ${project.version} + + io.serverlessworkflow + serverlessworkflow-impl-jackson + ${project.version} + org.slf4j slf4j-simple diff --git a/examples/simpleGet/pom.xml b/examples/simpleGet/pom.xml index 923001ae..4d07f168 100644 --- a/examples/simpleGet/pom.xml +++ b/examples/simpleGet/pom.xml @@ -11,7 +11,7 @@ io.serverlessworkflow - serverlessworkflow-impl-core + serverlessworkflow-impl-jackson io.serverlessworkflow diff --git a/impl/core/pom.xml b/impl/core/pom.xml index 4140e747..2ea1eb9b 100644 --- a/impl/core/pom.xml +++ b/impl/core/pom.xml @@ -8,31 +8,23 @@ serverlessworkflow-impl-core Serverless Workflow :: Impl :: Core - - io.serverlessworkflow - serverlessworkflow-types - ${project.version} + + io.serverlessworkflow + serverlessworkflow-types + ${project.version} io.cloudevents - cloudevents-api + cloudevents-core - io.cloudevents - cloudevents-json-jackson + org.slf4j + slf4j-api com.github.f4b6a3 ulid-creator - - com.networknt - json-schema-validator - - - net.thisptr - jackson-jq - org.junit.jupiter junit-jupiter-api @@ -58,11 +50,5 @@ logback-classic test - - io.serverlessworkflow - serverlessworkflow-api - ${project.version} - - diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java index ab09dac7..0477ccf1 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -17,6 +17,7 @@ import com.github.f4b6a3.ulid.UlidCreator; import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.SchemaInline; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.events.EventConsumer; import io.serverlessworkflow.impl.events.EventPublisher; @@ -24,16 +25,17 @@ 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.expressions.RuntimeDescriptor; -import io.serverlessworkflow.impl.jsonschema.DefaultSchemaValidatorFactory; -import io.serverlessworkflow.impl.jsonschema.SchemaValidatorFactory; import io.serverlessworkflow.impl.resources.DefaultResourceLoaderFactory; import io.serverlessworkflow.impl.resources.ResourceLoaderFactory; +import io.serverlessworkflow.impl.resources.StaticResource; +import io.serverlessworkflow.impl.schema.SchemaValidator; +import io.serverlessworkflow.impl.schema.SchemaValidatorFactory; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; +import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -101,11 +103,31 @@ public WorkflowIdFactory idFactory() { } public static class Builder { + private static final SchemaValidatorFactory EMPTY_SCHEMA_VALIDATOR = + new SchemaValidatorFactory() { + + private final SchemaValidator NoValidation = + new SchemaValidator() { + @Override + public void validate(WorkflowModel node) {} + }; + + @Override + public SchemaValidator getValidator(StaticResource resource) { + + return NoValidation; + } + + @Override + public SchemaValidator getValidator(SchemaInline inline) { + return NoValidation; + } + }; private TaskExecutorFactory taskFactory = DefaultTaskExecutorFactory.get(); - private ExpressionFactory exprFactory = JQExpressionFactory.get(); + private ExpressionFactory exprFactory; private Collection listeners; private ResourceLoaderFactory resourceLoaderFactory = DefaultResourceLoaderFactory.get(); - private SchemaValidatorFactory schemaValidatorFactory = DefaultSchemaValidatorFactory.get(); + private SchemaValidatorFactory schemaValidatorFactory; private WorkflowPositionFactory positionFactory = () -> new QueueWorkflowPosition(); private WorkflowIdFactory idFactory = () -> UlidCreator.getMonotonicUlid().toString(); private ExecutorServiceFactory executorFactory = () -> Executors.newCachedThreadPool(); @@ -175,6 +197,18 @@ public Builder withEventPublisher(EventPublisher eventPublisher) { } public WorkflowApplication build() { + if (exprFactory == null) { + exprFactory = + ServiceLoader.load(ExpressionFactory.class) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Expression factory is required")); + } + if (schemaValidatorFactory == null) { + schemaValidatorFactory = + ServiceLoader.load(SchemaValidatorFactory.class) + .findFirst() + .orElse(EMPTY_SCHEMA_VALIDATOR); + } return new WorkflowApplication(this); } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java index 639f368d..404ecf07 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java @@ -22,8 +22,8 @@ import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.executors.TaskExecutor; import io.serverlessworkflow.impl.executors.TaskExecutorHelper; -import io.serverlessworkflow.impl.jsonschema.SchemaValidator; import io.serverlessworkflow.impl.resources.ResourceLoader; +import io.serverlessworkflow.impl.schema.SchemaValidator; import java.nio.file.Path; import java.util.Collection; import java.util.Optional; diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java index 069986df..d9bf2824 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -21,9 +21,9 @@ import io.serverlessworkflow.api.types.SchemaUnion; import io.serverlessworkflow.api.types.UriTemplate; import io.serverlessworkflow.impl.expressions.ExpressionUtils; -import io.serverlessworkflow.impl.jsonschema.SchemaValidator; -import io.serverlessworkflow.impl.jsonschema.SchemaValidatorFactory; import io.serverlessworkflow.impl.resources.ResourceLoader; +import io.serverlessworkflow.impl.schema.SchemaValidator; +import io.serverlessworkflow.impl.schema.SchemaValidatorFactory; import java.net.URI; import java.util.Optional; import java.util.function.Function; diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java index a5a5e04c..f2857ab0 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java @@ -15,15 +15,7 @@ */ package io.serverlessworkflow.impl.events; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.NullNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import io.cloudevents.CloudEvent; -import io.cloudevents.CloudEventData; -import io.cloudevents.jackson.JsonCloudEventData; -import io.serverlessworkflow.impl.json.JsonUtils; -import java.io.IOException; -import java.io.UncheckedIOException; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.Date; @@ -32,50 +24,12 @@ public class CloudEventUtils { - public static JsonNode toJsonNode(CloudEvent event) { - ObjectNode result = JsonUtils.mapper().createObjectNode(); - if (event.getData() != null) { - result.set("data", toJsonNode(event.getData())); - } - if (event.getSubject() != null) { - result.put("subject", event.getSubject()); - } - if (event.getDataContentType() != null) { - result.put("datacontenttype", event.getDataContentType()); - } - result.put("id", event.getId()); - result.put("source", event.getSource().toString()); - result.put("type", event.getType()); - result.put("specversion", event.getSpecVersion().toString()); - if (event.getDataSchema() != null) { - result.put("dataschema", event.getDataSchema().toString()); - } - if (event.getTime() != null) { - result.put("time", event.getTime().toString()); - } - event - .getExtensionNames() - .forEach(n -> result.set(n, JsonUtils.fromValue(event.getExtension(n)))); - return result; - } + private CloudEventUtils() {} public static OffsetDateTime toOffset(Date date) { return date.toInstant().atOffset(ZoneOffset.UTC); } - public static JsonNode toJsonNode(CloudEventData data) { - if (data == null) { - return NullNode.instance; - } - try { - return data instanceof JsonCloudEventData - ? ((JsonCloudEventData) data).getNode() - : JsonUtils.mapper().readTree(data.toBytes()); - } catch (IOException io) { - throw new UncheckedIOException(io); - } - } - public static Map extensions(CloudEvent event) { Map result = new LinkedHashMap<>(); for (String name : event.getExtensionNames()) { @@ -83,6 +37,4 @@ public static Map extensions(CloudEvent event) { } return result; } - - private CloudEventUtils() {} } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java index 18d23d85..414c82c6 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java @@ -30,8 +30,8 @@ import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowStatus; -import io.serverlessworkflow.impl.jsonschema.SchemaValidator; import io.serverlessworkflow.impl.resources.ResourceLoader; +import io.serverlessworkflow.impl.schema.SchemaValidator; import java.time.Instant; import java.util.Iterator; import java.util.Map; diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/DateTimeDescriptor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/DateTimeDescriptor.java index 7936763f..898476f8 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/DateTimeDescriptor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/DateTimeDescriptor.java @@ -15,7 +15,6 @@ */ package io.serverlessworkflow.impl.expressions; -import com.fasterxml.jackson.annotation.JsonProperty; import java.time.Instant; public class DateTimeDescriptor { @@ -30,13 +29,11 @@ private DateTimeDescriptor(Instant instant) { this.instant = instant; } - @JsonProperty("iso8601") - public String iso8601() { + public String getIso8601() { return instant.toString(); } - @JsonProperty("epoch") - public Epoch epoch() { + public Epoch getEpoch() { return Epoch.of(instant); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DynamicResource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DynamicResource.java index accac01e..cd8b1780 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DynamicResource.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DynamicResource.java @@ -15,12 +15,12 @@ */ package io.serverlessworkflow.impl.resources; -import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; import java.io.InputStream; import java.util.Optional; public interface DynamicResource { - InputStream open(WorkflowContext workflow, Optional task, JsonNode input); + InputStream open(WorkflowContext workflow, Optional task, WorkflowModel input); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidator.java b/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidator.java similarity index 94% rename from impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidator.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidator.java index 9cce324e..fa66676b 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidator.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.jsonschema; +package io.serverlessworkflow.impl.schema; import io.serverlessworkflow.impl.WorkflowModel; diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidatorFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidatorFactory.java similarity index 95% rename from impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidatorFactory.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidatorFactory.java index 1581ecf3..56b4b079 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/SchemaValidatorFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidatorFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.jsonschema; +package io.serverlessworkflow.impl.schema; import io.serverlessworkflow.api.types.SchemaInline; import io.serverlessworkflow.impl.resources.StaticResource; diff --git a/impl/http/pom.xml b/impl/http/pom.xml index 04f6f625..65c48aac 100644 --- a/impl/http/pom.xml +++ b/impl/http/pom.xml @@ -15,11 +15,22 @@ org.glassfish.jersey.media jersey-media-json-jackson + runtime io.serverlessworkflow serverlessworkflow-impl-core + + io.serverlessworkflow + serverlessworkflow-api + test + + + io.serverlessworkflow + serverlessworkflow-impl-jackson + test + org.junit.jupiter junit-jupiter-api diff --git a/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java b/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java index d490859f..fd1c575b 100644 --- a/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java +++ b/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java @@ -19,7 +19,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowableOfType; -import io.serverlessworkflow.impl.json.JsonUtils; import java.io.IOException; import java.util.Map; import java.util.stream.Stream; @@ -47,7 +46,6 @@ void testWorkflowExecution(String fileName, Object input, Condition cond appl.workflowDefinition(readWorkflowFromClasspath(fileName)) .instance(input) .start() - .thenApply(JsonUtils::toJavaValue) .join()) .is(condition); } diff --git a/impl/jackson/pom.xml b/impl/jackson/pom.xml new file mode 100644 index 00000000..babc6904 --- /dev/null +++ b/impl/jackson/pom.xml @@ -0,0 +1,57 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-impl + 8.0.0-SNAPSHOT + + serverlessworkflow-impl-jackson + Serverless Workflow :: Impl :: HTTP + + + io.serverlessworkflow + serverlessworkflow-impl-core + + + io.serverlessworkflow + serverlessworkflow-api + + + io.cloudevents + cloudevents-json-jackson + + + com.networknt + json-schema-validator + + + net.thisptr + jackson-jq + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.assertj + assertj-core + test + + + ch.qos.logback + logback-classic + test + + + \ No newline at end of file diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/events/JacksonCloudEventUtils.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/events/JacksonCloudEventUtils.java new file mode 100644 index 00000000..f51f4547 --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/events/JacksonCloudEventUtils.java @@ -0,0 +1,78 @@ +/* + * 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.events; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.cloudevents.jackson.JsonCloudEventData; +import io.serverlessworkflow.impl.json.JsonUtils; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Date; + +public class JacksonCloudEventUtils { + + public static JsonNode toJsonNode(CloudEvent event) { + ObjectNode result = JsonUtils.mapper().createObjectNode(); + if (event.getData() != null) { + result.set("data", toJsonNode(event.getData())); + } + if (event.getSubject() != null) { + result.put("subject", event.getSubject()); + } + if (event.getDataContentType() != null) { + result.put("datacontenttype", event.getDataContentType()); + } + result.put("id", event.getId()); + result.put("source", event.getSource().toString()); + result.put("type", event.getType()); + result.put("specversion", event.getSpecVersion().toString()); + if (event.getDataSchema() != null) { + result.put("dataschema", event.getDataSchema().toString()); + } + if (event.getTime() != null) { + result.put("time", event.getTime().toString()); + } + event + .getExtensionNames() + .forEach(n -> result.set(n, JsonUtils.fromValue(event.getExtension(n)))); + return result; + } + + public static OffsetDateTime toOffset(Date date) { + return date.toInstant().atOffset(ZoneOffset.UTC); + } + + public static JsonNode toJsonNode(CloudEventData data) { + if (data == null) { + return NullNode.instance; + } + try { + return data instanceof JsonCloudEventData + ? ((JsonCloudEventData) data).getNode() + : JsonUtils.mapper().readTree(data.toBytes()); + } catch (IOException io) { + throw new UncheckedIOException(io); + } + } + + private JacksonCloudEventUtils() {} +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpression.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JQExpression.java similarity index 100% rename from impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpression.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JQExpression.java diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java similarity index 91% rename from impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java index 5d552934..e5e9c481 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java @@ -24,16 +24,8 @@ public class JQExpressionFactory extends ObjectExpressionFactory { - private JQExpressionFactory() {} - - private static final JQExpressionFactory instance = new JQExpressionFactory(); - private WorkflowModelFactory modelFactory = new JacksonModelFactory(); - public static JQExpressionFactory get() { - return instance; - } - private static Supplier scopeSupplier = new DefaultScopeSupplier(); private static class DefaultScopeSupplier implements Supplier { diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java similarity index 100% rename from impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java similarity index 100% rename from impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java similarity index 95% rename from impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java index 2c488707..00906165 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java @@ -27,7 +27,7 @@ import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowModelCollection; import io.serverlessworkflow.impl.WorkflowModelFactory; -import io.serverlessworkflow.impl.events.CloudEventUtils; +import io.serverlessworkflow.impl.events.JacksonCloudEventUtils; import io.serverlessworkflow.impl.json.JsonUtils; import java.math.BigDecimal; import java.time.OffsetDateTime; @@ -91,12 +91,12 @@ public WorkflowModel from(String value) { @Override public WorkflowModel from(CloudEvent ce) { - return new JacksonModel(CloudEventUtils.toJsonNode(ce)); + return new JacksonModel(JacksonCloudEventUtils.toJsonNode(ce)); } @Override public WorkflowModel from(CloudEventData ce) { - return new JacksonModel(CloudEventUtils.toJsonNode(ce)); + return new JacksonModel(JacksonCloudEventUtils.toJsonNode(ce)); } @Override diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java similarity index 100% rename from impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java similarity index 100% rename from impl/core/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/json/MergeUtils.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/json/MergeUtils.java similarity index 100% rename from impl/core/src/main/java/io/serverlessworkflow/impl/json/MergeUtils.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/json/MergeUtils.java diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidator.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidator.java similarity index 91% rename from impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidator.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidator.java index 1530757c..142faf73 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidator.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.jsonschema; +package io.serverlessworkflow.impl.schema; import com.fasterxml.jackson.databind.JsonNode; import com.networknt.schema.JsonSchema; @@ -23,11 +23,11 @@ import io.serverlessworkflow.impl.WorkflowModel; import java.util.Set; -public class DefaultSchemaValidator implements SchemaValidator { +public class JsonSchemaValidator implements SchemaValidator { private final JsonSchema schemaObject; - public DefaultSchemaValidator(JsonNode jsonNode) { + public JsonSchemaValidator(JsonNode jsonNode) { this.schemaObject = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(jsonNode); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidatorFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidatorFactory.java similarity index 73% rename from impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidatorFactory.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidatorFactory.java index cc14b265..269bebb4 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidatorFactory.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidatorFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.jsonschema; +package io.serverlessworkflow.impl.schema; import com.fasterxml.jackson.databind.ObjectMapper; import io.serverlessworkflow.api.WorkflowFormat; @@ -24,26 +24,18 @@ import java.io.InputStream; import java.io.UncheckedIOException; -public class DefaultSchemaValidatorFactory implements SchemaValidatorFactory { - - private DefaultSchemaValidatorFactory() {} - - private static final DefaultSchemaValidatorFactory instance = new DefaultSchemaValidatorFactory(); - - public static DefaultSchemaValidatorFactory get() { - return instance; - } +public class JsonSchemaValidatorFactory implements SchemaValidatorFactory { @Override public SchemaValidator getValidator(SchemaInline inline) { - return new DefaultSchemaValidator(JsonUtils.fromValue(inline.getDocument())); + return new JsonSchemaValidator(JsonUtils.fromValue(inline.getDocument())); } @Override public SchemaValidator getValidator(StaticResource resource) { ObjectMapper mapper = WorkflowFormat.fromFileName(resource.name()).mapper(); try (InputStream in = resource.open()) { - return new DefaultSchemaValidator(mapper.readTree(in)); + return new JsonSchemaValidator(mapper.readTree(in)); } catch (IOException io) { throw new UncheckedIOException(io); } diff --git a/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory b/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory new file mode 100644 index 00000000..1853d536 --- /dev/null +++ b/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory @@ -0,0 +1 @@ +io.serverlessworkflow.impl.expressions.JQExpressionFactory \ No newline at end of file diff --git a/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.schema.SchemaValidatorFactory b/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.schema.SchemaValidatorFactory new file mode 100644 index 00000000..b4bc2dd0 --- /dev/null +++ b/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.schema.SchemaValidatorFactory @@ -0,0 +1 @@ +io.serverlessworkflow.impl.schema.JsonSchemaValidatorFactory \ No newline at end of file diff --git a/impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java b/impl/jackson/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java similarity index 100% rename from impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java rename to impl/jackson/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java diff --git a/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java b/impl/jackson/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java similarity index 100% rename from impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java rename to impl/jackson/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java diff --git a/impl/core/src/test/resources/conditional-set.yaml b/impl/jackson/src/test/resources/conditional-set.yaml similarity index 100% rename from impl/core/src/test/resources/conditional-set.yaml rename to impl/jackson/src/test/resources/conditional-set.yaml diff --git a/impl/core/src/test/resources/emit-doctor.yaml b/impl/jackson/src/test/resources/emit-doctor.yaml similarity index 100% rename from impl/core/src/test/resources/emit-doctor.yaml rename to impl/jackson/src/test/resources/emit-doctor.yaml diff --git a/impl/core/src/test/resources/emit-out.yaml b/impl/jackson/src/test/resources/emit-out.yaml similarity index 100% rename from impl/core/src/test/resources/emit-out.yaml rename to impl/jackson/src/test/resources/emit-out.yaml diff --git a/impl/core/src/test/resources/emit.yaml b/impl/jackson/src/test/resources/emit.yaml similarity index 100% rename from impl/core/src/test/resources/emit.yaml rename to impl/jackson/src/test/resources/emit.yaml diff --git a/impl/core/src/test/resources/for-collect.yaml b/impl/jackson/src/test/resources/for-collect.yaml similarity index 100% rename from impl/core/src/test/resources/for-collect.yaml rename to impl/jackson/src/test/resources/for-collect.yaml diff --git a/impl/core/src/test/resources/for-sum.yaml b/impl/jackson/src/test/resources/for-sum.yaml similarity index 100% rename from impl/core/src/test/resources/for-sum.yaml rename to impl/jackson/src/test/resources/for-sum.yaml diff --git a/impl/core/src/test/resources/fork-no-compete.yaml b/impl/jackson/src/test/resources/fork-no-compete.yaml similarity index 100% rename from impl/core/src/test/resources/fork-no-compete.yaml rename to impl/jackson/src/test/resources/fork-no-compete.yaml diff --git a/impl/core/src/test/resources/fork.yaml b/impl/jackson/src/test/resources/fork.yaml similarity index 100% rename from impl/core/src/test/resources/fork.yaml rename to impl/jackson/src/test/resources/fork.yaml diff --git a/impl/core/src/test/resources/listen-to-all.yaml b/impl/jackson/src/test/resources/listen-to-all.yaml similarity index 100% rename from impl/core/src/test/resources/listen-to-all.yaml rename to impl/jackson/src/test/resources/listen-to-all.yaml diff --git a/impl/core/src/test/resources/listen-to-any-filter.yaml b/impl/jackson/src/test/resources/listen-to-any-filter.yaml similarity index 100% rename from impl/core/src/test/resources/listen-to-any-filter.yaml rename to impl/jackson/src/test/resources/listen-to-any-filter.yaml diff --git a/impl/core/src/test/resources/listen-to-any-until-consumed.yaml b/impl/jackson/src/test/resources/listen-to-any-until-consumed.yaml similarity index 100% rename from impl/core/src/test/resources/listen-to-any-until-consumed.yaml rename to impl/jackson/src/test/resources/listen-to-any-until-consumed.yaml diff --git a/impl/core/src/test/resources/listen-to-any.yaml b/impl/jackson/src/test/resources/listen-to-any.yaml similarity index 100% rename from impl/core/src/test/resources/listen-to-any.yaml rename to impl/jackson/src/test/resources/listen-to-any.yaml diff --git a/impl/core/src/test/resources/raise-inline copy.yaml b/impl/jackson/src/test/resources/raise-inline copy.yaml similarity index 100% rename from impl/core/src/test/resources/raise-inline copy.yaml rename to impl/jackson/src/test/resources/raise-inline copy.yaml diff --git a/impl/core/src/test/resources/raise-reusable.yaml b/impl/jackson/src/test/resources/raise-reusable.yaml similarity index 100% rename from impl/core/src/test/resources/raise-reusable.yaml rename to impl/jackson/src/test/resources/raise-reusable.yaml diff --git a/impl/core/src/test/resources/simple-expression.yaml b/impl/jackson/src/test/resources/simple-expression.yaml similarity index 100% rename from impl/core/src/test/resources/simple-expression.yaml rename to impl/jackson/src/test/resources/simple-expression.yaml diff --git a/impl/core/src/test/resources/switch-then-string.yaml b/impl/jackson/src/test/resources/switch-then-string.yaml similarity index 100% rename from impl/core/src/test/resources/switch-then-string.yaml rename to impl/jackson/src/test/resources/switch-then-string.yaml diff --git a/impl/pom.xml b/impl/pom.xml index a82d3348..7e76bbf4 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -26,6 +26,11 @@ serverlessworkflow-impl-http ${project.version} + + io.serverlessworkflow + serverlessworkflow-impl-jackson + ${project.version} + org.glassfish.jersey.core jersey-client @@ -38,7 +43,7 @@ io.cloudevents - cloudevents-api + cloudevents-core ${version.io.cloudevents} @@ -61,5 +66,6 @@ http core + jackson \ No newline at end of file diff --git a/pom.xml b/pom.xml index b17bda17..732e84b0 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ annotations generators serialization + examples @@ -142,12 +143,16 @@ jackson-annotations ${version.com.fasterxml.jackson} - org.slf4j slf4j-api ${version.org.slf4j} + + io.serverlessworkflow + serverlessworkflow-api + ${project.version} + com.networknt json-schema-validator 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