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 9723fba7..e427a222 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 @@ -21,6 +21,7 @@ import io.serverlessworkflow.api.types.ForTask; import io.serverlessworkflow.api.types.Workflow; 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; @@ -44,7 +45,7 @@ protected JavaForExecutorBuilder( protected Optional buildWhileFilter() { if (task instanceof ForTaskFunction taskFunctions) { final LoopPredicateIndex whilePred = taskFunctions.getWhilePredicate(); - Optional> modelClass = taskFunctions.getModelClass(); + Optional> whileClass = taskFunctions.getWhileClass(); String varName = task.getFor().getEach(); String indexName = task.getFor().getAt(); if (whilePred != null) { @@ -55,7 +56,7 @@ protected Optional buildWhileFilter() { .modelFactory() .from( whilePred.test( - JavaFuncUtils.convert(n, modelClass), + JavaFuncUtils.convert(n, whileClass), item, (Integer) safeObject(t.variables().get(indexName)))); }); @@ -66,7 +67,15 @@ protected Optional buildWhileFilter() { protected WorkflowFilter buildCollectionFilter() { return task instanceof ForTaskFunction taskFunctions - ? WorkflowUtils.buildWorkflowFilter(application, null, taskFunctions.getCollection()) + ? WorkflowUtils.buildWorkflowFilter( + application, null, collectionFilterObject(taskFunctions)) : super.buildCollectionFilter(); } + + private Object collectionFilterObject(ForTaskFunction taskFunctions) { + return taskFunctions.getForClass().isPresent() + ? new TypedFunction( + taskFunctions.getCollection(), taskFunctions.getForClass().orElseThrow()) + : taskFunctions.getCollection(); + } } 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 69585a90..7762638d 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 @@ -20,6 +20,7 @@ import io.serverlessworkflow.api.types.SwitchTask; import io.serverlessworkflow.api.types.Workflow; 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; @@ -27,6 +28,7 @@ import io.serverlessworkflow.impl.executors.SwitchExecutor.SwitchExecutorBuilder; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.Optional; +import java.util.function.Predicate; public class JavaSwitchExecutorBuilder extends SwitchExecutorBuilder { @@ -42,7 +44,14 @@ protected JavaSwitchExecutorBuilder( @Override protected Optional buildFilter(SwitchCase switchCase) { return switchCase instanceof SwitchCaseFunction function - ? Optional.of(WorkflowUtils.buildWorkflowFilter(application, null, function.predicate())) + ? Optional.of( + WorkflowUtils.buildWorkflowFilter( + application, null, predObject(function.predicate(), function.predicateClass()))) : super.buildFilter(switchCase); } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private Object predObject(Predicate pred, Optional> predClass) { + return predClass.isPresent() ? new TypedPredicate(pred, predClass.orElseThrow()) : pred; + } } 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 4d6ca49f..eba835f2 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 @@ -17,6 +17,8 @@ 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; @@ -52,8 +54,12 @@ public Expression buildExpression(String expression) { public WorkflowFilter buildFilter(String expr, Object value) { if (value instanceof Function func) { return (w, t, n) -> modelFactory.fromAny(func.apply(n.asJavaObject())); + } else if (value instanceof TypedFunction func) { + return (w, t, n) -> modelFactory.fromAny(func.function().apply(n.as(func.argClass()))); } 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) { @@ -70,14 +76,23 @@ private WorkflowFilter fromPredicate(Predicate pred) { return (w, t, n) -> modelFactory.from(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()))); + } + @Override public Optional buildIfFilter(TaskBase task) { TaskMetadata metadata = task.getMetadata(); - return metadata != null - && metadata.getAdditionalProperties().get(TaskMetadataKeys.IF_PREDICATE) - instanceof Predicate pred - ? Optional.of(fromPredicate(pred)) - : ExpressionFactory.super.buildIfFilter(task); + if (metadata != null) { + Object obj = metadata.getAdditionalProperties().get(TaskMetadataKeys.IF_PREDICATE); + if (obj instanceof Predicate pred) { + return Optional.of(fromPredicate(pred)); + } else if (obj instanceof TypedPredicate pred) { + return Optional.of(fromPredicate(pred)); + } + } + return ExpressionFactory.super.buildIfFilter(task); } @Override diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ExportAsFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ExportAsFunction.java index e7879af3..45a81892 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ExportAsFunction.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ExportAsFunction.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.api.types.func; import io.serverlessworkflow.api.types.ExportAs; +import java.util.Objects; import java.util.function.Function; public class ExportAsFunction extends ExportAs { @@ -24,4 +25,10 @@ public ExportAs withFunction(Function value) { setObject(value); return this; } + + public ExportAs withFunction(Function value, Class argClass) { + Objects.requireNonNull(argClass); + setObject(new TypedFunction<>(value, argClass)); + return this; + } } diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ForTaskFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ForTaskFunction.java index 8c9ad956..eb4ac716 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ForTaskFunction.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ForTaskFunction.java @@ -26,8 +26,9 @@ public class ForTaskFunction extends ForTask { private static final long serialVersionUID = 1L; private LoopPredicateIndex whilePredicate; - private Optional> modelClass; + private Optional> whileClass; private Optional> itemClass; + private Optional> forClass; private Function> collection; public ForTaskFunction withWhile(LoopPredicate whilePredicate) { @@ -53,12 +54,12 @@ public ForTaskFunction withWhile(LoopPredicateIndex whilePredicate) public ForTaskFunction withWhile( LoopPredicateIndex whilePredicate, Class modelClass) { - return withWhile(whilePredicate, Optional.of(modelClass), Optional.empty()); + return withWhile(whilePredicate, Optional.ofNullable(modelClass), Optional.empty()); } public ForTaskFunction withWhile( LoopPredicateIndex whilePredicate, Class modelClass, Class itemClass) { - return withWhile(whilePredicate, Optional.of(modelClass), Optional.of(itemClass)); + return withWhile(whilePredicate, Optional.ofNullable(modelClass), Optional.of(itemClass)); } private ForTaskFunction withWhile( @@ -66,13 +67,19 @@ private ForTaskFunction withWhile( Optional> modelClass, Optional> itemClass) { this.whilePredicate = whilePredicate; - this.modelClass = modelClass; + this.whileClass = modelClass; this.itemClass = itemClass; return this; } public ForTaskFunction withCollection(Function> collection) { + return withCollection(collection, null); + } + + public ForTaskFunction withCollection( + Function> collection, Class colArgClass) { this.collection = collection; + this.forClass = Optional.ofNullable(colArgClass); return this; } @@ -80,8 +87,12 @@ public ForTaskFunction withCollection(Function> collection) return whilePredicate; } - public Optional> getModelClass() { - return modelClass; + public Optional> getWhileClass() { + return whileClass; + } + + public Optional> getForClass() { + return forClass; } public Optional> getItemClass() { diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/InputFromFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/InputFromFunction.java index 49249bc2..521dca87 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/InputFromFunction.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/InputFromFunction.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.api.types.func; import io.serverlessworkflow.api.types.InputFrom; +import java.util.Objects; import java.util.function.Function; public class InputFromFunction extends InputFrom { @@ -24,4 +25,10 @@ public InputFrom withFunction(Function value) { setObject(value); return this; } + + public InputFrom withFunction(Function value, Class argClass) { + Objects.requireNonNull(argClass); + setObject(new TypedFunction<>(value, argClass)); + return this; + } } diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/OutputAsFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/OutputAsFunction.java index b593cb13..8d2d6dc5 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/OutputAsFunction.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/OutputAsFunction.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.api.types.func; import io.serverlessworkflow.api.types.OutputAs; +import java.util.Objects; import java.util.function.Function; public class OutputAsFunction extends OutputAs { @@ -24,4 +25,10 @@ public OutputAs withFunction(Function value) { setObject(value); return this; } + + public OutputAs withFunction(Function value, Class argClass) { + Objects.requireNonNull(argClass); + setObject(new TypedFunction<>(value, argClass)); + return this; + } } diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/SwitchCaseFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/SwitchCaseFunction.java index 234fcc80..01813c5d 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/SwitchCaseFunction.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/SwitchCaseFunction.java @@ -16,23 +16,32 @@ package io.serverlessworkflow.api.types.func; import io.serverlessworkflow.api.types.SwitchCase; +import java.util.Optional; import java.util.function.Predicate; public class SwitchCaseFunction extends SwitchCase { private static final long serialVersionUID = 1L; private Predicate predicate; + private Optional> predicateClass; public SwitchCaseFunction withPredicate(Predicate predicate) { this.predicate = predicate; + this.predicateClass = Optional.empty(); return this; } - public void setPredicate(Predicate predicate) { + public SwitchCaseFunction withPredicate(Predicate predicate, Class predicateClass) { this.predicate = predicate; + this.predicateClass = Optional.ofNullable(predicateClass); + return this; } public Predicate predicate() { return predicate; } + + public Optional> predicateClass() { + return predicateClass; + } } diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedFunction.java new file mode 100644 index 00000000..c38bbb92 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedFunction.java @@ -0,0 +1,20 @@ +/* + * 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.api.types.func; + +import java.util.function.Function; + +public record TypedFunction(Function function, Class argClass) {} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedPredicate.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedPredicate.java new file mode 100644 index 00000000..26c0893e --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedPredicate.java @@ -0,0 +1,20 @@ +/* + * 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.api.types.func; + +import java.util.function.Predicate; + +public record TypedPredicate(Predicate pred, Class argClass) {} diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java index 3b463644..ebcde632 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java @@ -38,7 +38,7 @@ public static Function toFunction(AgentExecutor exec) { return exec::invoke; } - public static LoopPredicateIndex toWhile(Predicate exit) { - return (model, item, idx) -> !exit.test((Cognisphere) model); + public static LoopPredicateIndex toWhile(Predicate exit) { + return (model, item, idx) -> !exit.test(model); } } diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java index 31f00316..f98089c8 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java @@ -61,7 +61,7 @@ public LoopAgentsBuilder maxIterations(int maxIterations) { } public LoopAgentsBuilder exitCondition(Predicate exitCondition) { - this.forTask.withWhile(AgentAdapters.toWhile(exitCondition)); + this.forTask.withWhile(AgentAdapters.toWhile(exitCondition), Cognisphere.class); return this; } diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncSwitchTaskBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncSwitchTaskBuilder.java index db3e8867..d1a1b642 100644 --- a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncSwitchTaskBuilder.java +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncSwitchTaskBuilder.java @@ -83,7 +83,12 @@ public static final class SwitchCaseFunctionBuilder { } public SwitchCaseFunctionBuilder when(Predicate when) { - this.switchCase.setPredicate(when); + this.switchCase.withPredicate(when); + return this; + } + + public SwitchCaseFunctionBuilder when(Predicate when, Class whenClass) { + this.switchCase.withPredicate(when, whenClass); return this; } diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilder.java index 5032ff4e..383bf7f3 100644 --- a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilder.java +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilder.java @@ -16,8 +16,8 @@ package io.serverlessworkflow.fluent.func.spi; import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.api.types.TaskMetadata; -import io.serverlessworkflow.impl.expressions.TaskMetadataKeys; +import io.serverlessworkflow.api.types.func.TypedPredicate; +import java.util.Objects; import java.util.function.Predicate; public interface ConditionalTaskBuilder { @@ -25,10 +25,13 @@ public interface ConditionalTaskBuilder { TaskBase getTask(); default SELF when(Predicate predicate) { - if (getTask().getMetadata() == null) { - getTask().setMetadata(new TaskMetadata()); - } - getTask().getMetadata().setAdditionalProperty(TaskMetadataKeys.IF_PREDICATE, predicate); + ConditionalTaskBuilderHelper.setMetadata(getTask(), predicate); + return (SELF) this; + } + + default SELF when(Predicate predicate, Class argClass) { + Objects.requireNonNull(argClass); + ConditionalTaskBuilderHelper.setMetadata(getTask(), new TypedPredicate<>(predicate, argClass)); return (SELF) this; } } diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilderHelper.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilderHelper.java new file mode 100644 index 00000000..2ffd1d91 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilderHelper.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.func.spi; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.TaskMetadata; +import io.serverlessworkflow.impl.expressions.TaskMetadataKeys; + +class ConditionalTaskBuilderHelper { + + private ConditionalTaskBuilderHelper() {} + + static void setMetadata(TaskBase task, Object predicate) { + TaskMetadata metadata = task.getMetadata(); + if (metadata == null) { + metadata = new TaskMetadata(); + task.setMetadata(metadata); + } + metadata.setAdditionalProperty(TaskMetadataKeys.IF_PREDICATE, predicate); + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java index b063e4f8..db257dd0 100644 --- a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java @@ -32,13 +32,28 @@ default SELF exportAsFn(Function function) { return (SELF) this; } + default SELF exportAsFn(Function function, Class argClass) { + setExport(new Export().withAs(new ExportAsFunction().withFunction(function, argClass))); + return (SELF) this; + } + default SELF inputFrom(Function function) { setInput(new Input().withFrom(new InputFromFunction().withFunction(function))); return (SELF) this; } + default SELF inputFrom(Function function, Class argClass) { + setInput(new Input().withFrom(new InputFromFunction().withFunction(function, argClass))); + return (SELF) this; + } + default SELF outputAs(Function function) { setOutput(new Output().withAs(new OutputAsFunction().withFunction(function))); return (SELF) this; } + + default SELF outputAs(Function function, Class argClass) { + setOutput(new Output().withAs(new OutputAsFunction().withFunction(function, argClass))); + return (SELF) this; + } }