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 844cf2a4..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-api - ${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 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..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); } } @@ -202,6 +236,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..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,9 +22,8 @@ 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 io.serverlessworkflow.impl.schema.SchemaValidator; import java.nio.file.Path; import java.util.Collection; import java.util.Optional; @@ -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..d9bf2824 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 io.serverlessworkflow.impl.schema.SchemaValidator; +import io.serverlessworkflow.impl.schema.SchemaValidatorFactory; 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..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,16 +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.core.builder.CloudEventBuilder; -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; @@ -33,62 +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 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; - } - 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()) { @@ -96,6 +37,4 @@ public static Map extensions(CloudEvent event) { } return result; } - - private CloudEventUtils() {} } 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..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 @@ -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,10 +27,11 @@ 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; import io.serverlessworkflow.impl.resources.ResourceLoader; +import io.serverlessworkflow.impl.schema.SchemaValidator; import java.time.Instant; import java.util.Iterator; import java.util.Map; @@ -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/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/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/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/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 83% 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 d86a582f..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,10 +13,10 @@ * 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 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/schema/SchemaValidatorFactory.java similarity index 71% 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 52c29584..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,10 +13,13 @@ * 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 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/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/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..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); } @@ -68,20 +66,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 +88,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 +97,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"))); } } 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 80% 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 index 6041515a..e55f9877 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/JQExpression.java +++ b/impl/jackson/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/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JQExpressionFactory.java similarity index 76% 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 0375224a..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 @@ -15,21 +15,16 @@ */ 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(); - - public static JQExpressionFactory get() { - return instance; - } + private WorkflowModelFactory modelFactory = new JacksonModelFactory(); private static Supplier scopeSupplier = new DefaultScopeSupplier(); @@ -50,11 +45,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/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModel.java new file mode 100644 index 00000000..1deb520e --- /dev/null +++ b/impl/jackson/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/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelCollection.java new file mode 100644 index 00000000..43da790a --- /dev/null +++ b/impl/jackson/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/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelFactory.java new file mode 100644 index 00000000..00906165 --- /dev/null +++ b/impl/jackson/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.JacksonCloudEventUtils; +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(JacksonCloudEventUtils.toJsonNode(ce)); + } + + @Override + public WorkflowModel from(CloudEventData ce) { + return new JacksonModel(JacksonCloudEventUtils.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/jsonschema/DefaultSchemaValidatorFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java similarity index 52% rename from impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidatorFactory.java rename to impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java index 0f74e433..874bd674 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/jsonschema/DefaultSchemaValidatorFactory.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/JacksonModelSerializer.java @@ -13,22 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.jsonschema; +package io.serverlessworkflow.impl.expressions; -import com.fasterxml.jackson.databind.JsonNode; +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 DefaultSchemaValidatorFactory implements SchemaValidatorFactory { +public class JacksonModelSerializer extends StdSerializer { - private DefaultSchemaValidatorFactory() {} + private static final long serialVersionUID = 1L; - private static final DefaultSchemaValidatorFactory instance = new DefaultSchemaValidatorFactory(); - - public static DefaultSchemaValidatorFactory get() { - return instance; + protected JacksonModelSerializer() { + super(JacksonModel.class); } @Override - public SchemaValidator getValidator(JsonNode node) { - return new DefaultSchemaValidator(node); + 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/json/JsonUtils.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java similarity index 94% 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 index 37d5c668..42a32b7d 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/json/JsonUtils.java +++ b/impl/jackson/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/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 69% 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 8982908f..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,26 +13,33 @@ * 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; 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 { +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); } @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/jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidatorFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidatorFactory.java new file mode 100644 index 00000000..269bebb4 --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/JsonSchemaValidatorFactory.java @@ -0,0 +1,43 @@ +/* + * 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.schema; + +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 JsonSchemaValidatorFactory implements SchemaValidatorFactory { + + @Override + public SchemaValidator getValidator(SchemaInline inline) { + 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 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 94% rename from impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java rename to impl/jackson/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/jackson/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/jackson/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java similarity index 92% rename from impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java rename to impl/jackson/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/jackson/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/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