diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java index 9ab57838..fb119ba1 100644 --- a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java @@ -32,6 +32,11 @@ class AgenticModel extends JavaModel { this.cognisphere = cognisphere; } + @Override + public void setObject(Object obj) { + super.setObject(obj); + } + @Override public Collection asCollection() { return object instanceof Collection value diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java index dc7c53ac..fe57b99e 100644 --- a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java @@ -26,12 +26,23 @@ import java.util.Map; class AgenticModelFactory implements WorkflowModelFactory { - private final Cognisphere cognisphere = CognisphereRegistry.createEphemeralCognisphere(); + + private Cognisphere cognisphere = CognisphereRegistry.createEphemeralCognisphere(); private final AgenticModel TrueModel = new AgenticModel(Boolean.TRUE, cognisphere); private final AgenticModel FalseModel = new AgenticModel(Boolean.FALSE, cognisphere); private final AgenticModel NullModel = new AgenticModel(null, cognisphere); + public void setCognishere(Cognisphere cognisphere) { + this.cognisphere = cognisphere; + } + + @Override + public WorkflowModel fromAny(WorkflowModel prev, Object obj) { + ((AgenticModel) prev).setObject(obj); + return prev; + } + @Override public WorkflowModel combine(Map workflowVariables) { return new AgenticModel(workflowVariables, cognisphere); diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaForExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaForExecutorBuilder.java index e427a222..8d66828b 100644 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaForExecutorBuilder.java +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaForExecutorBuilder.java @@ -23,12 +23,14 @@ import io.serverlessworkflow.api.types.func.ForTaskFunction; import io.serverlessworkflow.api.types.func.TypedFunction; import io.serverlessworkflow.impl.WorkflowApplication; -import io.serverlessworkflow.impl.WorkflowFilter; import io.serverlessworkflow.impl.WorkflowPosition; -import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.WorkflowPredicate; +import io.serverlessworkflow.impl.WorkflowValueResolver; import io.serverlessworkflow.impl.executors.ForExecutor.ForExecutorBuilder; +import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import io.serverlessworkflow.impl.expressions.LoopPredicateIndex; import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.Collection; import java.util.Optional; public class JavaForExecutorBuilder extends ForExecutorBuilder { @@ -42,7 +44,8 @@ protected JavaForExecutorBuilder( super(position, task, workflow, application, resourceLoader); } - protected Optional buildWhileFilter() { + @Override + protected Optional buildWhileFilter() { if (task instanceof ForTaskFunction taskFunctions) { final LoopPredicateIndex whilePred = taskFunctions.getWhilePredicate(); Optional> whileClass = taskFunctions.getWhileClass(); @@ -52,23 +55,21 @@ protected Optional buildWhileFilter() { return Optional.of( (w, t, n) -> { Object item = safeObject(t.variables().get(varName)); - return application - .modelFactory() - .from( - whilePred.test( - JavaFuncUtils.convert(n, whileClass), - item, - (Integer) safeObject(t.variables().get(indexName)))); + return whilePred.test( + JavaFuncUtils.convert(n, whileClass), + item, + (Integer) safeObject(t.variables().get(indexName))); }); } } return super.buildWhileFilter(); } - protected WorkflowFilter buildCollectionFilter() { + protected WorkflowValueResolver> buildCollectionFilter() { return task instanceof ForTaskFunction taskFunctions - ? WorkflowUtils.buildWorkflowFilter( - application, null, collectionFilterObject(taskFunctions)) + ? application + .expressionFactory() + .resolveCollection(ExpressionDescriptor.object(collectionFilterObject(taskFunctions))) : super.buildCollectionFilter(); } diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaSwitchExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaSwitchExecutorBuilder.java index 7762638d..118b4e9a 100644 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaSwitchExecutorBuilder.java +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaSwitchExecutorBuilder.java @@ -22,10 +22,10 @@ import io.serverlessworkflow.api.types.func.SwitchCaseFunction; import io.serverlessworkflow.api.types.func.TypedPredicate; import io.serverlessworkflow.impl.WorkflowApplication; -import io.serverlessworkflow.impl.WorkflowFilter; import io.serverlessworkflow.impl.WorkflowPosition; -import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.WorkflowPredicate; import io.serverlessworkflow.impl.executors.SwitchExecutor.SwitchExecutorBuilder; +import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.Optional; import java.util.function.Predicate; @@ -42,11 +42,14 @@ protected JavaSwitchExecutorBuilder( } @Override - protected Optional buildFilter(SwitchCase switchCase) { + protected Optional buildFilter(SwitchCase switchCase) { return switchCase instanceof SwitchCaseFunction function ? Optional.of( - WorkflowUtils.buildWorkflowFilter( - application, null, predObject(function.predicate(), function.predicateClass()))) + application + .expressionFactory() + .buildPredicate( + ExpressionDescriptor.object( + predObject(function.predicate(), function.predicateClass())))) : super.buildFilter(switchCase); } diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaExpressionFactory.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaExpressionFactory.java index 79bb6a8e..c2d4654d 100644 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaExpressionFactory.java +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaExpressionFactory.java @@ -15,75 +15,52 @@ */ package io.serverlessworkflow.impl.expressions.func; +import io.cloudevents.CloudEventData; import io.serverlessworkflow.api.types.TaskBase; import io.serverlessworkflow.api.types.TaskMetadata; import io.serverlessworkflow.api.types.func.TypedFunction; import io.serverlessworkflow.api.types.func.TypedPredicate; -import io.serverlessworkflow.impl.TaskContext; -import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowFilter; -import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowModelFactory; -import io.serverlessworkflow.impl.expressions.Expression; -import io.serverlessworkflow.impl.expressions.ExpressionFactory; +import io.serverlessworkflow.impl.WorkflowPredicate; +import io.serverlessworkflow.impl.expressions.AbstractExpressionFactory; +import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; +import io.serverlessworkflow.impl.expressions.ObjectExpression; import io.serverlessworkflow.impl.expressions.TaskMetadataKeys; +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Map; import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; -public class JavaExpressionFactory implements ExpressionFactory { +public class JavaExpressionFactory extends AbstractExpressionFactory { private final WorkflowModelFactory modelFactory = new JavaModelFactory(); - private final Expression dummyExpression = - new Expression() { - @Override - public WorkflowModel eval( - WorkflowContext workflowContext, TaskContext context, WorkflowModel model) { - return model; - } - }; @Override - public Expression buildExpression(String expression) { - return dummyExpression; - } - - @Override - public WorkflowFilter buildFilter(String expr, Object value) { + public ObjectExpression buildExpression(ExpressionDescriptor descriptor) { + Object value = descriptor.asObject(); if (value instanceof Function func) { - return (w, t, n) -> modelFactory.fromAny(func.apply(n.asJavaObject())); + return (w, t, n) -> func.apply(n.asJavaObject()); } else if (value instanceof TypedFunction func) { - return (w, t, n) -> - modelFactory.fromAny(func.function().apply(n.as(func.argClass()).orElseThrow())); - } else if (value instanceof Predicate pred) { - return fromPredicate(pred); - } else if (value instanceof TypedPredicate pred) { - return fromPredicate(pred); - } else if (value instanceof BiPredicate pred) { - return (w, t, n) -> modelFactory.from(pred.test(w, t)); - } else if (value instanceof BiFunction func) { - return (w, t, n) -> modelFactory.fromAny(func.apply(w, t)); - } else if (value instanceof WorkflowFilter filter) { - return filter; + return (w, t, n) -> func.function().apply(n.as(func.argClass()).orElseThrow()); } else { return (w, t, n) -> modelFactory.fromAny(value); } } @SuppressWarnings({"rawtypes", "unchecked"}) - private WorkflowFilter fromPredicate(Predicate pred) { - return (w, t, n) -> modelFactory.from(pred.test(n.asJavaObject())); + private WorkflowPredicate fromPredicate(Predicate pred) { + return (w, t, n) -> pred.test(n.asJavaObject()); } @SuppressWarnings({"rawtypes", "unchecked"}) - private WorkflowFilter fromPredicate(TypedPredicate pred) { - return (w, t, n) -> modelFactory.from(pred.pred().test(n.as(pred.argClass()).orElseThrow())); + private WorkflowPredicate fromPredicate(TypedPredicate pred) { + return (w, t, n) -> pred.pred().test(n.as(pred.argClass()).orElseThrow()); } @Override - public Optional buildIfFilter(TaskBase task) { + public Optional buildIfFilter(TaskBase task) { TaskMetadata metadata = task.getMetadata(); if (metadata != null) { Object obj = metadata.getAdditionalProperties().get(TaskMetadataKeys.IF_PREDICATE); @@ -93,11 +70,54 @@ public Optional buildIfFilter(TaskBase task) { return Optional.of(fromPredicate(pred)); } } - return ExpressionFactory.super.buildIfFilter(task); + return super.buildIfFilter(task); } @Override public WorkflowModelFactory modelFactory() { return modelFactory; } + + @Override + public WorkflowPredicate buildPredicate(ExpressionDescriptor desc) { + Object value = desc.asObject(); + if (value instanceof Predicate pred) { + return fromPredicate(pred); + } else if (value instanceof TypedPredicate pred) { + return fromPredicate(pred); + } else if (value instanceof Boolean bool) { + return (w, f, n) -> bool; + } else { + throw new IllegalArgumentException("value should be a predicate or a boolean"); + } + } + + @Override + protected String toString(Object eval) { + return asClass(eval, String.class); + } + + @Override + protected CloudEventData toCloudEventData(Object eval) { + return asClass(eval, CloudEventData.class); + } + + @Override + protected OffsetDateTime toDate(Object eval) { + return asClass(eval, OffsetDateTime.class); + } + + @Override + protected Map toMap(Object eval) { + return asClass(eval, Map.class); + } + + @Override + protected Collection toCollection(Object obj) { + return asClass(obj, Collection.class); + } + + private T asClass(Object obj, Class clazz) { + return clazz.cast(obj); + } } diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java index 11cff762..654276dc 100644 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java @@ -15,7 +15,6 @@ */ package io.serverlessworkflow.impl.expressions.func; -import io.cloudevents.CloudEventData; import io.serverlessworkflow.impl.WorkflowModel; import java.time.OffsetDateTime; import java.util.Collection; @@ -28,12 +27,16 @@ public class JavaModel implements WorkflowModel { - protected final Object object; + protected Object object; protected JavaModel(Object object) { this.object = asJavaObject(object); } + protected void setObject(Object object) { + this.object = object; + } + @Override public void forEach(BiConsumer consumer) { asMap() @@ -72,11 +75,6 @@ public Optional asNumber() { return object instanceof Number value ? Optional.of(value) : Optional.empty(); } - @Override - public Optional asCloudEventData() { - return object instanceof CloudEventData value ? Optional.of(value) : Optional.empty(); - } - @Override public Optional> asMap() { return object instanceof Map ? Optional.of((Map) object) : Optional.empty(); diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModelFactory.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModelFactory.java index 2ccc0dcd..c314bea7 100644 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModelFactory.java +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModelFactory.java @@ -79,7 +79,7 @@ public WorkflowModel fromNull() { } @Override - public WorkflowModel fromAny(Object obj) { + public WorkflowModel fromOther(Object obj) { return new JavaModel(obj); } } 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 04c34d29..71f5351f 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowFilter.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowFilter.java @@ -17,5 +17,5 @@ @FunctionalInterface public interface WorkflowFilter { - WorkflowModel apply(WorkflowContext workflow, TaskContext task, WorkflowModel node); + WorkflowModel apply(WorkflowContext workflow, TaskContext task, WorkflowModel model); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java index dc45896f..85634228 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java @@ -15,7 +15,6 @@ */ package io.serverlessworkflow.impl; -import io.cloudevents.CloudEventData; import java.time.OffsetDateTime; import java.util.Collection; import java.util.Map; @@ -36,8 +35,6 @@ public interface WorkflowModel { Optional asNumber(); - Optional asCloudEventData(); - Optional> asMap(); Object asJavaObject(); diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java index 25564bc0..1475b71e 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java @@ -47,6 +47,10 @@ default WorkflowModel fromOther(Object obj) { "Unsupported conversion for object " + obj + " of type" + obj.getClass()); } + default WorkflowModel fromAny(WorkflowModel prev, Object obj) { + return fromAny(obj); + } + default WorkflowModel fromAny(Object obj) { if (obj == null) { return fromNull(); @@ -66,6 +70,10 @@ default WorkflowModel fromAny(Object obj) { return from((Map) obj); } else if (obj instanceof WorkflowModel model) { return model; + } else if (obj instanceof CloudEventData ce) { + return from(ce); + } else if (obj instanceof CloudEvent ce) { + return from(ce); } else { return fromOther(obj); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/StringFilter.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowPredicate.java similarity index 85% rename from impl/core/src/main/java/io/serverlessworkflow/impl/StringFilter.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowPredicate.java index 2fbec647..cb4ff3f0 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/StringFilter.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowPredicate.java @@ -15,5 +15,6 @@ */ package io.serverlessworkflow.impl; -@FunctionalInterface -public interface StringFilter extends ExpressionHolder {} +public interface WorkflowPredicate { + boolean test(WorkflowContext workflow, TaskContext task, WorkflowModel model); +} 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 4a343e3c..db056676 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -20,13 +20,14 @@ import io.serverlessworkflow.api.types.OutputAs; import io.serverlessworkflow.api.types.SchemaUnion; import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import io.serverlessworkflow.impl.expressions.ExpressionUtils; import io.serverlessworkflow.impl.resources.ResourceLoader; import io.serverlessworkflow.impl.schema.SchemaValidator; import io.serverlessworkflow.impl.schema.SchemaValidatorFactory; import java.net.URI; +import java.util.Map; import java.util.Optional; -import java.util.function.Function; public class WorkflowUtils { @@ -60,62 +61,72 @@ public static Optional buildWorkflowFilter(WorkflowApplication a : Optional.empty(); } - public static ExpressionHolder buildExpressionHolder( - WorkflowApplication app, String expression, T literal, Function converter) { - return expression != null - ? buildExpressionHolder(buildWorkflowFilter(app, expression), converter) - : buildExpressionHolder(literal); - } - - private static ExpressionHolder buildExpressionHolder( - WorkflowFilter filter, Function converter) { - return (w, t) -> converter.apply(filter.apply(w, t, t.input())); - } - - private static ExpressionHolder buildExpressionHolder(T literal) { - return (w, t) -> literal; - } - public static Optional buildWorkflowFilter(WorkflowApplication app, ExportAs as) { return as != null ? Optional.of(buildWorkflowFilter(app, as.getString(), as.getObject())) : Optional.empty(); } - public static StringFilter buildStringFilter( + public static WorkflowValueResolver buildStringFilter( WorkflowApplication app, String expression, String literal) { - return expression != null ? toString(buildWorkflowFilter(app, expression)) : toString(literal); + return expression != null ? toExprString(app, expression) : toString(literal); + } + + public static WorkflowValueResolver buildStringFilter( + WorkflowApplication app, String str) { + return ExpressionUtils.isExpr(str) ? toExprString(app, str) : toString(str); } - public static StringFilter buildStringFilter(WorkflowApplication app, String str) { - return ExpressionUtils.isExpr(str) ? toString(buildWorkflowFilter(app, str)) : toString(str); + public static WorkflowValueResolver buildCollectionFilter( + WorkflowApplication app, String expression) { + return expression != null ? toExprString(app, expression) : toString(expression); } - private static StringFilter toString(WorkflowFilter filter) { - return (w, t) -> - filter - .apply(w, t, t.input()) - .asText() - .orElseThrow(() -> new IllegalArgumentException("Result is not an string")); + private static WorkflowValueResolver toExprString( + WorkflowApplication app, String expression) { + return app.expressionFactory().resolveString(ExpressionDescriptor.from(expression)); } - private static StringFilter toString(String literal) { - return (w, t) -> literal; + private static WorkflowValueResolver toString(String literal) { + return (w, t, m) -> literal; } public static WorkflowFilter buildWorkflowFilter( WorkflowApplication app, String str, Object object) { - return app.expressionFactory().buildFilter(str, object); + return app.expressionFactory().buildFilter(new ExpressionDescriptor(str, object)); + } + + public static WorkflowValueResolver buildStringResolver( + WorkflowApplication app, String str) { + return app.expressionFactory().resolveString(ExpressionDescriptor.from(str)); + } + + public static WorkflowValueResolver buildStringResolver( + WorkflowApplication app, String str, Object obj) { + return app.expressionFactory().resolveString(new ExpressionDescriptor(str, obj)); + } + + public static WorkflowValueResolver> buildMapResolver( + WorkflowApplication app, String str, Object obj) { + return app.expressionFactory().resolveMap(new ExpressionDescriptor(str, obj)); } public static WorkflowFilter buildWorkflowFilter(WorkflowApplication app, String str) { - return app.expressionFactory().buildFilter(str, null); + return app.expressionFactory().buildFilter(ExpressionDescriptor.from(str)); + } + + public static WorkflowPredicate buildPredicate(WorkflowApplication app, String str) { + return app.expressionFactory().buildPredicate(ExpressionDescriptor.from(str)); } public static Optional optionalFilter(WorkflowApplication app, String str) { return str != null ? Optional.of(buildWorkflowFilter(app, str)) : Optional.empty(); } + public static Optional optionalPredicate(WorkflowApplication app, String str) { + return str != null ? Optional.of(buildPredicate(app, str)) : Optional.empty(); + } + public static Optional optionalFilter( WorkflowApplication app, Object obj, String str) { return str != null || obj != null diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/ExpressionHolder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowValueResolver.java similarity index 83% rename from impl/core/src/main/java/io/serverlessworkflow/impl/ExpressionHolder.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowValueResolver.java index f899f186..5b08f39b 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/ExpressionHolder.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowValueResolver.java @@ -15,6 +15,6 @@ */ package io.serverlessworkflow.impl; -import java.util.function.BiFunction; - -public interface ExpressionHolder extends BiFunction {} +public interface WorkflowValueResolver { + T apply(WorkflowContext workflow, TaskContext task, WorkflowModel model); +} 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 ee319727..a67cf36f 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 @@ -24,10 +24,9 @@ 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.WorkflowPredicate; +import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import java.net.URI; import java.time.OffsetDateTime; import java.util.Map; @@ -65,18 +64,20 @@ private CloudEventAttrPredicate> additionalFilter( Map additionalProperties, WorkflowApplication app) { return additionalProperties != null && !additionalProperties.isEmpty() ? fromMap( - app.modelFactory(), WorkflowUtils.buildWorkflowFilter(app, null, additionalProperties)) + app.modelFactory(), + app.expressionFactory() + .buildPredicate(ExpressionDescriptor.object(additionalProperties))) : isTrue(); } private CloudEventAttrPredicate fromCloudEvent( - WorkflowModelFactory workflowModelFactory, WorkflowFilter filter) { - return d -> filter.apply(null, null, workflowModelFactory.from(d)).asBoolean().orElse(false); + WorkflowModelFactory workflowModelFactory, WorkflowPredicate filter) { + return d -> filter.test(null, null, workflowModelFactory.from(d)); } private CloudEventAttrPredicate> fromMap( - WorkflowModelFactory workflowModelFactory, WorkflowFilter filter) { - return d -> filter.apply(null, null, workflowModelFactory.from(d)).asBoolean().orElse(false); + WorkflowModelFactory workflowModelFactory, WorkflowPredicate filter) { + return d -> filter.test(null, null, workflowModelFactory.from(d)); } private CloudEventAttrPredicate dataFilter( @@ -84,7 +85,9 @@ private CloudEventAttrPredicate dataFilter( return data != null ? fromCloudEvent( app.modelFactory(), - WorkflowUtils.buildWorkflowFilter(app, data.getRuntimeExpression(), data.getObject())) + app.expressionFactory() + .buildPredicate( + new ExpressionDescriptor(data.getRuntimeExpression(), data.getObject()))) : isTrue(); } @@ -92,8 +95,9 @@ private CloudEventAttrPredicate offsetTimeFilter( EventTime time, WorkflowApplication app) { if (time != null) { if (time.getRuntimeExpression() != null) { - final Expression expr = - app.expressionFactory().buildExpression(time.getRuntimeExpression()); + final WorkflowPredicate expr = + app.expressionFactory() + .buildPredicate(ExpressionDescriptor.from(time.getRuntimeExpression())); return s -> evalExpr(app.modelFactory(), expr, s); } else if (time.getLiteralTime() != null) { return s -> Objects.equals(s, CloudEventUtils.toOffset(time.getLiteralTime())); @@ -106,8 +110,9 @@ private CloudEventAttrPredicate dataSchemaFilter( EventDataschema dataSchema, WorkflowApplication app) { if (dataSchema != null) { if (dataSchema.getExpressionDataSchema() != null) { - final Expression expr = - app.expressionFactory().buildExpression(dataSchema.getExpressionDataSchema()); + final WorkflowPredicate expr = + app.expressionFactory() + .buildPredicate(ExpressionDescriptor.from(dataSchema.getExpressionDataSchema())); return s -> evalExpr(app.modelFactory(), expr, toString(s)); } else if (dataSchema.getLiteralDataSchema() != null) { return templateFilter(dataSchema.getLiteralDataSchema()); @@ -123,8 +128,9 @@ private CloudEventAttrPredicate stringFilter(String str) { private CloudEventAttrPredicate sourceFilter(EventSource source, WorkflowApplication app) { if (source != null) { if (source.getRuntimeExpression() != null) { - final Expression expr = - app.expressionFactory().buildExpression(source.getRuntimeExpression()); + final WorkflowPredicate expr = + app.expressionFactory() + .buildPredicate(ExpressionDescriptor.from(source.getRuntimeExpression())); return s -> evalExpr(app.modelFactory(), expr, toString(s)); } else if (source.getUriTemplate() != null) { return templateFilter(source.getUriTemplate()); @@ -144,13 +150,14 @@ private String toString(T uri) { return uri != null ? uri.toString() : null; } - 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, WorkflowPredicate expr, String value) { + return expr.test(null, null, modelFactory.from(value)); } private boolean evalExpr( - WorkflowModelFactory modelFactory, Expression expr, OffsetDateTime value) { - return expr.eval(null, null, modelFactory.from(value)).asBoolean().orElse(false); + WorkflowModelFactory modelFactory, WorkflowPredicate expr, OffsetDateTime value) { + return expr.test(null, null, modelFactory.from(value)); } @Override 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 a9681f39..d5a0f055 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java @@ -30,6 +30,7 @@ import io.serverlessworkflow.impl.WorkflowFilter; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowPredicate; import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.resources.ResourceLoader; import io.serverlessworkflow.impl.schema.SchemaValidator; @@ -50,14 +51,14 @@ public abstract class AbstractTaskExecutor implements TaskEx private final Optional inputSchemaValidator; private final Optional outputSchemaValidator; private final Optional contextSchemaValidator; - private final Optional ifFilter; + private final Optional ifFilter; public abstract static class AbstractTaskExecutorBuilder implements TaskExecutorBuilder { private Optional inputProcessor = Optional.empty(); private Optional outputProcessor = Optional.empty(); private Optional contextProcessor = Optional.empty(); - private Optional ifFilter = Optional.empty(); + private Optional ifFilter = Optional.empty(); private Optional inputSchemaValidator = Optional.empty(); private Optional outputSchemaValidator = Optional.empty(); private Optional contextSchemaValidator = Optional.empty(); @@ -181,9 +182,7 @@ public CompletableFuture apply( if (!TaskExecutorHelper.isActive(workflowContext)) { return completable; } - if (ifFilter - .flatMap(f -> f.apply(workflowContext, taskContext, input).asBoolean()) - .orElse(true)) { + if (ifFilter.map(f -> f.test(workflowContext, taskContext, input)).orElse(true)) { return executeNext( completable .thenApply( 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 1c7f99df..8ce6c730 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 @@ -16,6 +16,7 @@ package io.serverlessworkflow.impl.executors; import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; import io.cloudevents.core.builder.CloudEventBuilder; import io.serverlessworkflow.api.types.EmitTask; import io.serverlessworkflow.api.types.EventData; @@ -24,16 +25,15 @@ import io.serverlessworkflow.api.types.EventSource; import io.serverlessworkflow.api.types.EventTime; import io.serverlessworkflow.api.types.Workflow; -import io.serverlessworkflow.impl.ExpressionHolder; -import io.serverlessworkflow.impl.StringFilter; import io.serverlessworkflow.impl.TaskContext; 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.WorkflowValueResolver; import io.serverlessworkflow.impl.events.CloudEventUtils; +import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.net.URI; import java.time.OffsetDateTime; @@ -88,51 +88,40 @@ private CloudEvent buildCloudEvent(WorkflowContext workflow, TaskContext taskCon ceBuilder.withId( props .idFilter() - .map(filter -> filter.apply(workflow, taskContext)) + .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) .orElse(UUID.randomUUID().toString())); ceBuilder.withSource( props .sourceFilter() - .map(filter -> filter.apply(workflow, taskContext)) + .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) .map(URI::create) .orElse(URI.create("reference-impl"))); ceBuilder.withType( props .typeFilter() - .map(filter -> filter.apply(workflow, taskContext)) + .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) .orElseThrow( () -> new IllegalArgumentException("Type is required for emitting events"))); props .timeFilter() - .map(filter -> filter.apply(workflow, taskContext)) + .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) .ifPresent(value -> ceBuilder.withTime(value)); props .subjectFilter() - .map(filter -> filter.apply(workflow, taskContext)) + .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) .ifPresent(value -> ceBuilder.withSubject(value)); props .dataSchemaFilter() - .map(filter -> filter.apply(workflow, taskContext)) + .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) .ifPresent(value -> ceBuilder.withDataSchema(URI.create(value))); props .contentTypeFilter() - .map(filter -> filter.apply(workflow, taskContext)) + .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) .ifPresent(value -> ceBuilder.withDataContentType(value)); props .dataFilter() .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) - .ifPresent( - value -> - ceBuilder.withData( - value - .asCloudEventData() - .orElseThrow( - () -> - new IllegalArgumentException( - "Workflow model " - + value - + " cannot be converted to CloudEvent")))); - // TODO JsonCloudEventData.wrap(value) + .ifPresent(value -> ceBuilder.withData(value)); props .additionalFilter() .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) @@ -141,37 +130,34 @@ private CloudEvent buildCloudEvent(WorkflowContext workflow, TaskContext taskCon 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 void addExtension(CloudEventBuilder builder, String name, Object value) { + if (value instanceof String s) { + builder.withExtension(name, s); + } else if (value instanceof Boolean b) { + builder.withExtension(name, b); + } else if (value instanceof Number n) { + builder.withExtension(name, n); + } else if (value instanceof OffsetDateTime t) { + builder.withExtension(name, t); + } } private static record EventPropertiesBuilder( - Optional idFilter, - Optional sourceFilter, - Optional subjectFilter, - Optional contentTypeFilter, - Optional typeFilter, - Optional dataSchemaFilter, - Optional> timeFilter, - Optional dataFilter, - Optional additionalFilter) { + Optional> idFilter, + Optional> sourceFilter, + Optional> subjectFilter, + Optional> contentTypeFilter, + Optional> typeFilter, + Optional> dataSchemaFilter, + Optional> timeFilter, + Optional> dataFilter, + Optional>> additionalFilter) { public static EventPropertiesBuilder build( EventProperties properties, WorkflowApplication app) { - Optional idFilter = buildFilter(app, properties.getId()); + Optional> idFilter = buildFilter(app, properties.getId()); EventSource source = properties.getSource(); - Optional sourceFilter = + Optional> sourceFilter = source == null ? Optional.empty() : Optional.of( @@ -179,11 +165,13 @@ public static EventPropertiesBuilder build( app, source.getRuntimeExpression(), WorkflowUtils.toString(source.getUriTemplate()))); - Optional subjectFilter = buildFilter(app, properties.getSubject()); - Optional contentTypeFilter = buildFilter(app, properties.getDatacontenttype()); - Optional typeFilter = buildFilter(app, 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 = + Optional> dataSchemaFilter = dataSchema == null ? Optional.empty() : Optional.of( @@ -192,32 +180,28 @@ public static EventPropertiesBuilder build( dataSchema.getExpressionDataSchema(), WorkflowUtils.toString(dataSchema.getLiteralDataSchema()))); EventTime time = properties.getTime(); - Optional> timeFilter = + Optional> timeFilter = time == null ? Optional.empty() : Optional.of( - WorkflowUtils.buildExpressionHolder( - app, - time.getRuntimeExpression(), - CloudEventUtils.toOffset(time.getLiteralTime()), - v -> - v.asDate() - .orElseThrow( - () -> - new IllegalArgumentException( - "Expression does not generate a valid date")))); + time.getRuntimeExpression() != null + ? app.expressionFactory() + .resolveDate(ExpressionDescriptor.from(time.getRuntimeExpression())) + : (w, t, n) -> CloudEventUtils.toOffset(time.getLiteralTime())); EventData data = properties.getData(); - Optional dataFilter = + Optional> dataFilter = properties.getData() == null ? Optional.empty() : Optional.of( - WorkflowUtils.buildWorkflowFilter( - app, data.getRuntimeExpression(), data.getObject())); + app.expressionFactory() + .resolveCE( + new ExpressionDescriptor(data.getRuntimeExpression(), data.getObject()))); Map ceAttrs = properties.getAdditionalProperties(); - Optional additionalFilter = + Optional>> additionalFilter = ceAttrs == null || ceAttrs.isEmpty() ? Optional.empty() - : Optional.of(WorkflowUtils.buildWorkflowFilter(app, null, ceAttrs)); + : Optional.of( + app.expressionFactory().resolveMap(ExpressionDescriptor.object(ceAttrs))); return new EventPropertiesBuilder( idFilter, sourceFilter, @@ -229,11 +213,10 @@ public static EventPropertiesBuilder build( dataFilter, additionalFilter); } + } - private static Optional buildFilter(WorkflowApplication appl, String str) { - return str == null - ? Optional.empty() - : Optional.of(WorkflowUtils.buildStringFilter(appl, str)); - } + private static Optional> buildFilter( + WorkflowApplication appl, String str) { + return str == null ? Optional.empty() : 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 977152ec..c77b6761 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 @@ -20,24 +20,27 @@ import io.serverlessworkflow.impl.TaskContext; 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.WorkflowPredicate; import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.WorkflowValueResolver; +import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.Collection; import java.util.Iterator; import java.util.Optional; import java.util.concurrent.CompletableFuture; public class ForExecutor extends RegularTaskExecutor { - private final WorkflowFilter collectionExpr; - private final Optional whileExpr; + private final WorkflowValueResolver> collectionExpr; + private final Optional whileExpr; private final TaskExecutor taskExecutor; public static class ForExecutorBuilder extends RegularTaskExecutorBuilder { - private WorkflowFilter collectionExpr; - private Optional whileExpr; + private WorkflowValueResolver> collectionExpr; + private Optional whileExpr; private TaskExecutor taskExecutor; protected ForExecutorBuilder( @@ -54,12 +57,14 @@ protected ForExecutorBuilder( position, task.getDo(), workflow, application, resourceLoader); } - protected Optional buildWhileFilter() { - return WorkflowUtils.optionalFilter(application, task.getWhile()); + protected Optional buildWhileFilter() { + return WorkflowUtils.optionalPredicate(application, task.getWhile()); } - protected WorkflowFilter buildCollectionFilter() { - return WorkflowUtils.buildWorkflowFilter(application, task.getFor().getIn()); + protected WorkflowValueResolver> buildCollectionFilter() { + return application + .expressionFactory() + .resolveCollection(ExpressionDescriptor.from(task.getFor().getIn())); } @Override @@ -78,19 +83,14 @@ protected ForExecutor(ForExecutorBuilder builder) { @Override protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { - Iterator iter = - collectionExpr.apply(workflow, taskContext, taskContext.input()).asCollection().iterator(); + Iterator iter = collectionExpr.apply(workflow, taskContext, taskContext.input()).iterator(); int i = 0; CompletableFuture future = CompletableFuture.completedFuture(taskContext.input()); while (iter.hasNext()) { - WorkflowModel item = iter.next(); - taskContext.variables().put(task.getFor().getEach(), item); + taskContext.variables().put(task.getFor().getEach(), iter.next()); taskContext.variables().put(task.getFor().getAt(), i++); - if (whileExpr - .map(w -> w.apply(workflow, taskContext, taskContext.input())) - .map(n -> n.asBoolean().orElse(true)) - .orElse(true)) { + if (whileExpr.map(w -> w.test(workflow, taskContext, taskContext.input())).orElse(true)) { future = future.thenCompose( input -> 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 ebede8c1..058917da 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 @@ -31,10 +31,10 @@ import io.serverlessworkflow.impl.TaskContext; 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.WorkflowPredicate; import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.events.EventConsumer; @@ -63,7 +63,7 @@ private static record EventRegistrationBuilderCollection( public static class ListenExecutorBuilder extends RegularTaskExecutorBuilder { private EventRegistrationBuilderCollection registrations; - private WorkflowFilter until; + private WorkflowPredicate until; private EventRegistrationBuilderCollection untilRegistrations; private TaskExecutor loop; private Function converter = @@ -101,8 +101,7 @@ protected ListenExecutorBuilder( if (untilDesc != null) { if (untilDesc.getAnyEventUntilCondition() != null) { until = - WorkflowUtils.buildWorkflowFilter( - application, untilDesc.getAnyEventUntilCondition()); + WorkflowUtils.buildPredicate(application, untilDesc.getAnyEventUntilCondition()); } else if (untilDesc.getAnyEventUntilConsumed() != null) { EventConsumptionStrategy strategy = untilDesc.getAnyEventUntilConsumed(); if (strategy.getAllEventConsumptionStrategy() != null) { @@ -173,7 +172,7 @@ protected void internalProcessCe( public static class OrListenExecutor extends ListenExecutor { - private final Optional until; + private final Optional until; private final EventRegistrationBuilderCollection untilRegBuilders; public OrListenExecutor(ListenExecutorBuilder builder) { @@ -209,8 +208,7 @@ protected void internalProcessCe( TaskContext taskContext, CompletableFuture future) { arrayNode.add(node); - if ((until.isEmpty() - || until.map(u -> u.apply(workflow, taskContext, arrayNode).asBoolean()).isPresent()) + if ((until.isEmpty() || until.map(u -> u.test(workflow, taskContext, arrayNode)).isPresent()) && untilRegBuilders == null) { future.complete(node); } 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 27c9018f..ac213e7b 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 @@ -21,7 +21,6 @@ import io.serverlessworkflow.api.types.RaiseTask; import io.serverlessworkflow.api.types.RaiseTaskError; import io.serverlessworkflow.api.types.Workflow; -import io.serverlessworkflow.impl.StringFilter; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; @@ -30,6 +29,7 @@ import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.WorkflowValueResolver; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.Map; import java.util.Optional; @@ -43,10 +43,10 @@ public class RaiseExecutor extends RegularTaskExecutor { public static class RaiseExecutorBuilder extends RegularTaskExecutorBuilder { private final BiFunction errorBuilder; - private final StringFilter typeFilter; - private final Optional instanceFilter; - private final StringFilter titleFilter; - private final StringFilter detailFilter; + private final WorkflowValueResolver typeFilter; + private final Optional> instanceFilter; + private final WorkflowValueResolver titleFilter; + private final WorkflowValueResolver detailFilter; protected RaiseExecutorBuilder( WorkflowPosition position, @@ -77,17 +77,18 @@ protected RaiseExecutorBuilder( private WorkflowError buildError( Error error, WorkflowContext context, TaskContext taskContext) { - return WorkflowError.error(typeFilter.apply(context, taskContext), error.getStatus()) + return WorkflowError.error( + typeFilter.apply(context, taskContext, taskContext.input()), error.getStatus()) .instance( instanceFilter - .map(f -> f.apply(context, taskContext)) + .map(f -> f.apply(context, taskContext, taskContext.input())) .orElseGet(() -> taskContext.position().jsonPointer())) - .title(titleFilter.apply(context, taskContext)) - .details(detailFilter.apply(context, taskContext)) + .title(titleFilter.apply(context, taskContext, taskContext.input())) + .details(detailFilter.apply(context, taskContext, taskContext.input())) .build(); } - private Optional getInstanceFunction( + private Optional> getInstanceFunction( WorkflowApplication app, ErrorInstance errorInstance) { return errorInstance != null ? Optional.of( @@ -98,7 +99,7 @@ private Optional getInstanceFunction( : Optional.empty(); } - private StringFilter getTypeFunction(WorkflowApplication app, ErrorType type) { + private WorkflowValueResolver getTypeFunction(WorkflowApplication app, ErrorType type) { return WorkflowUtils.buildStringFilter( app, type.getExpressionErrorType(), type.getLiteralErrorType().get().toString()); } 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 19a69568..0ad74caf 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 @@ -23,8 +23,8 @@ import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowFilter; import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowPredicate; import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.HashMap; @@ -36,12 +36,12 @@ public class SwitchExecutor extends AbstractTaskExecutor { - private final Map workflowFilters; + private final Map workflowFilters; private final TransitionInfo defaultTask; public static class SwitchExecutorBuilder extends AbstractTaskExecutorBuilder { - private final Map workflowFilters = new HashMap<>(); - private Map switchFilters = new HashMap<>(); + private final Map workflowFilters = new HashMap<>(); + private Map switchFilters = new HashMap<>(); private FlowDirective defaultDirective; private TransitionInfoBuilder defaultTask; @@ -61,9 +61,9 @@ public SwitchExecutorBuilder( } } - protected Optional buildFilter(SwitchCase switchCase) { + protected Optional buildFilter(SwitchCase switchCase) { return switchCase.getWhen() != null - ? Optional.of(WorkflowUtils.buildWorkflowFilter(application, switchCase.getWhen())) + ? Optional.of(WorkflowUtils.buildPredicate(application, switchCase.getWhen())) : Optional.empty(); } @@ -99,12 +99,8 @@ private SwitchExecutor(SwitchExecutorBuilder builder) { 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() - .orElse(false)) { + for (Entry entry : workflowFilters.entrySet()) { + if (entry.getKey().test(workflow, taskContext, taskContext.input())) { return future.thenApply(t -> t.transition(entry.getValue())); } } 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 b3efca9e..6a45ee13 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 @@ -26,9 +26,9 @@ import io.serverlessworkflow.impl.WorkflowContext; 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.WorkflowPredicate; import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.List; @@ -39,16 +39,16 @@ public class TryExecutor extends RegularTaskExecutor { - private final Optional whenFilter; - private final Optional exceptFilter; + private final Optional whenFilter; + private final Optional exceptFilter; private final Optional> errorFilter; private final TaskExecutor taskExecutor; private final Optional> catchTaskExecutor; public static class TryExecutorBuilder extends RegularTaskExecutorBuilder { - private final Optional whenFilter; - private final Optional exceptFilter; + private final Optional whenFilter; + private final Optional exceptFilter; private final Optional> errorFilter; private final TaskExecutor taskExecutor; private final Optional> catchTaskExecutor; @@ -62,8 +62,8 @@ protected TryExecutorBuilder( super(position, task, workflow, application, resourceLoader); TryTaskCatch catchInfo = task.getCatch(); this.errorFilter = buildErrorFilter(catchInfo.getErrors()); - this.whenFilter = WorkflowUtils.optionalFilter(application, catchInfo.getWhen()); - this.exceptFilter = WorkflowUtils.optionalFilter(application, catchInfo.getExceptWhen()); + this.whenFilter = WorkflowUtils.optionalPredicate(application, catchInfo.getWhen()); + this.exceptFilter = WorkflowUtils.optionalPredicate(application, catchInfo.getExceptWhen()); this.taskExecutor = TaskExecutorHelper.createExecutorList( position, task.getTry(), workflow, application, resourceLoader); @@ -107,15 +107,9 @@ private CompletableFuture handleException( if (e instanceof WorkflowException) { WorkflowException exception = (WorkflowException) e; if (errorFilter.map(f -> f.test(exception.getWorflowError())).orElse(true) - && whenFilter - .flatMap(w -> w.apply(workflow, taskContext, taskContext.input()).asBoolean()) - .orElse(true) + && whenFilter.map(w -> w.test(workflow, taskContext, taskContext.input())).orElse(true) && exceptFilter - .map( - w -> - !w.apply(workflow, taskContext, taskContext.input()) - .asBoolean() - .orElse(false)) + .map(w -> !w.test(workflow, taskContext, taskContext.input())) .orElse(true)) { if (catchTaskExecutor.isPresent()) { return TaskExecutorHelper.processTaskList( diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/AbstractExpressionFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/AbstractExpressionFactory.java new file mode 100644 index 00000000..6abe723a --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/AbstractExpressionFactory.java @@ -0,0 +1,74 @@ +/* + * 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.cloudevents.CloudEventData; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowValueResolver; +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Map; + +public abstract class AbstractExpressionFactory implements ExpressionFactory { + + public WorkflowValueResolver resolveDate(ExpressionDescriptor desc) { + ObjectExpression expr = buildExpression(desc); + return (w, t, m) -> toDate(expr.eval(w, t, m)); + } + + public WorkflowValueResolver resolveCE(ExpressionDescriptor desc) { + ObjectExpression expr = buildExpression(desc); + return (w, t, m) -> toCloudEventData(expr.eval(w, t, m)); + } + + public WorkflowValueResolver> resolveMap(ExpressionDescriptor desc) { + ObjectExpression expr = buildExpression(desc); + return (w, t, m) -> toMap(expr.eval(w, t, m)); + } + + @Override + public WorkflowValueResolver resolveString(ExpressionDescriptor desc) { + ObjectExpression expr = buildExpression(desc); + return (w, t, m) -> toString(expr.eval(w, t, m)); + } + + @Override + public WorkflowValueResolver> resolveCollection(ExpressionDescriptor desc) { + ObjectExpression expr = buildExpression(desc); + return (w, t, m) -> toCollection(expr.eval(w, t, m)); + } + + @Override + public WorkflowFilter buildFilter(ExpressionDescriptor desc) { + if (desc.asObject() instanceof WorkflowFilter filter) { + return filter; + } + ObjectExpression expr = buildExpression(desc); + return (w, t, m) -> modelFactory().fromAny(m, expr.eval(w, t, m)); + } + + protected abstract ObjectExpression buildExpression(ExpressionDescriptor desc); + + protected abstract String toString(Object obj); + + protected abstract CloudEventData toCloudEventData(Object obj); + + protected abstract OffsetDateTime toDate(Object obj); + + protected abstract Map toMap(Object obj); + + protected abstract Collection toCollection(Object obj); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionDescriptor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionDescriptor.java new file mode 100644 index 00000000..f7aef6c3 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionDescriptor.java @@ -0,0 +1,26 @@ +/* + * 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; + +public record ExpressionDescriptor(String asString, Object asObject) { + public static ExpressionDescriptor from(String string) { + return new ExpressionDescriptor(string, null); + } + + public static ExpressionDescriptor object(Object obj) { + return new ExpressionDescriptor(null, obj); + } +} 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 28039cc7..25211d7a 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,25 +15,39 @@ */ package io.serverlessworkflow.impl.expressions; +import io.cloudevents.CloudEventData; import io.serverlessworkflow.api.types.TaskBase; import io.serverlessworkflow.impl.ServicePriority; import io.serverlessworkflow.impl.WorkflowFilter; import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.WorkflowPredicate; +import io.serverlessworkflow.impl.WorkflowValueResolver; +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Map; import java.util.Optional; public interface ExpressionFactory extends ServicePriority { - /** - * @throws ExpressionValidationException - * @param expression - * @return - */ - Expression buildExpression(String expression); - WorkflowFilter buildFilter(String expr, Object value); + WorkflowValueResolver resolveString(ExpressionDescriptor desc); + + WorkflowValueResolver resolveDate(ExpressionDescriptor desc); + + WorkflowValueResolver resolveCE(ExpressionDescriptor desc); + + WorkflowValueResolver> resolveMap(ExpressionDescriptor desc); + + WorkflowValueResolver> resolveCollection(ExpressionDescriptor desc); + + WorkflowFilter buildFilter(ExpressionDescriptor desc); + + WorkflowPredicate buildPredicate(ExpressionDescriptor desc); WorkflowModelFactory modelFactory(); - default Optional buildIfFilter(TaskBase task) { - return task.getIf() != null ? Optional.of(buildFilter(task.getIf(), null)) : Optional.empty(); + default Optional buildIfFilter(TaskBase task) { + return task.getIf() != null + ? Optional.of(buildPredicate(ExpressionDescriptor.from(task.getIf()))) + : Optional.empty(); } } 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 83f6fe1c..b91ac21c 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,11 +15,6 @@ */ package io.serverlessworkflow.impl.expressions; -import io.serverlessworkflow.impl.TaskContext; -import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowModel; -import java.util.Map; - public class ExpressionUtils { private static final String EXPR_PREFIX = "${"; @@ -27,21 +22,6 @@ public class ExpressionUtils { private ExpressionUtils() {} - public static Map buildExpressionMap( - Map origMap, ExpressionFactory factory) { - return new ProxyMap(origMap, o -> isExpr(o) ? factory.buildExpression(o.toString()) : o); - } - - public static Map evaluateExpressionMap( - Map origMap, WorkflowContext workflow, TaskContext task, WorkflowModel n) { - return new ProxyMap( - origMap, o -> o instanceof Expression ? ((Expression) o).eval(workflow, task, n) : o); - } - - public static Object buildExpressionObject(Object obj, ExpressionFactory factory) { - return obj instanceof Map map ? ExpressionUtils.buildExpressionMap(map, factory) : obj; - } - public static boolean isExpr(Object expr) { return expr instanceof String && ((String) expr).startsWith(EXPR_PREFIX); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/Expression.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpression.java similarity index 86% rename from impl/core/src/main/java/io/serverlessworkflow/impl/expressions/Expression.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpression.java index f2e91ace..59126ac1 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/Expression.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpression.java @@ -19,6 +19,6 @@ import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowModel; -public interface Expression { - WorkflowModel eval(WorkflowContext workflowContext, TaskContext context, WorkflowModel model); +public interface ObjectExpression { + Object eval(WorkflowContext workflowContext, TaskContext context, WorkflowModel model); } 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 index f96894cc..06ed1b17 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpressionFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpressionFactory.java @@ -15,23 +15,58 @@ */ package io.serverlessworkflow.impl.expressions; -import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPredicate; import java.util.Map; -public abstract class ObjectExpressionFactory implements ExpressionFactory { +public abstract class ObjectExpressionFactory extends AbstractExpressionFactory { - @Override - public WorkflowFilter buildFilter(String str, Object object) { - if (str != null) { - assert str != null; - Expression expression = buildExpression(str); + protected abstract ObjectExpression buildExpression(String expression); + + protected ObjectExpression buildExpression(ExpressionDescriptor desc) { + if (desc.asString() != null) { + ObjectExpression expression = buildExpression(desc.asString()); return expression::eval; - } else if (object != null) { - Object exprObj = ExpressionUtils.buildExpressionObject(object, this); + } else if (desc.asObject() != null) { + Object exprObj = buildExpressionObject(desc.asObject(), this); return exprObj instanceof Map map - ? (w, t, n) -> modelFactory().from(ExpressionUtils.evaluateExpressionMap(map, w, t, n)) - : (w, t, n) -> modelFactory().fromAny(object); + ? (w, t, n) -> evaluateExpressionMap(map, w, t, n) + : (w, t, n) -> desc.asObject(); } throw new IllegalArgumentException("Both object and str are null"); } + + @Override + public WorkflowPredicate buildPredicate(ExpressionDescriptor desc) { + ObjectExpression expr = buildExpression(desc); + return (w, t, m) -> toBoolean(expr.eval(w, t, m)); + } + + protected abstract boolean toBoolean(Object eval); + + protected Object toJavaObject(Object eval) { + return eval; + } + + private Map buildExpressionMap( + Map origMap, ExpressionFactory factory) { + return new ProxyMap( + origMap, o -> ExpressionUtils.isExpr(o) ? buildExpression(o.toString()) : o); + } + + private Object buildExpressionObject(Object obj, ExpressionFactory factory) { + return obj instanceof Map map ? buildExpressionMap(map, factory) : obj; + } + + private Map evaluateExpressionMap( + Map origMap, WorkflowContext workflow, TaskContext task, WorkflowModel n) { + return new ProxyMap( + origMap, + o -> + o instanceof ObjectExpression + ? toJavaObject(((ObjectExpression) o).eval(workflow, task, n)) + : o); + } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java index a8bcfead..0dbf66eb 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java @@ -17,12 +17,12 @@ import io.serverlessworkflow.api.types.BasicAuthenticationPolicy; import io.serverlessworkflow.api.types.Workflow; -import io.serverlessworkflow.impl.StringFilter; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.WorkflowValueResolver; import jakarta.ws.rs.client.Invocation.Builder; import java.util.Base64; @@ -31,8 +31,8 @@ class BasicAuthProvider implements AuthProvider { private static final String BASIC_TOKEN = "Basic %s"; private static final String USER_PASSWORD = "%s:%s"; - private StringFilter userFilter; - private StringFilter passwordFilter; + private WorkflowValueResolver userFilter; + private WorkflowValueResolver passwordFilter; public BasicAuthProvider( WorkflowApplication app, Workflow workflow, BasicAuthenticationPolicy authPolicy) { @@ -59,8 +59,8 @@ public Builder build( .encode( String.format( USER_PASSWORD, - userFilter.apply(workflow, task), - passwordFilter.apply(workflow, task)) + userFilter.apply(workflow, task, model), + passwordFilter.apply(workflow, task, model)) .getBytes()))); return builder; } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java index a0df5b61..79f5584f 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java @@ -18,19 +18,19 @@ import io.serverlessworkflow.api.types.BearerAuthenticationPolicy; import io.serverlessworkflow.api.types.BearerAuthenticationPolicyConfiguration; import io.serverlessworkflow.api.types.Workflow; -import io.serverlessworkflow.impl.StringFilter; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.WorkflowValueResolver; import jakarta.ws.rs.client.Invocation.Builder; class BearerAuthProvider implements AuthProvider { private static final String BEARER_TOKEN = "Bearer %s"; - private StringFilter tokenFilter; + private WorkflowValueResolver tokenFilter; public BearerAuthProvider( WorkflowApplication app, @@ -50,7 +50,7 @@ public Builder build( Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) { builder.header( AuthProviderFactory.AUTH_HEADER_NAME, - String.format(BEARER_TOKEN, tokenFilter.apply(workflow, task))); + String.format(BEARER_TOKEN, tokenFilter.apply(workflow, task, model))); return builder; } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java index b66a9319..7b7243e1 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java @@ -15,6 +15,8 @@ */ package io.serverlessworkflow.impl.executors.http; +import static io.serverlessworkflow.impl.WorkflowUtils.buildMapResolver; + import io.serverlessworkflow.api.types.CallHTTP; import io.serverlessworkflow.api.types.Endpoint; import io.serverlessworkflow.api.types.EndpointUri; @@ -27,11 +29,10 @@ import io.serverlessworkflow.impl.WorkflowContext; 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.WorkflowValueResolver; import io.serverlessworkflow.impl.executors.CallableTask; -import io.serverlessworkflow.impl.expressions.Expression; +import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import io.serverlessworkflow.impl.expressions.ExpressionFactory; import io.serverlessworkflow.impl.resources.ResourceLoader; import jakarta.ws.rs.HttpMethod; @@ -40,6 +41,7 @@ import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.Invocation.Builder; import jakarta.ws.rs.client.WebTarget; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; @@ -49,8 +51,8 @@ public class HttpExecutor implements CallableTask { private static final Client client = ClientBuilder.newClient(); private TargetSupplier targetSupplier; - private Optional headersMap; - private Optional queryMap; + private Optional>> headersMap; + private Optional>> queryMap; private Optional authProvider; private RequestSupplier requestFunction; private HttpModelConverter converter = new HttpModelConverter() {}; @@ -83,7 +85,7 @@ public void init( this.headersMap = httpArgs.getHeaders() != null ? Optional.of( - WorkflowUtils.buildWorkflowFilter( + buildMapResolver( application, httpArgs.getHeaders().getRuntimeExpression(), httpArgs.getHeaders().getHTTPHeaders() != null @@ -93,7 +95,7 @@ public void init( this.queryMap = httpArgs.getQuery() != null ? Optional.of( - WorkflowUtils.buildWorkflowFilter( + buildMapResolver( application, httpArgs.getQuery().getRuntimeExpression(), httpArgs.getQuery().getHTTPQuery() != null @@ -102,8 +104,8 @@ public void init( : Optional.empty(); switch (httpArgs.getMethod().toUpperCase()) { case HttpMethod.POST: - WorkflowFilter bodyFilter = - WorkflowUtils.buildWorkflowFilter(application, null, httpArgs.getBody()); + WorkflowValueResolver> bodyFilter = + buildMapResolver(application, null, httpArgs.getBody()); this.requestFunction = (request, w, context, node) -> converter.toModel( @@ -143,15 +145,11 @@ public CompletableFuture apply( 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()))); + q -> q.apply(workflow, taskContext, input).forEach((k, v) -> supplier.addQuery(k, v))); Builder request = supplier.get().request(); authProvider.ifPresent(auth -> auth.build(request, workflow, taskContext, input)); headersMap.ifPresent( - h -> - h.apply(workflow, taskContext, input) - .forEach((k, v) -> request.header(k, v.asJavaObject()))); + h -> h.apply(workflow, taskContext, input).forEach((k, v) -> request.header(k, v))); return CompletableFuture.supplyAsync( () -> { try { @@ -179,11 +177,13 @@ private static TargetSupplier getTargetSupplier( return getURISupplier(uri.getLiteralEndpointURI()); } else if (uri.getExpressionEndpointURI() != null) { return new ExpressionURISupplier( - expressionFactory.buildExpression(uri.getExpressionEndpointURI())); + expressionFactory.resolveString( + ExpressionDescriptor.from(uri.getExpressionEndpointURI()))); } } else if (endpoint.getRuntimeExpression() != null) { return new ExpressionURISupplier( - expressionFactory.buildExpression(endpoint.getRuntimeExpression())); + expressionFactory.resolveString( + ExpressionDescriptor.from(endpoint.getRuntimeExpression()))); } else if (endpoint.getUriTemplate() != null) { return getURISupplier(endpoint.getUriTemplate()); } @@ -201,20 +201,15 @@ private static TargetSupplier getURISupplier(UriTemplate template) { } private static class ExpressionURISupplier implements TargetSupplier { - private Expression expr; + private WorkflowValueResolver expr; - public ExpressionURISupplier(Expression expr) { + public ExpressionURISupplier(WorkflowValueResolver expr) { this.expr = expr; } @Override 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"))); + return client.target(expr.apply(workflow, task, node)); } } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java index 161db2ec..6e5d92e3 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java @@ -18,6 +18,7 @@ import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowModelFactory; import jakarta.ws.rs.client.Entity; +import java.util.Map; public interface HttpModelConverter { @@ -25,7 +26,7 @@ default WorkflowModel toModel(WorkflowModelFactory factory, Object entity) { return factory.fromAny(entity); } - default Entity toEntity(WorkflowModel model) { - return Entity.json(model.asIs()); + default Entity toEntity(Map model) { + return Entity.json(model); } } diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java index f7fd2504..8234eb1b 100644 --- a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java @@ -22,8 +22,7 @@ import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowModel; -import io.serverlessworkflow.impl.WorkflowModelFactory; -import io.serverlessworkflow.impl.expressions.Expression; +import io.serverlessworkflow.impl.expressions.ObjectExpression; import io.serverlessworkflow.impl.expressions.TaskDescriptor; import io.serverlessworkflow.impl.expressions.WorkflowDescriptor; import io.serverlessworkflow.impl.jackson.JsonUtils; @@ -34,29 +33,26 @@ import net.thisptr.jackson.jq.exception.JsonQueryException; import net.thisptr.jackson.jq.internal.javacc.ExpressionParser; -public class JQExpression implements Expression { +public class JQExpression implements ObjectExpression { 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, WorkflowModelFactory modelFactory) + public JQExpression(Supplier scope, String expr, Version version) throws JsonQueryException { this.expr = expr; this.scope = scope; this.internalExpr = ExpressionParser.compile(expr, version); - this.modelFactory = modelFactory; } @Override - public WorkflowModel eval(WorkflowContext workflow, TaskContext task, WorkflowModel model) { + public Object eval(WorkflowContext workflow, TaskContext task, WorkflowModel model) { JsonNodeOutput output = new JsonNodeOutput(); JsonNode node = modelToJson(model); try { internalExpr.apply(createScope(workflow, task), node, output); - return modelFactory.fromAny(output.getResult()); + return output.getResult(); } catch (JsonQueryException e) { throw new IllegalArgumentException( "Unable to evaluate content " + node + " using expr " + expr, e); diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpressionFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpressionFactory.java index 2909eb7b..de06c0e1 100644 --- a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpressionFactory.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpressionFactory.java @@ -15,10 +15,16 @@ */ package io.serverlessworkflow.impl.expressions.jq; +import io.cloudevents.CloudEventData; +import io.cloudevents.jackson.JsonCloudEventData; import io.serverlessworkflow.impl.WorkflowModelFactory; -import io.serverlessworkflow.impl.expressions.Expression; import io.serverlessworkflow.impl.expressions.ExpressionUtils; +import io.serverlessworkflow.impl.expressions.ObjectExpression; import io.serverlessworkflow.impl.expressions.ObjectExpressionFactory; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Map; import java.util.function.Supplier; import net.thisptr.jackson.jq.BuiltinFunctionLoader; import net.thisptr.jackson.jq.Scope; @@ -48,10 +54,9 @@ public Scope get() { } @Override - public Expression buildExpression(String expression) { + public ObjectExpression buildExpression(String expression) { try { - return new JQExpression( - scopeSupplier, ExpressionUtils.trimExpr(expression), Versions.JQ_1_6, modelFactory); + return new JQExpression(scopeSupplier, ExpressionUtils.trimExpr(expression), Versions.JQ_1_6); } catch (JsonQueryException e) { throw new IllegalArgumentException(e); } @@ -61,4 +66,41 @@ public Expression buildExpression(String expression) { public WorkflowModelFactory modelFactory() { return modelFactory; } + + @Override + protected boolean toBoolean(Object eval) { + return JsonUtils.convertValue(eval, Boolean.class); + } + + @Override + protected String toString(Object eval) { + return JsonUtils.convertValue(eval, String.class); + } + + @Override + protected CloudEventData toCloudEventData(Object eval) { + return JsonCloudEventData.wrap(JsonUtils.fromValue(eval)); + } + + @Override + protected OffsetDateTime toDate(Object eval) { + return JsonUtils.toDate(JsonUtils.fromValue(eval)) + .orElseThrow( + () -> new IllegalStateException("Cannot convert " + eval + " to OffseDateTime")); + } + + @Override + protected Map toMap(Object eval) { + return (Map) JsonUtils.toJavaValue(eval); + } + + @Override + protected Object toJavaObject(Object eval) { + return JsonUtils.toJavaValue(eval); + } + + @Override + protected Collection toCollection(Object obj) { + return (Collection) JsonUtils.toJavaValue(obj); + } } diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java index dc71fcb2..7d430214 100644 --- a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java @@ -20,13 +20,9 @@ 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.jackson.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; @@ -68,14 +64,7 @@ public Optional asText() { @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(); - } + return JsonUtils.toDate(node); } @Override @@ -83,11 +72,6 @@ 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()) diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelFactory.java index 866c3264..f025fc83 100644 --- a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelFactory.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelFactory.java @@ -110,12 +110,8 @@ public WorkflowModel from(Map map) { } @Override - public WorkflowModel fromAny(Object obj) { - if (obj instanceof JsonNode node) { - return new JacksonModel(node); - } else { - return WorkflowModelFactory.super.fromAny(obj); - } + public WorkflowModel fromOther(Object value) { + return new JacksonModel(JsonUtils.fromValue(value)); } @Override diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/jackson/JsonUtils.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/jackson/JsonUtils.java index 04822457..6434c6f5 100644 --- a/impl/jackson/src/main/java/io/serverlessworkflow/impl/jackson/JsonUtils.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/jackson/JsonUtils.java @@ -44,6 +44,7 @@ import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; @@ -91,12 +92,6 @@ public Supplier supplier() { }; } - public static OffsetDateTime toOffsetDateTime(JsonNode node) { - return node.isTextual() - ? OffsetDateTime.parse(node.asText()) - : OffsetDateTime.ofInstant(Instant.ofEpochMilli(node.asLong()), ZoneOffset.UTC); - } - /* * Implementation note: * Although we can use directly ObjectMapper.convertValue for implementing fromValue and toJavaValue methods, @@ -275,5 +270,16 @@ public static ArrayNode array() { return mapper.createArrayNode(); } + public static Optional toDate(JsonNode node) { + 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(); + } + } + private JsonUtils() {} }