diff --git a/api/src/main/java/io/serverlessworkflow/api/WorkflowWriter.java b/api/src/main/java/io/serverlessworkflow/api/WorkflowWriter.java index 3285cff43..7af3d0290 100644 --- a/api/src/main/java/io/serverlessworkflow/api/WorkflowWriter.java +++ b/api/src/main/java/io/serverlessworkflow/api/WorkflowWriter.java @@ -22,6 +22,8 @@ import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Utility class for writing Serverless Workflow definitions to various outputs and formats. @@ -32,6 +34,8 @@ */ public class WorkflowWriter { + private static final Logger logger = LoggerFactory.getLogger(WorkflowWriter.class); + /** * Writes a {@link Workflow} to the given {@link OutputStream} in the specified format. * @@ -95,7 +99,9 @@ public static void writeWorkflow(Path output, Workflow workflow, WorkflowFormat */ public static String workflowAsString(Workflow workflow, WorkflowFormat format) throws IOException { - return format.mapper().writeValueAsString(workflow); + String result = format.mapper().writeValueAsString(workflow); + logger.trace("Workflow is {}", result); + return result; } /** diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallTaskBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallTaskBuilder.java index 31bf37b46..21a4f5222 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallTaskBuilder.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallTaskBuilder.java @@ -15,8 +15,9 @@ */ package io.serverlessworkflow.fluent.func; +import io.serverlessworkflow.api.reflection.func.SerializableFunction; +import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallTaskJava; import io.serverlessworkflow.api.types.func.ContextFunction; import io.serverlessworkflow.api.types.func.FilterFunction; import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; @@ -29,19 +30,16 @@ public class FuncCallTaskBuilder extends TaskBaseBuilder implements FuncTaskTransformations, ConditionalTaskBuilder { - private CallTaskJava callTaskJava; + private CallTask callTaskJava; - FuncCallTaskBuilder() { - callTaskJava = new CallTaskJava(new CallJava() {}); - super.setTask(callTaskJava.getCallJava()); - } + FuncCallTaskBuilder() {} @Override protected FuncCallTaskBuilder self() { return this; } - public FuncCallTaskBuilder function(Function function) { + public FuncCallTaskBuilder function(SerializableFunction function) { return function(function, null); } @@ -51,8 +49,9 @@ public FuncCallTaskBuilder function(Function function, Class arg public FuncCallTaskBuilder function( Function function, Class argClass, Class returnClass) { - this.callTaskJava = new CallTaskJava(CallJava.function(function, argClass, returnClass)); - super.setTask(this.callTaskJava.getCallJava()); + this.callTaskJava = + new CallTask().withCallFunction(CallJava.function(function, argClass, returnClass)); + super.setTask(this.callTaskJava.getCallFunction()); return this; } @@ -66,8 +65,9 @@ public FuncCallTaskBuilder function(ContextFunction function, Class public FuncCallTaskBuilder function( ContextFunction function, Class argClass, Class returnClass) { - this.callTaskJava = new CallTaskJava(CallJava.function(function, argClass, returnClass)); - super.setTask(this.callTaskJava.getCallJava()); + this.callTaskJava = + new CallTask().withCallFunction(CallJava.function(function, argClass, returnClass)); + super.setTask(this.callTaskJava.getCallFunction()); return this; } @@ -81,26 +81,27 @@ public FuncCallTaskBuilder function(FilterFunction function, Class< public FuncCallTaskBuilder function( FilterFunction function, Class argClass, Class outputClass) { - this.callTaskJava = new CallTaskJava(CallJava.function(function, argClass, outputClass)); - super.setTask(this.callTaskJava.getCallJava()); + this.callTaskJava = + new CallTask().withCallFunction(CallJava.function(function, argClass, outputClass)); + super.setTask(this.callTaskJava.getCallFunction()); return this; } /** Accept a side-effect Consumer; engine should pass input through unchanged. */ public FuncCallTaskBuilder consumer(Consumer consumer) { - this.callTaskJava = new CallTaskJava(CallJava.consumer(consumer)); - super.setTask(this.callTaskJava.getCallJava()); + this.callTaskJava = new CallTask().withCallFunction(CallJava.consumer(consumer)); + super.setTask(this.callTaskJava.getCallFunction()); return this; } /** Accept a Consumer with explicit input type hint. */ public FuncCallTaskBuilder consumer(Consumer consumer, Class argClass) { - this.callTaskJava = new CallTaskJava(CallJava.consumer(consumer, argClass)); - super.setTask(this.callTaskJava.getCallJava()); + this.callTaskJava = new CallTask().withCallFunction(CallJava.consumer(consumer, argClass)); + super.setTask(this.callTaskJava.getCallFunction()); return this; } - public CallTaskJava build() { + public CallTask build() { return this.callTaskJava; } } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForTaskBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForTaskBuilder.java index 3e0a59d60..48235d16e 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForTaskBuilder.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForTaskBuilder.java @@ -15,11 +15,12 @@ */ package io.serverlessworkflow.fluent.func; +import io.serverlessworkflow.api.types.CallTask; +import io.serverlessworkflow.api.types.ForTask; import io.serverlessworkflow.api.types.ForTaskConfiguration; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallTaskJava; import io.serverlessworkflow.api.types.func.ForTaskFunction; import io.serverlessworkflow.api.types.func.LoopFunction; import io.serverlessworkflow.api.types.func.LoopPredicate; @@ -39,14 +40,16 @@ public class FuncForTaskBuilder extends TaskBaseBuilder ConditionalTaskBuilder, ForEachTaskFluent { + private final ForTask forTask; private final ForTaskFunction forTaskFunction; private final List items; FuncForTaskBuilder() { - this.forTaskFunction = new ForTaskFunction(); - this.forTaskFunction.withFor(new ForTaskConfiguration()); + this.forTask = new ForTask(); + this.forTaskFunction = new ForTaskFunction(forTask); + this.forTask.withFor(new ForTaskConfiguration()); this.items = new ArrayList<>(); - super.setTask(forTaskFunction); + super.setTask(forTask); } @Override @@ -84,9 +87,9 @@ public FuncForTaskBuilder tasks(String name, LoopFunction fun name, new Task() .withCallTask( - new CallTaskJava( - CallJava.loopFunction( - function, this.forTaskFunction.getFor().getEach()))))); + new CallTask() + .withCallFunction( + CallJava.loopFunction(function, this.forTask.getFor().getEach()))))); return this; } @@ -96,25 +99,25 @@ public FuncForTaskBuilder tasks(LoopFunction function) { @Override public FuncForTaskBuilder each(String each) { - this.forTaskFunction.getFor().withEach(each); + this.forTask.getFor().withEach(each); return this; } @Override public FuncForTaskBuilder in(String in) { - this.forTaskFunction.getFor().withIn(in); + this.forTask.getFor().withIn(in); return this; } @Override public FuncForTaskBuilder at(String at) { - this.forTaskFunction.getFor().withAt(at); + this.forTask.getFor().withAt(at); return this; } @Override public FuncForTaskBuilder whileC(String expression) { - this.forTaskFunction.setWhile(expression); + this.forTask.setWhile(expression); return this; } @@ -125,8 +128,8 @@ public FuncForTaskBuilder tasks(Consumer consumer) { return this; } - public ForTaskFunction build() { - this.forTaskFunction.setDo(this.items); - return this.forTaskFunction; + public ForTask build() { + this.forTask.setDo(this.items); + return this.forTask; } } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForkTaskBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForkTaskBuilder.java index f7f87e62d..9d43116ce 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForkTaskBuilder.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForkTaskBuilder.java @@ -15,10 +15,10 @@ */ package io.serverlessworkflow.fluent.func; +import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallTaskJava; import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; import io.serverlessworkflow.fluent.func.spi.FuncTaskTransformations; import io.serverlessworkflow.fluent.spec.AbstractForkTaskBuilder; @@ -61,7 +61,8 @@ public FuncForkTaskBuilder branch( this.defaultBranchName(name, this.currentOffset()), new Task() .withCallTask( - new CallTaskJava(CallJava.function(function, argParam, returnClass))))); + new CallTask() + .withCallFunction(CallJava.function(function, argParam, returnClass))))); return this; } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java index 07b32c5f8..86daac8cd 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java @@ -27,6 +27,7 @@ import io.serverlessworkflow.fluent.spec.WaitTaskBuilder; import io.serverlessworkflow.fluent.spec.WorkflowTaskBuilder; import java.util.List; +import java.util.Map; import java.util.function.Consumer; public class FuncTaskItemListBuilder extends BaseTaskItemListBuilder @@ -78,6 +79,10 @@ public FuncTaskItemListBuilder set(String name, String expr) { return this.set(name, s -> s.expr(expr)); } + public FuncTaskItemListBuilder set(String name, Map map) { + return this.set(name, s -> s.expr(map)); + } + @Override public FuncTaskItemListBuilder emit(String name, Consumer itemsConfigurer) { name = this.defaultNameAndRequireConfig(name, itemsConfigurer, TYPE_EMIT); diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java index 160d1b084..6cf7a5836 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java @@ -1600,7 +1600,7 @@ public static FuncTaskConfigurer forEach( } public static FuncTaskConfigurer forEachItem( - SerializableFunction> collection, Function function) { + SerializableFunction> collection, SerializableFunction function) { return forEachItem(null, collection, function); } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTaskTransformations.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTaskTransformations.java index 74be14047..0d741d251 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTaskTransformations.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTaskTransformations.java @@ -16,9 +16,12 @@ package io.serverlessworkflow.fluent.func.spi; import io.serverlessworkflow.api.types.Export; +import io.serverlessworkflow.api.types.ExportAs; import io.serverlessworkflow.api.types.func.ContextFunction; -import io.serverlessworkflow.api.types.func.ExportAsFunction; import io.serverlessworkflow.api.types.func.FilterFunction; +import io.serverlessworkflow.api.types.func.TypedContextFunction; +import io.serverlessworkflow.api.types.func.TypedFilterFunction; +import io.serverlessworkflow.api.types.func.TypedFunction; import io.serverlessworkflow.fluent.spec.spi.TaskTransformationHandlers; import java.util.function.Function; @@ -27,37 +30,43 @@ public interface FuncTaskTransformations SELF exportAs(Function function) { - setExport(new Export().withAs(new ExportAsFunction().withFunction(function))); + setExport(new Export().withAs(new ExportAs().withObject(function))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF exportAs(Function function, Class argClass) { - setExport(new Export().withAs(new ExportAsFunction().withFunction(function, argClass))); + setExport( + new Export() + .withAs(new ExportAs().withObject(new TypedFunction(function, argClass)))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF exportAs(FilterFunction function) { - setExport(new Export().withAs(new ExportAsFunction().withFunction(function))); + setExport(new Export().withAs(new ExportAs().withObject(function))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF exportAs(FilterFunction function, Class argClass) { - setExport(new Export().withAs(new ExportAsFunction().withFunction(function, argClass))); + setExport( + new Export() + .withAs(new ExportAs().withObject(new TypedFilterFunction<>(function, argClass)))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF exportAs(ContextFunction function) { - setExport(new Export().withAs(new ExportAsFunction().withFunction(function))); + setExport(new Export().withAs(new ExportAs().withObject(function))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF exportAs(ContextFunction function, Class argClass) { - setExport(new Export().withAs(new ExportAsFunction().withFunction(function, argClass))); + setExport( + new Export() + .withAs(new ExportAs().withObject(new TypedContextFunction<>(function, argClass)))); return (SELF) this; } } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java index eb21f7aeb..8d1dc8ec2 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java @@ -21,8 +21,9 @@ import io.serverlessworkflow.api.types.OutputAs; import io.serverlessworkflow.api.types.func.ContextFunction; import io.serverlessworkflow.api.types.func.FilterFunction; -import io.serverlessworkflow.api.types.func.InputFromFunction; -import io.serverlessworkflow.api.types.func.OutputAsFunction; +import io.serverlessworkflow.api.types.func.TypedContextFunction; +import io.serverlessworkflow.api.types.func.TypedFilterFunction; +import io.serverlessworkflow.api.types.func.TypedFunction; import io.serverlessworkflow.fluent.spec.spi.TransformationHandlers; import java.util.function.Function; @@ -31,37 +32,42 @@ public interface FuncTransformations> @SuppressWarnings("unchecked") default SELF inputFrom(Function function) { - setInput(new Input().withFrom(new InputFromFunction().withFunction(function))); + setInput(new Input().withFrom(new InputFrom().withObject(function))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF inputFrom(Function function, Class argClass) { - setInput(new Input().withFrom(new InputFromFunction().withFunction(function, argClass))); + setInput( + new Input().withFrom(new InputFrom().withObject(new TypedFunction<>(function, argClass)))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF inputFrom(FilterFunction function) { - setInput(new Input().withFrom(new InputFromFunction().withFunction(function))); + setInput(new Input().withFrom(new InputFrom().withObject(function))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF inputFrom(FilterFunction function, Class argClass) { - setInput(new Input().withFrom(new InputFromFunction().withFunction(function, argClass))); + setInput( + new Input() + .withFrom(new InputFrom().withObject(new TypedFilterFunction<>(function, argClass)))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF inputFrom(ContextFunction function) { - setInput(new Input().withFrom(new InputFromFunction().withFunction(function))); + setInput(new Input().withFrom(new InputFrom().withObject((function)))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF inputFrom(ContextFunction function, Class argClass) { - setInput(new Input().withFrom(new InputFromFunction().withFunction(function, argClass))); + setInput( + new Input() + .withFrom(new InputFrom().withObject(new TypedContextFunction<>(function, argClass)))); return (SELF) this; } @@ -73,37 +79,42 @@ default SELF inputFrom(String jqExpression) { @SuppressWarnings("unchecked") default SELF outputAs(Function function) { - setOutput(new Output().withAs(new OutputAsFunction().withFunction(function))); + setOutput(new Output().withAs(new OutputAs().withObject(function))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF outputAs(Function function, Class argClass) { - setOutput(new Output().withAs(new OutputAsFunction().withFunction(function, argClass))); + setOutput( + new Output().withAs(new OutputAs().withObject(new TypedFunction<>(function, argClass)))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF outputAs(FilterFunction function) { - setOutput(new Output().withAs(new OutputAsFunction().withFunction(function))); + setOutput(new Output().withAs(new OutputAs().withObject(function))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF outputAs(FilterFunction function, Class argClass) { - setOutput(new Output().withAs(new OutputAsFunction().withFunction(function, argClass))); + setOutput( + new Output() + .withAs(new OutputAs().withObject(new TypedFilterFunction<>(function, argClass)))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF outputAs(ContextFunction function) { - setOutput(new Output().withAs(new OutputAsFunction().withFunction(function))); + setOutput(new Output().withAs(new OutputAs().withObject(function))); return (SELF) this; } @SuppressWarnings("unchecked") default SELF outputAs(ContextFunction function, Class argClass) { - setOutput(new Output().withAs(new OutputAsFunction().withFunction(function, argClass))); + setOutput( + new Output() + .withAs(new OutputAs().withObject(new TypedContextFunction<>(function, argClass)))); return (SELF) this; } diff --git a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java index 1f518e99c..b4d89fa28 100644 --- a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java +++ b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java @@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import io.serverlessworkflow.api.types.CallFunction; import io.serverlessworkflow.api.types.CallGRPC; import io.serverlessworkflow.api.types.CallHTTP; import io.serverlessworkflow.api.types.Export; @@ -41,7 +42,6 @@ import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.api.types.Workflow; -import io.serverlessworkflow.api.types.func.CallJava; import io.serverlessworkflow.api.types.func.FilterFunction; import io.serverlessworkflow.fluent.func.dsl.FuncDSL; import java.net.URI; @@ -68,7 +68,7 @@ void function_step_exportAs_function_sets_export() { Task t = items.get(0).getTask(); assertNotNull(t.getCallTask(), "CallTask expected"); - Export ex = ((CallJava) t.getCallTask().get()).getExport(); + Export ex = ((CallFunction) t.getCallTask().get()).getExport(); assertNotNull(ex, "Export should be set via Step.exportAs(Function)"); assertNotNull(ex.getAs(), "'as' should be populated"); // functional export should not produce a literal string @@ -174,7 +174,7 @@ void mixed_chaining_order_and_exports() { assertNotNull(t2.getListenTask()); assertNotNull( - ((CallJava) t0.getCallTask().get()).getExport(), "function step should carry export"); + ((CallFunction) t0.getCallTask().get()).getExport(), "function step should carry export"); assertNotNull(t2.getListenTask().getExport(), "listen step should carry export"); } @@ -465,7 +465,7 @@ void function_step_then_task_name_sets_flow_directive() { Task t = items.get(0).getTask(); assertNotNull(t.getCallTask(), "CallTask expected"); - CallJava callJava = (CallJava) t.getCallTask().get(); + CallFunction callJava = (CallFunction) t.getCallTask().get(); assertNotNull(callJava.getThen(), "then() should be set on the task"); assertEquals("otherTask", callJava.getThen().getString(), "then() should point to 'otherTask'"); } @@ -484,7 +484,7 @@ void function_step_then_flow_directive_enum_sets_end() { Task t = items.get(0).getTask(); assertNotNull(t.getCallTask(), "CallTask expected"); - CallJava callJava = (CallJava) t.getCallTask().get(); + CallFunction callJava = (CallFunction) t.getCallTask().get(); assertNotNull(callJava.getThen(), "then() should be set on the task"); assertEquals( FlowDirectiveEnum.END, @@ -510,7 +510,7 @@ void consume_step_then_task_name_sets_flow_directive() { Task t = items.get(0).getTask(); assertNotNull(t.getCallTask(), "CallTask expected for consume step"); - CallJava callJava = (CallJava) t.getCallTask().get(); + CallFunction callJava = (CallFunction) t.getCallTask().get(); assertNotNull(callJava.getThen(), "then() should be set on the consume task"); assertEquals("otherTask", callJava.getThen().getString(), "then() should point to 'otherTask'"); } @@ -532,7 +532,7 @@ void consume_step_then_flow_directive_enum_sets_end() { Task t = items.get(0).getTask(); assertNotNull(t.getCallTask(), "CallTask expected for consume step"); - CallJava callJava = (CallJava) t.getCallTask().get(); + CallFunction callJava = (CallFunction) t.getCallTask().get(); assertNotNull(callJava.getThen(), "then() should be set on the consume task"); assertEquals( FlowDirectiveEnum.END, diff --git a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLUniqueIdTest.java b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLUniqueIdTest.java index b3ae573c3..db6b3a804 100644 --- a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLUniqueIdTest.java +++ b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLUniqueIdTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.when; import io.serverlessworkflow.api.reflection.func.UniqueIdBiFunction; +import io.serverlessworkflow.api.types.CallFunction; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.api.types.Workflow; @@ -45,9 +46,10 @@ class FuncDSLUniqueIdTest { @SuppressWarnings("unchecked") - private static FilterFunction extractFilterFunction(CallJava callJava) { - if (callJava instanceof CallJava.CallJavaFilterFunction f) { - return (FilterFunction) f.function(); + private static FilterFunction extractFilterFunction(CallFunction callJava) { + if (callJava.getWith().getAdditionalProperties().get(CallJava.FUNCTION_NAME_KEY) + instanceof FilterFunction f) { + return f; } fail("CallTask is not a CallJavaFilterFunction; DSL contract may have changed."); return null; // unreachable @@ -77,7 +79,7 @@ void withUniqueId_uses_json_pointer_for_unique_id() { Task t = items.get(0).getTask(); assertNotNull(t.getCallTask(), "CallTask expected"); - CallJava cj = (CallJava) t.getCallTask().get(); + CallFunction cj = (CallFunction) t.getCallTask().get(); var jff = extractFilterFunction(cj); assertNotNull(jff, "JavaFilterFunction must be present for withUniqueId"); @@ -125,7 +127,7 @@ void agent_uses_json_pointer_for_unique_id() { Task t = items.get(0).getTask(); assertNotNull(t.getCallTask(), "CallTask expected"); - CallJava cj = (CallJava) t.getCallTask().get(); + CallFunction cj = (CallFunction) t.getCallTask().get(); var jff = extractFilterFunction(cj); assertNotNull(jff, "JavaFilterFunction must be present for agent/withUniqueId"); diff --git a/experimental/fluent/jackson/pom.xml b/experimental/fluent/jackson/pom.xml new file mode 100644 index 000000000..10b764f89 --- /dev/null +++ b/experimental/fluent/jackson/pom.xml @@ -0,0 +1,36 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-experimental-fluent + 8.0.0-SNAPSHOT + + serverlessworkflow-experimental-fluent-serialization-jackson + Serverless Workflow :: Experimental :: Fluent :: Serialization:: Jackson + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + + + io.serverlessworkflow + serverlessworkflow-experimental-types + + + io.serverlessworkflow + serverlessworkflow-serialization + + + io.serverlessworkflow + serverlessworkflow-impl-json + + + io.serverlessworkflow + serverlessworkflow-api + + + \ No newline at end of file diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallAbstractJavaFunction.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/AbstractMapValueTransformer.java similarity index 52% rename from experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallAbstractJavaFunction.java rename to experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/AbstractMapValueTransformer.java index 0a8af1201..42a022a37 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallAbstractJavaFunction.java +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/AbstractMapValueTransformer.java @@ -13,27 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.api.types.func; +package io.serverlessworkflow.fluent.func.serialization.jackson; -import java.util.Optional; +import com.fasterxml.jackson.databind.util.StdConverter; +import java.util.Map; -public abstract class CallAbstractJavaFunction extends CallJava { +public abstract class AbstractMapValueTransformer extends StdConverter { - private static final long serialVersionUID = 1L; - - private final Optional> outputClass; - - protected CallAbstractJavaFunction() { - this(Optional.empty(), Optional.empty()); + @Override + public T convert(T value) { + for (Map.Entry entry : map(value).entrySet()) { + GlobalConverterRegistry.get() + .findConverter(entry.getKey()) + .ifPresent(c -> entry.setValue(c.apply(entry.getValue()))); + } + return value; } - protected CallAbstractJavaFunction( - Optional> inputClass, Optional> outputClass) { - super(inputClass); - this.outputClass = outputClass; - } - - public Optional> outputClass() { - return outputClass; - } + protected abstract Map map(T value); } diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncJacksonModule.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncJacksonModule.java new file mode 100644 index 000000000..7e7ebbcc6 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncJacksonModule.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.fluent.func.serialization.jackson; + +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; +import io.serverlessworkflow.api.reflection.func.SerializableConsumer; +import io.serverlessworkflow.api.reflection.func.SerializableFunction; +import io.serverlessworkflow.api.reflection.func.SerializablePredicate; +import io.serverlessworkflow.api.types.FunctionArguments; +import io.serverlessworkflow.api.types.TaskMetadata; +import io.serverlessworkflow.api.types.func.ContextFunction; +import io.serverlessworkflow.api.types.func.FilterFunction; +import io.serverlessworkflow.api.types.func.LoopFunction; +import io.serverlessworkflow.api.types.func.LoopFunctionIndex; +import io.serverlessworkflow.api.types.func.LoopPredicate; +import io.serverlessworkflow.api.types.func.LoopPredicateIndex; +import io.serverlessworkflow.api.types.func.LoopPredicateIndexContext; +import io.serverlessworkflow.api.types.func.LoopPredicateIndexFilter; +import java.lang.invoke.SerializedLambda; +import java.util.List; + +public class FuncJacksonModule extends SimpleModule { + + private static final long serialVersionUID = 1L; + + public void setupModule(com.fasterxml.jackson.databind.Module.SetupContext context) { + SerializableFunctionSerializer serializer = new SerializableFunctionSerializer(); + super.addSerializer(SerializableFunction.class, serializer); + super.addSerializer(SerializablePredicate.class, serializer); + super.addSerializer(SerializableConsumer.class, serializer); + super.addSerializer(ContextFunction.class, serializer); + super.addSerializer(FilterFunction.class, serializer); + super.addSerializer(LoopFunction.class, serializer); + super.addSerializer(LoopFunctionIndex.class, serializer); + super.addSerializer(LoopPredicate.class, serializer); + super.addSerializer(LoopPredicateIndex.class, serializer); + super.addSerializer(LoopPredicateIndexContext.class, serializer); + super.addSerializer(LoopPredicateIndexFilter.class, serializer); + super.addDeserializer(SerializedLambda.class, new SerializedLambdaDeserializer()); + super.setMixInAnnotation(TaskMetadata.class, TaskMetadataMixIn.class); + super.setMixInAnnotation(FunctionArguments.class, FunctionArgumentsMixIn.class); + super.setSerializerModifier( + new BeanSerializerModifier() { + @Override + public List changeProperties( + SerializationConfig config, + BeanDescription beanDesc, + List beanProperties) { + if (beanDesc.getBeanClass().equals(SerializedLambda.class)) { + beanProperties.add(new SerializedLambdaWriter(beanProperties.get(0))); + } + return beanProperties; + } + }); + super.setupModule(context); + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsMixIn.java similarity index 61% rename from experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java rename to experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsMixIn.java index cde6281f2..431e32977 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsMixIn.java @@ -13,24 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.api.types.func; +package io.serverlessworkflow.fluent.func.serialization.jackson; -import io.serverlessworkflow.api.types.CallTask; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -public class CallTaskJava extends CallTask { - - private CallJava callJava; - - public CallTaskJava(CallJava callJava) { - this.callJava = callJava; - } - - public CallJava getCallJava() { - return callJava; - } - - @Override - public Object get() { - return callJava != null ? callJava : super.get(); - } -} +@JsonDeserialize(converter = FunctionArgumentsTransformer.class) +public abstract class FunctionArgumentsMixIn {} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsTransformer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsTransformer.java new file mode 100644 index 000000000..9825d575f --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsTransformer.java @@ -0,0 +1,35 @@ +/* + * 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.serialization.jackson; + +import io.serverlessworkflow.api.types.FunctionArguments; +import io.serverlessworkflow.api.types.func.CallJava; +import java.util.Map; + +public class FunctionArgumentsTransformer extends AbstractMapValueTransformer { + + static { + GlobalConverterRegistry.get() + .registerConverter(CallJava.FUNCTION_NAME_KEY, SerializationUtils::getFunction) + .registerConverter(CallJava.INPUT_CLASS_KEY, SerializationUtils::getClassOrEmpty) + .registerConverter(CallJava.OUTPUT_CLASS_KEY, SerializationUtils::getClassOrEmpty); + } + + @Override + protected Map map(FunctionArguments value) { + return value.getAdditionalProperties(); + } +} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/GlobalConverterRegistry.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/GlobalConverterRegistry.java new file mode 100644 index 000000000..6d63448e8 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/GlobalConverterRegistry.java @@ -0,0 +1,44 @@ +/* + * 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.serialization.jackson; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +public class GlobalConverterRegistry { + + private static GlobalConverterRegistry instance = new GlobalConverterRegistry(); + private Map> converterMap; + + private GlobalConverterRegistry() { + this.converterMap = new HashMap<>(); + } + + public static GlobalConverterRegistry get() { + return instance; + } + + public GlobalConverterRegistry registerConverter(String key, Function converter) { + converterMap.put(key, converter); + return this; + } + + public Optional findConverter(String key) { + return Optional.ofNullable(converterMap.get(key)); + } +} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializableFunctionSerializer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializableFunctionSerializer.java new file mode 100644 index 000000000..8e90f5427 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializableFunctionSerializer.java @@ -0,0 +1,38 @@ +/* + * 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.serialization.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import io.serverlessworkflow.api.reflection.func.ReflectionUtils; +import java.io.IOException; +import java.lang.invoke.SerializedLambda; +import java.util.Optional; + +public class SerializableFunctionSerializer extends JsonSerializer { + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + Optional serializedLambda = ReflectionUtils.serializedFromFuntion(value); + if (serializedLambda.isPresent()) { + gen.writeObject(serializedLambda.orElseThrow()); + } else { + gen.writeString(value.toString()); + } + } +} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializationUtils.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializationUtils.java new file mode 100644 index 000000000..25284e88d --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializationUtils.java @@ -0,0 +1,66 @@ +/* + * 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.serialization.jackson; + +import io.serverlessworkflow.api.reflection.func.ReflectionUtils; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import java.lang.invoke.SerializedLambda; +import java.util.Map; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SerializationUtils { + + private static final Logger logger = LoggerFactory.getLogger(SerializationUtils.class); + + private SerializationUtils() {} + + public static Optional> getClassOrEmpty(Object obj) { + + if (obj instanceof Optional optional) { + return optional; + } else if (obj instanceof Class clazz) { + return Optional.of(clazz); + } else if (obj instanceof String str) { + try { + return Optional.of(ReflectionUtils.loadClass(str)); + } catch (ClassNotFoundException e) { + logger.error("Error loading class {}. Returning optinal empty", str, e); + } + } + return Optional.empty(); + } + + public static Optional getSerializedLambda(Object obj) { + return obj instanceof Map map + ? Optional.of(JsonUtils.convertValue(map, SerializedLambda.class)) + : Optional.empty(); + } + + public static Object getFunction(Object obj) { + try { + return ReflectionUtils.functionFromSerialized( + getSerializedLambda(obj) + .orElseThrow( + () -> + new IllegalStateException( + "Expecting function object to be a Map, but it was " + obj))); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializedLambdaDeserializer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializedLambdaDeserializer.java new file mode 100644 index 000000000..6e796dca7 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializedLambdaDeserializer.java @@ -0,0 +1,95 @@ +/* + * 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.serialization.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.serverlessworkflow.api.reflection.func.ReflectionUtils; +import java.io.IOException; +import java.lang.invoke.SerializedLambda; + +public class SerializedLambdaDeserializer extends JsonDeserializer { + + static final String CAPTURING_CLASS = "capturingClass"; + static final String FUNCTIONAL_CLASS = "functionalInterfaceClass"; + static final String FUNCTIONAL_METHOD_NAME = "functionalInterfaceMethodName"; + static final String FUNCTIONAL_METHOD_SIGNATURE = "functionalInterfaceMethodSignature"; + static final String METHOD_KIND = "implMethodKind"; + static final String METHOD_CLASS = "implClass"; + static final String METHOD_NAME = "implMethodName"; + static final String METHOD_SIGNATURE = "implMethodSignature"; + static final String METHOD_TYPE = "instantiatedMethodType"; + static final String CAPTURED_ARGS = "capturedArgs"; + static final String TYPE_CAPTURE_ARG = "type"; + static final String DATA_CAPTURE_ARG = "data"; + + @Override + public SerializedLambda deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException { + TreeNode tree = p.readValueAsTree(); + + if (tree instanceof ObjectNode node) { + try { + return new SerializedLambda( + ReflectionUtils.loadCapturingClass(node.get(CAPTURING_CLASS).asText()), + node.get(FUNCTIONAL_CLASS).asText(), + node.get(FUNCTIONAL_METHOD_NAME).asText(), + node.get(FUNCTIONAL_METHOD_SIGNATURE).asText(), + node.get(METHOD_KIND).asInt(), + node.get(METHOD_CLASS).asText(), + node.get(METHOD_NAME).asText(), + node.get(METHOD_SIGNATURE).asText(), + node.get(METHOD_TYPE).asText(), + fromArray(ctxt, (ArrayNode) node.get(CAPTURED_ARGS))); + } catch (ReflectiveOperationException ex) { + throw new IOException("Error unmarshalling SerializedLambda " + node, ex); + } + } else { + throw new IOException( + "Node " + + tree + + " is not an object and therefore cannot be converted into SerializedLambda"); + } + } + + private Object[] fromArray(DeserializationContext ctxt, ArrayNode node) + throws IOException, ReflectiveOperationException { + if (node == null) { + return new Object[0]; + } else { + Object[] result = new Object[node.size()]; + for (int i = 0; i < result.length; i++) { + ObjectNode objectNode = (ObjectNode) node.get(i); + JsonNode type = objectNode.get(TYPE_CAPTURE_ARG); + if (type != null && type.isTextual()) { + Object value = + ctxt.readTreeAsValue( + objectNode.get(DATA_CAPTURE_ARG), ReflectionUtils.loadClass(type.asText())); + result[i] = + value instanceof SerializedLambda sl + ? ReflectionUtils.functionFromSerialized(sl) + : value; + } + } + return result; + } + } +} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializedLambdaWriter.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializedLambdaWriter.java new file mode 100644 index 000000000..97436974f --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializedLambdaWriter.java @@ -0,0 +1,70 @@ +/* + * 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.serialization.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; +import io.serverlessworkflow.api.reflection.func.ReflectionUtils; +import java.io.IOException; +import java.lang.invoke.SerializedLambda; + +public class SerializedLambdaWriter extends BeanPropertyWriter { + + private static final long serialVersionUID = 1L; + + public SerializedLambdaWriter(BeanPropertyWriter base) { + super(base); + } + + @Override + public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) + throws IOException { + ObjectMapper mapper = (ObjectMapper) gen.getCodec(); + boolean isEnabled = mapper.isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS); + if (isEnabled) { + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + } + try { + SerializedLambda sl = (SerializedLambda) bean; + int size = sl.getCapturedArgCount(); + if (size > 0) { + gen.writeArrayFieldStart(SerializedLambdaDeserializer.CAPTURED_ARGS); + for (int i = 0; i < size; i++) { + Object obj = sl.getCapturedArg(i); + gen.writeStartObject(); + if (obj != null) { + + gen.writeStringField( + SerializedLambdaDeserializer.TYPE_CAPTURE_ARG, + ReflectionUtils.serializedFromFuntion(obj) + .map(v -> SerializedLambda.class.getName()) + .orElse(obj.getClass().getName())); + gen.writeObjectField(SerializedLambdaDeserializer.DATA_CAPTURE_ARG, obj); + } + gen.writeEndObject(); + } + gen.writeEndArray(); + } + } finally { + if (isEnabled) { + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, true); + } + } + } +} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataMixIn.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataMixIn.java new file mode 100644 index 000000000..555253ab7 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataMixIn.java @@ -0,0 +1,21 @@ +/* + * 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.serialization.jackson; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(converter = TaskMetadataTransformer.class) +public abstract class TaskMetadataMixIn {} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataTransformer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataTransformer.java new file mode 100644 index 000000000..fbfc7ff58 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataTransformer.java @@ -0,0 +1,38 @@ +/* + * 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.serialization.jackson; + +import io.serverlessworkflow.api.types.TaskMetadata; +import io.serverlessworkflow.api.types.func.ForTaskFunction; +import java.util.Map; + +public class TaskMetadataTransformer extends AbstractMapValueTransformer { + + static { + GlobalConverterRegistry registry = GlobalConverterRegistry.get(); + registry + .registerConverter(ForTaskFunction.COLLECTION, SerializationUtils::getFunction) + .registerConverter(ForTaskFunction.WHILE_PREDICATE, SerializationUtils::getFunction) + .registerConverter(ForTaskFunction.FOR_CLASS, SerializationUtils::getClassOrEmpty) + .registerConverter(ForTaskFunction.WHILE_CLASS, SerializationUtils::getClassOrEmpty) + .registerConverter(ForTaskFunction.ITEM_CLASS, SerializationUtils::getClassOrEmpty); + } + + @Override + protected Map map(TaskMetadata value) { + return value.getAdditionalProperties(); + } +} diff --git a/experimental/fluent/jackson/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module b/experimental/fluent/jackson/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module new file mode 100644 index 000000000..3ae3675c6 --- /dev/null +++ b/experimental/fluent/jackson/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module @@ -0,0 +1 @@ +io.serverlessworkflow.fluent.func.serialization.jackson.FuncJacksonModule \ No newline at end of file diff --git a/experimental/fluent/pom.xml b/experimental/fluent/pom.xml index 56bd2f5aa..15f289354 100644 --- a/experimental/fluent/pom.xml +++ b/experimental/fluent/pom.xml @@ -60,5 +60,6 @@ func + jackson \ No newline at end of file diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/AbstractJavaCallExecutor.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/AbstractJavaCallExecutor.java index 466a9c277..c723fc87c 100644 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/AbstractJavaCallExecutor.java +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/AbstractJavaCallExecutor.java @@ -31,10 +31,6 @@ public abstract class AbstractJavaCallExecutor implements CallableTask { private final boolean directCompletable; private final boolean convertedCompletable; - protected AbstractJavaCallExecutor() { - this(Optional.empty(), Optional.empty()); - } - protected AbstractJavaCallExecutor( Optional> inputClass, Optional> outputClass) { this.inputClass = inputClass; diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaCallFunctionBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaCallFunctionBuilder.java new file mode 100644 index 000000000..4e6afcc2e --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaCallFunctionBuilder.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.executors.func; + +import io.serverlessworkflow.api.types.CallFunction; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.api.types.func.ContextFunction; +import io.serverlessworkflow.api.types.func.FilterFunction; +import io.serverlessworkflow.api.types.func.LoopFunction; +import io.serverlessworkflow.api.types.func.LoopFunctionIndex; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowMutablePosition; +import io.serverlessworkflow.impl.executors.CallFunctionExecutorBuilder; +import io.serverlessworkflow.impl.executors.CallableTaskFactory; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +public class JavaCallFunctionBuilder extends CallFunctionExecutorBuilder { + + @Override + public int priority() { + return DEFAULT_PRIORITY - 10; + } + + @Override + public CallableTaskFactory init( + CallFunction task, WorkflowDefinition definition, WorkflowMutablePosition position) { + if (task.getCall().equals(CallJava.JAVA_CALL_KEY)) { + Map props = task.getWith().getAdditionalProperties(); + Object obj = props.get(CallJava.FUNCTION_NAME_KEY); + Optional> input = + (Optional>) props.getOrDefault(CallJava.INPUT_CLASS_KEY, Optional.empty()); + Optional> output = + (Optional>) props.getOrDefault(CallJava.OUTPUT_CLASS_KEY, Optional.empty()); + if (obj instanceof ContextFunction fn) { + return () -> new JavaContextFunctionCallExecutor(input, output, fn); + } else if (obj instanceof FilterFunction fn) { + return () -> new JavaFilterFunctionCallExecutor(input, output, fn); + } else if (obj instanceof LoopFunction loop) { + return () -> + new JavaLoopFunctionCallExecutor( + loop, (String) props.get(CallJava.VAR_NAME_KEY), input, output); + } else if (obj instanceof LoopFunctionIndex loop) { + return () -> + new JavaLoopFunctionIndexCallExecutor( + loop, + (String) props.get(CallJava.VAR_NAME_KEY), + (String) props.get(CallJava.INDEX_NAME_KEY), + input, + output); + + } else if (obj instanceof Function fn) { + return () -> new JavaFunctionCallExecutor(input, output, fn); + } else if (obj instanceof Consumer consumer) { + return () -> new JavaConsumerCallExecutor(input, consumer); + } else { + throw new UnsupportedOperationException("Unrecognized function " + obj); + } + } else { + return super.init(task, definition, position); + } + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaConsumerCallExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaConsumerCallExecutorBuilder.java deleted file mode 100644 index 555d331d5..000000000 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaConsumerCallExecutorBuilder.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.impl.executors.func; - -import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.WorkflowMutablePosition; -import io.serverlessworkflow.impl.executors.CallableTaskBuilder; -import io.serverlessworkflow.impl.executors.CallableTaskFactory; - -public class JavaConsumerCallExecutorBuilder - implements CallableTaskBuilder> { - - public CallableTaskFactory init( - CallJava.CallJavaConsumer task, - WorkflowDefinition definition, - WorkflowMutablePosition position) { - return () -> new JavaConsumerCallExecutor(task.inputClass(), task.consumer()); - } - - @Override - public boolean accept(Class clazz) { - return CallJava.CallJavaConsumer.class.isAssignableFrom(clazz); - } -} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaContextFunctionCallExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaContextFunctionCallExecutorBuilder.java deleted file mode 100644 index 1c6126b7e..000000000 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaContextFunctionCallExecutorBuilder.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.impl.executors.func; - -import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallJava.CallJavaContextFunction; -import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.WorkflowMutablePosition; -import io.serverlessworkflow.impl.executors.CallableTaskBuilder; -import io.serverlessworkflow.impl.executors.CallableTaskFactory; - -public class JavaContextFunctionCallExecutorBuilder - implements CallableTaskBuilder> { - - @Override - public boolean accept(Class clazz) { - return CallJava.CallJavaContextFunction.class.isAssignableFrom(clazz); - } - - @Override - public CallableTaskFactory init( - CallJavaContextFunction task, - WorkflowDefinition definition, - WorkflowMutablePosition position) { - return () -> - new JavaContextFunctionCallExecutor( - task.inputClass(), task.outputClass(), task.function()); - } -} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFilterFunctionCallExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFilterFunctionCallExecutorBuilder.java deleted file mode 100644 index 6178ada8c..000000000 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFilterFunctionCallExecutorBuilder.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.impl.executors.func; - -import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallJava.CallJavaFilterFunction; -import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.WorkflowMutablePosition; -import io.serverlessworkflow.impl.executors.CallableTaskBuilder; -import io.serverlessworkflow.impl.executors.CallableTaskFactory; - -public class JavaFilterFunctionCallExecutorBuilder - implements CallableTaskBuilder> { - - @Override - public boolean accept(Class clazz) { - return CallJava.CallJavaFilterFunction.class.isAssignableFrom(clazz); - } - - @Override - public CallableTaskFactory init( - CallJavaFilterFunction task, - WorkflowDefinition definition, - WorkflowMutablePosition position) { - return () -> - new JavaFilterFunctionCallExecutor<>( - task.inputClass(), task.outputClass(), task.function()); - } -} 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 8b1c23b3c..3663bb085 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 @@ -32,51 +32,53 @@ public class JavaForExecutorBuilder extends ForExecutorBuilder { + private final ForTaskFunction taskFunctions; + protected JavaForExecutorBuilder( WorkflowMutablePosition position, ForTask task, WorkflowDefinition definition) { super(position, task, definition); + this.taskFunctions = new ForTaskFunction(task); } @Override protected Optional buildWhileFilter() { - if (task instanceof ForTaskFunction taskFunctions) { - final LoopPredicateIndexFilter whilePred = taskFunctions.getWhilePredicate(); - Optional> whileClass = taskFunctions.getWhileClass(); - String varName = task.getFor().getEach(); - String indexName = task.getFor().getAt(); - if (whilePred != null) { - return Optional.of( - (w, t, n) -> { - Object item = safeObject(t.variables().get(varName)); - return whilePred.test( - JavaFuncUtils.convert(n, whileClass), - item, - (Integer) safeObject(t.variables().get(indexName)), - w, - t); - }); - } + final LoopPredicateIndexFilter whilePred = taskFunctions.getWhilePredicate(); + Optional> whileClass = taskFunctions.getWhileClass(); + String varName = task.getFor().getEach(); + String indexName = task.getFor().getAt(); + if (whilePred != null) { + return Optional.of( + (w, t, n) -> { + Object item = safeObject(t.variables().get(varName)); + return whilePred.test( + JavaFuncUtils.convert(n, whileClass), + item, + (Integer) safeObject(t.variables().get(indexName)), + w, + t); + }); } return super.buildWhileFilter(); } protected WorkflowValueResolver> buildCollectionFilter() { - return task instanceof ForTaskFunction taskFunctions + Object inCollection = collectionFilterObject(); + return inCollection != null ? application .expressionFactory() - .resolveCollection(ExpressionDescriptor.object(collectionFilterObject(taskFunctions))) + .resolveCollection(ExpressionDescriptor.object(inCollection)) : super.buildCollectionFilter(); } - private Object collectionFilterObject(ForTaskFunction taskFunctions) { + private Object collectionFilterObject() { return taskFunctions .getForClass() .map(forClass -> typedCollectionFunction(taskFunctions, forClass)) - .orElse(taskFunctions.getCollection()); + .orElse(taskFunctions.getInCollection()); } @SuppressWarnings({"rawtypes", "unchecked"}) private Object typedCollectionFunction(ForTaskFunction taskFunctions, Class forClass) { - return new TypedFunction(taskFunctions.getCollection(), forClass); + return new TypedFunction(taskFunctions.getInCollection(), forClass); } } diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFunctionCallExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFunctionCallExecutorBuilder.java deleted file mode 100644 index 57058bbb3..000000000 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFunctionCallExecutorBuilder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.impl.executors.func; - -import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallJava.CallJavaFunction; -import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.WorkflowMutablePosition; -import io.serverlessworkflow.impl.executors.CallableTaskBuilder; -import io.serverlessworkflow.impl.executors.CallableTaskFactory; - -public class JavaFunctionCallExecutorBuilder - implements CallableTaskBuilder> { - - @Override - public boolean accept(Class clazz) { - return CallJava.CallJavaFunction.class.isAssignableFrom(clazz); - } - - @Override - public CallableTaskFactory init( - CallJavaFunction task, - WorkflowDefinition definition, - WorkflowMutablePosition position) { - return () -> - new JavaFunctionCallExecutor<>(task.inputClass(), task.outputClass(), task.function()); - } -} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutor.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutor.java index 840f3a201..f22af220a 100644 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutor.java +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutor.java @@ -20,13 +20,19 @@ import io.serverlessworkflow.api.types.func.LoopFunction; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; +import java.util.Optional; public class JavaLoopFunctionCallExecutor extends AbstractJavaCallExecutor { private final LoopFunction function; private final String varName; - public JavaLoopFunctionCallExecutor(LoopFunction function, String varName) { + public JavaLoopFunctionCallExecutor( + LoopFunction function, + String varName, + Optional> inputClass, + Optional> outputClass) { + super(inputClass, outputClass); this.function = function; this.varName = varName; } diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutorBuilder.java deleted file mode 100644 index 5c95ec611..000000000 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutorBuilder.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.impl.executors.func; - -import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallJava.CallJavaLoopFunction; -import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.WorkflowMutablePosition; -import io.serverlessworkflow.impl.executors.CallableTaskBuilder; -import io.serverlessworkflow.impl.executors.CallableTaskFactory; - -public class JavaLoopFunctionCallExecutorBuilder - implements CallableTaskBuilder { - - @Override - public boolean accept(Class clazz) { - return CallJava.CallJavaLoopFunction.class.isAssignableFrom(clazz); - } - - @Override - public CallableTaskFactory init( - CallJavaLoopFunction task, WorkflowDefinition definition, WorkflowMutablePosition position) { - return () -> new JavaLoopFunctionCallExecutor<>(task.function(), task.varName()); - } -} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutor.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutor.java index 5e4837d52..360c65174 100644 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutor.java +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutor.java @@ -20,6 +20,7 @@ import io.serverlessworkflow.api.types.func.LoopFunctionIndex; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; +import java.util.Optional; public class JavaLoopFunctionIndexCallExecutor extends AbstractJavaCallExecutor { @@ -28,7 +29,12 @@ public class JavaLoopFunctionIndexCallExecutor extends AbstractJavaCall private final String indexName; public JavaLoopFunctionIndexCallExecutor( - LoopFunctionIndex function, String varName, String indexName) { + LoopFunctionIndex function, + String varName, + String indexName, + Optional> inputClass, + Optional> outputClass) { + super(inputClass, outputClass); this.function = function; this.varName = varName; this.indexName = indexName; diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutorBuilder.java deleted file mode 100644 index 7e6f0c9d9..000000000 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutorBuilder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.impl.executors.func; - -import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallJava.CallJavaLoopFunctionIndex; -import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.WorkflowMutablePosition; -import io.serverlessworkflow.impl.executors.CallableTaskBuilder; -import io.serverlessworkflow.impl.executors.CallableTaskFactory; - -public class JavaLoopFunctionIndexCallExecutorBuilder - implements CallableTaskBuilder { - - @Override - public boolean accept(Class clazz) { - return CallJava.CallJavaLoopFunctionIndex.class.isAssignableFrom(clazz); - } - - @Override - public CallableTaskFactory init( - CallJavaLoopFunctionIndex task, - WorkflowDefinition definition, - WorkflowMutablePosition position) { - return () -> - new JavaLoopFunctionIndexCallExecutor<>(task.function(), task.varName(), task.indexName()); - } -} diff --git a/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTaskBuilder b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTaskBuilder index 01f6607e8..e6c043b77 100644 --- a/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTaskBuilder +++ b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTaskBuilder @@ -1,6 +1,2 @@ -io.serverlessworkflow.impl.executors.func.JavaLoopFunctionIndexCallExecutorBuilder -io.serverlessworkflow.impl.executors.func.JavaLoopFunctionCallExecutorBuilder -io.serverlessworkflow.impl.executors.func.JavaFunctionCallExecutorBuilder -io.serverlessworkflow.impl.executors.func.JavaConsumerCallExecutorBuilder -io.serverlessworkflow.impl.executors.func.JavaContextFunctionCallExecutorBuilder -io.serverlessworkflow.impl.executors.func.JavaFilterFunctionCallExecutorBuilder +io.serverlessworkflow.impl.executors.func.JavaCallFunctionBuilder + diff --git a/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/CallJavaContextFunctionTest.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/CallJavaContextFunctionTest.java index 3a90da348..a7d46b94a 100644 --- a/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/CallJavaContextFunctionTest.java +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/CallJavaContextFunctionTest.java @@ -17,12 +17,12 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Document; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallTaskJava; import io.serverlessworkflow.api.types.func.ContextFunction; import io.serverlessworkflow.impl.WorkflowApplication; import java.util.List; @@ -57,7 +57,8 @@ void testJavaContextFunction_simple() throws InterruptedException, ExecutionExce "javaContextCall", new Task() .withCallTask( - new CallTaskJava(CallJava.function(ctxFn, Person.class)))))); + new CallTask() + .withCallFunction(CallJava.function(ctxFn, Person.class)))))); var out = app.workflowDefinition(workflow) diff --git a/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/CallTest.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/CallTest.java index 5427b5c28..0f73fc7ce 100644 --- a/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/CallTest.java +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/CallTest.java @@ -17,9 +17,12 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.serverlessworkflow.api.types.CallFunction; +import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Document; import io.serverlessworkflow.api.types.FlowDirective; import io.serverlessworkflow.api.types.FlowDirectiveEnum; +import io.serverlessworkflow.api.types.ForTask; import io.serverlessworkflow.api.types.ForTaskConfiguration; import io.serverlessworkflow.api.types.SwitchItem; import io.serverlessworkflow.api.types.SwitchTask; @@ -28,7 +31,6 @@ import io.serverlessworkflow.api.types.TaskMetadata; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallTaskJava; import io.serverlessworkflow.api.types.func.ForTaskFunction; import io.serverlessworkflow.api.types.func.SwitchCasePredicate; import io.serverlessworkflow.api.types.func.TaskMetadataKeys; @@ -72,7 +74,8 @@ private void internalJavaFunctionTest(Function function, Class c "javaCall", new Task() .withCallTask( - new CallTaskJava(CallJava.function(function, clazz)))))); + new CallTask() + .withCallFunction(CallJava.function(function, clazz)))))); assertThat( app.workflowDefinition(workflow) @@ -99,20 +102,24 @@ void testForLoop() throws InterruptedException, ExecutionException { "forLoop", new Task() .withForTask( - new ForTaskFunction() + new ForTaskFunction( + new ForTask() + .withFor(forConfig) + .withDo( + List.of( + new TaskItem( + "javaCall", + new Task() + .withCallTask( + new CallTask() + .withCallFunction( + CallJava.loopFunction( + CallTest::sum, + forConfig + .getEach()))))))) .withWhile(CallTest::isEven) .withCollection(v -> v, Collection.class) - .withFor(forConfig) - .withDo( - List.of( - new TaskItem( - "javaCall", - new Task() - .withCallTask( - new CallTaskJava( - CallJava.loopFunction( - CallTest::sum, - forConfig.getEach())))))))))); + .task())))); assertThat( app.workflowDefinition(workflow) @@ -152,7 +159,9 @@ void testSwitch() throws InterruptedException, ExecutionException { new TaskItem( "java", new Task() - .withCallTask(new CallTaskJava(CallJava.function(CallTest::zero)))))); + .withCallTask( + new CallTask() + .withCallFunction(CallJava.function(CallTest::zero)))))); WorkflowDefinition definition = app.workflowDefinition(workflow); assertThat(definition.instance(3).start().get().asNumber().orElseThrow()).isEqualTo(3); @@ -173,9 +182,11 @@ void testIf() throws InterruptedException, ExecutionException { "java", new Task() .withCallTask( - new CallTaskJava( - withPredicate( - CallJava.function(CallTest::zero), CallTest::isOdd)))))); + new CallTask() + .withCallFunction( + withPredicate( + CallJava.function(CallTest::zero), + CallTest::isOdd)))))); WorkflowDefinition definition = app.workflowDefinition(workflow); assertThat(definition.instance(3).start().get().asNumber().orElseThrow()).isEqualTo(0); assertThat(definition.instance(4).start().get().asNumber().orElseThrow()).isEqualTo(4); @@ -195,21 +206,21 @@ void testIfWithModel() throws InterruptedException, ExecutionException { "java", new Task() .withCallTask( - new CallTaskJava( - withPredicate( - CallJava.function( - CallTest::zeroWithModel, WorkflowModel.class), - CallTest::isOdd)))))); + new CallTask() + .withCallFunction( + withPredicate( + CallJava.function( + CallTest::zeroWithModel, WorkflowModel.class), + CallTest::isOdd)))))); WorkflowDefinition definition = app.workflowDefinition(workflow); assertThat(definition.instance(3).start().get().asNumber().orElseThrow()).isEqualTo(0); assertThat(definition.instance(4).start().get().asNumber().orElseThrow()).isEqualTo(4); } } - private CallJava withPredicate(CallJava call, Predicate pred) { - return (CallJava) - call.withMetadata( - new TaskMetadata().withAdditionalProperty(TaskMetadataKeys.IF_PREDICATE, pred)); + private CallFunction withPredicate(CallFunction call, Predicate pred) { + return call.withMetadata( + new TaskMetadata().withAdditionalProperty(TaskMetadataKeys.IF_PREDICATE, pred)); } public static boolean isEven(Object model, Integer number) { diff --git a/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/ForTaskFunctionRegressionTest.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/ForTaskFunctionRegressionTest.java index e000f4987..57e5e375b 100644 --- a/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/ForTaskFunctionRegressionTest.java +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/ForTaskFunctionRegressionTest.java @@ -17,20 +17,16 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.ForTask; import io.serverlessworkflow.api.types.ForTaskConfiguration; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallTaskJava; import io.serverlessworkflow.api.types.func.ForTaskFunction; import io.serverlessworkflow.impl.WorkflowApplication; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.lang.reflect.Field; import java.util.Collection; import java.util.List; import java.util.concurrent.ExecutionException; @@ -38,30 +34,6 @@ class ForTaskFunctionRegressionTest { - @Test - void initializesOptionalFieldsAsEmpty() { - ForTaskFunction taskFunction = new ForTaskFunction(); - - assertThat(taskFunction.getWhileClass()).isNotNull().isEmpty(); - assertThat(taskFunction.getItemClass()).isNotNull().isEmpty(); - assertThat(taskFunction.getForClass()).isNotNull().isEmpty(); - } - - @Test - void optionalFieldsSurviveJavaSerializationRoundTrip() throws Exception { - ForTaskFunction taskFunction = new ForTaskFunction(); - clearField(taskFunction, "whileClass"); - clearField(taskFunction, "itemClass"); - clearField(taskFunction, "forClass"); - clearField(taskFunction, "collection"); - - ForTaskFunction copy = roundTrip(taskFunction); - - assertThat(copy.getWhileClass()).isNotNull().isEmpty(); - assertThat(copy.getItemClass()).isNotNull().isEmpty(); - assertThat(copy.getForClass()).isNotNull().isEmpty(); - } - @Test void forLoopWithExplicitCollectionClassExecutesSuccessfully() throws InterruptedException, ExecutionException { @@ -80,43 +52,28 @@ void forLoopWithExplicitCollectionClassExecutesSuccessfully() "forLoop", new Task() .withForTask( - new ForTaskFunction() + new ForTaskFunction( + new ForTask() + .withDo( + List.of( + new TaskItem( + "javaCall", + new Task() + .withCallTask( + new CallTask() + .withCallFunction( + CallJava.loopFunction( + CallTest::sum, + forConfig + .getEach())))))) + .withFor(forConfig)) .withWhile(CallTest::isEven) .withCollection(v -> v, Collection.class) - .withFor(forConfig) - .withDo( - List.of( - new TaskItem( - "javaCall", - new Task() - .withCallTask( - new CallTaskJava( - CallJava.loopFunction( - CallTest::sum, - forConfig.getEach())))))))))); + .task())))); var result = app.workflowDefinition(workflow).instance(List.of(2, 4, 6)).start().get(); assertThat(result.asNumber().orElseThrow()).isEqualTo(12); } } - - private static ForTaskFunction roundTrip(ForTaskFunction taskFunction) throws Exception { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - try (ObjectOutputStream oos = new ObjectOutputStream(output)) { - oos.writeObject(taskFunction); - } - - try (ObjectInputStream ois = - new ObjectInputStream(new ByteArrayInputStream(output.toByteArray()))) { - return (ForTaskFunction) ois.readObject(); - } - } - - private static void clearField(Object target, String fieldName) - throws ReflectiveOperationException { - Field field = target.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - field.set(target, null); - } } diff --git a/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/ModelTest.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/ModelTest.java index aef280b93..bb9dddf2a 100644 --- a/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/ModelTest.java +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/ModelTest.java @@ -20,6 +20,7 @@ import io.serverlessworkflow.api.types.Document; import io.serverlessworkflow.api.types.DurationInline; import io.serverlessworkflow.api.types.Output; +import io.serverlessworkflow.api.types.OutputAs; import io.serverlessworkflow.api.types.Set; import io.serverlessworkflow.api.types.SetTask; import io.serverlessworkflow.api.types.Task; @@ -27,13 +28,15 @@ import io.serverlessworkflow.api.types.TimeoutAfter; import io.serverlessworkflow.api.types.WaitTask; import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.ContextFunction; +import io.serverlessworkflow.api.types.func.FilterFunction; import io.serverlessworkflow.api.types.func.MapSetTaskConfiguration; -import io.serverlessworkflow.api.types.func.OutputAsFunction; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowInstance; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; +import java.util.function.Function; import org.junit.jupiter.api.Test; class ModelTest { @@ -59,7 +62,9 @@ void testStringExpression() throws InterruptedException, ExecutionException { .withOutput( new Output() .withAs( - new OutputAsFunction().withFunction(JavaFunctions::addJavieritoString))); + new OutputAs() + .withObject( + (Function) JavaFunctions::addJavieritoString))); assertThat( app.workflowDefinition(workflow) @@ -94,9 +99,10 @@ void testMapExpression() throws InterruptedException, ExecutionException { .withOutput( new Output() .withAs( - new OutputAsFunction() - .withFunction( - JavaFunctions::addJavierito))))))); + new OutputAs() + .withObject( + (Function) + JavaFunctions::addJavierito))))))); assertThat( app.workflowDefinition(workflow) .instance(Map.of()) @@ -129,7 +135,9 @@ void testStringPOJOExpression() throws InterruptedException, ExecutionException new DurationInline().withMilliseconds(10))))))) .withOutput( new Output() - .withAs(new OutputAsFunction().withFunction(JavaFunctions::personPojo))); + .withAs( + new OutputAs() + .withObject((Function) JavaFunctions::personPojo))); assertThat( app.workflowDefinition(workflow) @@ -162,7 +170,10 @@ void testPOJOStringExpression() throws InterruptedException, ExecutionException .withDurationInline( new DurationInline().withMilliseconds(10))))))) .withOutput( - new Output().withAs(new OutputAsFunction().withFunction(JavaFunctions::getName))); + new Output() + .withAs( + new OutputAs() + .withObject((Function) JavaFunctions::getName))); assertThat( app.workflowDefinition(workflow) @@ -195,7 +206,11 @@ void testPOJOStringExpressionWithContext() throws InterruptedException, Executio new DurationInline().withMilliseconds(10))))))) .withOutput( new Output() - .withAs(new OutputAsFunction().withFunction(JavaFunctions::getContextName))); + .withAs( + new OutputAs() + .withObject( + (ContextFunction) + JavaFunctions::getContextName))); WorkflowInstance instance = app.workflowDefinition(workflow).instance(new Person("Francisco", 33)); assertThat(instance.start().get().asText().orElseThrow()) @@ -220,8 +235,10 @@ void testPOJOStringExpressionWithFilter() throws InterruptedException, Execution .withOutput( new Output() .withAs( - new OutputAsFunction() - .withFunction(JavaFunctions::getFilterName))) + new OutputAs() + .withObject( + (FilterFunction) + JavaFunctions::getFilterName))) .withWait( new TimeoutAfter() .withDurationInline( diff --git a/experimental/pom.xml b/experimental/pom.xml index 54dd0e206..1fda5a72a 100644 --- a/experimental/pom.xml +++ b/experimental/pom.xml @@ -41,6 +41,11 @@ serverlessworkflow-experimental-fluent-func ${project.version} + + io.serverlessworkflow + serverlessworkflow-experimental-fluent-serialization-jackson + ${project.version} + diff --git a/experimental/test/pom.xml b/experimental/test/pom.xml index 85f3d96c1..c730ab334 100644 --- a/experimental/test/pom.xml +++ b/experimental/test/pom.xml @@ -97,5 +97,25 @@ ${version.org.glassfish.jersey} test + + io.serverlessworkflow + serverlessworkflow-api + test + + + ch.qos.logback + logback-classic + test + + + org.junit.jupiter + junit-jupiter-params + test + + + io.serverlessworkflow + serverlessworkflow-experimental-fluent-serialization-jackson + test + \ No newline at end of file diff --git a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/ForEachFuncTest.java b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/ForEachFuncTest.java index 01737f035..cea746e57 100644 --- a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/ForEachFuncTest.java +++ b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/ForEachFuncTest.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.fluent.test; import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.*; +import static io.serverlessworkflow.fluent.test.TestSerializationUtils.writeAndReadInMemory; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -26,6 +27,7 @@ import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.lifecycle.TraceExecutionListener; +import java.io.IOException; import java.time.Duration; import java.util.Collection; import java.util.List; @@ -44,12 +46,13 @@ private record OrdersPayload(List orders) {} private record OrderName(String id, String name) {} @Test - void testForEachIteration() { + void testForEachIteration() throws IOException { Workflow workflow = - FuncWorkflowBuilder.workflow("foreach-workflow") - .tasks(forEachItem(OrdersPayload::orders, ForEachFuncTest::enhace)) - .build(); + writeAndReadInMemory( + FuncWorkflowBuilder.workflow("foreach-workflow") + .tasks(forEachItem(OrdersPayload::orders, ForEachFuncTest::enhace)) + .build()); try (WorkflowApplication app = WorkflowApplication.builder().build()) { OrdersPayload input = diff --git a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/ForkFuncTest.java b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/ForkFuncTest.java index a70ef4d5b..c63f8ed72 100644 --- a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/ForkFuncTest.java +++ b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/ForkFuncTest.java @@ -21,12 +21,19 @@ import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder; import io.serverlessworkflow.impl.WorkflowApplication; +import java.io.IOException; import org.junit.jupiter.api.Test; public class ForkFuncTest { + private int value = 2; + + public int getValue() { + return value; + } + @Test - void testForkVerbose() { + void testForkVerbose() throws IOException { testIt( FuncWorkflowBuilder.workflow("parallel-execution-workflow") .tasks( @@ -35,35 +42,36 @@ void testForkVerbose() { funcForkTaskBuilder -> funcForkTaskBuilder.branches( inner -> { - inner.function(f -> f.function(this::doubleIt, int.class)); - inner.function(f -> f.function(this::halfIt, int.class)); + inner.function(f -> f.function(this::doubleIt)); + inner.function(f -> f.function(this::halfIt)); }))) .build()); } @Test - void testForkSyntaxSugar() { + void testForkSyntaxSugar() throws IOException { testIt( FuncWorkflowBuilder.workflow("parallel-execution-workflow") .tasks(fork(function(this::doubleIt), function(this::halfIt))) .build()); } - private void testIt(Workflow workflow) { + private void testIt(Workflow workflow) throws IOException { + workflow = TestSerializationUtils.writeAndReadInMemory(workflow); try (WorkflowApplication app = WorkflowApplication.builder().build()) { assertThat( app.workflowDefinition(workflow).instance(8).start().join().asCollection().stream() .flatMap(m -> m.asMap().orElseThrow().values().stream()) .toList()) - .containsExactlyInAnyOrder(4, 16); + .containsExactlyInAnyOrder(2, 18); } } private int doubleIt(int number) { - return number << 1; + return (number << 1) + value; } private int halfIt(int number) { - return number >> 1; + return (number >> 1) - value; } } diff --git a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncCallAsyncTest.java b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncCallAsyncTest.java index 1aead510e..d4188a6c1 100644 --- a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncCallAsyncTest.java +++ b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncCallAsyncTest.java @@ -25,6 +25,7 @@ import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.lifecycle.WorkflowExecutionListener; import io.serverlessworkflow.impl.lifecycle.WorkflowStartedEvent; +import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicLong; import org.junit.jupiter.api.Test; @@ -53,17 +54,17 @@ private Integer waitSync(Integer waitTime) { } @Test - void testCompletableCall() { + void testCompletableCall() throws IOException { runIt(FuncWorkflowBuilder.workflow("waitCompletable").tasks(function(this::waitAsync)).build()); } @Test - void testReferencedFunctionCall() { + void testReferencedFunctionCall() throws IOException { runIt(FuncWorkflowBuilder.workflow("waitReference").tasks(function(this::waitSync)).build()); } @Test - void testLambdaCall() { + void testLambdaCall() throws IOException { runIt(FuncWorkflowBuilder.workflow("waitLambda").tasks(function(v -> 1)).build()); } @@ -77,11 +78,13 @@ public void onWorkflowStarted(WorkflowStartedEvent ev) { } } - private void runIt(Workflow workflow) { + private void runIt(Workflow workflow) throws IOException { TimeListener listener = new TimeListener(); try (WorkflowApplication app = WorkflowApplication.builder().withListener(listener).build()) { final long waitTime = 200; - WorkflowInstance instance = app.workflowDefinition(workflow).instance(waitTime); + WorkflowInstance instance = + app.workflowDefinition(TestSerializationUtils.writeAndReadInMemory(workflow)) + .instance(waitTime); CompletableFuture future = instance.start(); assertThat(System.currentTimeMillis() - listener.startTime.get()).isLessThan(waitTime); assertThat(future.join().asNumber().map(Number::intValue).orElseThrow()).isEqualTo(1); diff --git a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncDSLSerializationTest.java b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncDSLSerializationTest.java new file mode 100644 index 000000000..c8d753f49 --- /dev/null +++ b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncDSLSerializationTest.java @@ -0,0 +1,107 @@ +/* + * 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.test; + +import static io.serverlessworkflow.api.WorkflowWriter.workflowAsBytes; +import static io.serverlessworkflow.api.WorkflowWriter.workflowAsString; +import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.function; +import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.withContext; +import static io.serverlessworkflow.fluent.test.TestSerializationUtils.writeAndReadInMemory; +import static org.assertj.core.api.Assertions.assertThat; + +import io.serverlessworkflow.api.WorkflowFormat; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContextData; +import io.serverlessworkflow.impl.WorkflowDefinition; +import java.io.IOException; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class FuncDSLSerializationTest { + + @ParameterizedTest + @MethodSource("workflows") + public void testSpecFeaturesParsing(Workflow workflow, Consumer assertion) + throws IOException { + Workflow otherWorkflow = writeAndReadInMemory(workflow); + assertWorkflowEquals(workflow, otherWorkflow); + try (WorkflowApplication application = WorkflowApplication.builder().build()) { + assertion.accept(application.workflowDefinition(otherWorkflow)); + } + } + + static Stream workflows() { + final int QUANTITY = 3; + return Stream.of( + Arguments.of( + FuncWorkflowBuilder.workflow("hello") + .tasks(t -> t.set("sayHelloWorld", b -> b.expr(Map.of("result", "hello world!")))) + .build(), + new CheckResult(Map.of(), Map.of("result", "hello world!"))), + Arguments.of( + FuncWorkflowBuilder.workflow("inc") + .tasks(function(FuncDSLSerializationTest::inc)) + .build(), + new CheckResult(1, 2)), + Arguments.of( + FuncWorkflowBuilder.workflow("incContext") + .tasks(withContext(FuncDSLSerializationTest::incContext)) + .build(), + new CheckResult(1, 3)), + Arguments.of( + FuncWorkflowBuilder.workflow("incLambda") + .tasks(function((Integer number) -> number + QUANTITY)) + .build(), + new CheckResult(1, 4))); + } + + private static class CheckResult implements Consumer { + + private final Object input; + private final Object output; + + public CheckResult(Object input, Object output) { + this.input = input; + this.output = output; + } + + @Override + public void accept(WorkflowDefinition t) { + assertThat(t.instance(this.input).start().join().asJavaObject()).isEqualTo(this.output); + } + } + + private static void assertWorkflowEquals(Workflow workflow, Workflow other) throws IOException { + assertThat(workflowAsString(workflow, WorkflowFormat.YAML)) + .isEqualTo(workflowAsString(other, WorkflowFormat.YAML)); + assertThat(workflowAsBytes(workflow, WorkflowFormat.JSON)) + .isEqualTo(workflowAsBytes(other, WorkflowFormat.JSON)); + } + + private static Integer inc(Integer quantity) { + return quantity + 1; + } + + private static Integer incContext(Integer quantity, WorkflowContextData workflowContext) { + return quantity + 2; + } +} diff --git a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncDoTaskTest.java b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncDoTaskTest.java index d73142ab5..28a2dc22f 100644 --- a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncDoTaskTest.java +++ b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncDoTaskTest.java @@ -22,6 +22,7 @@ import io.serverlessworkflow.impl.WorkflowInstance; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowStatus; +import java.io.IOException; import java.net.URI; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -30,7 +31,7 @@ public class FuncDoTaskTest { @Test - void testDoTaskRaiseAndTryCatch() throws Exception { + void testDoTaskRaiseAndTryCatch() throws IOException { try (WorkflowApplication app = WorkflowApplication.builder().build()) { var workflow = @@ -67,7 +68,9 @@ void testDoTaskRaiseAndTryCatch() throws Exception { Map.of("handled", true))))))) .build(); - WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of()); + WorkflowInstance instance = + app.workflowDefinition(TestSerializationUtils.writeAndReadInMemory(workflow)) + .instance(Map.of()); CompletableFuture future = instance.start(); WorkflowModel result = future.join(); diff --git a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncHttpTest.java b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncHttpTest.java index 724b4fa8e..d6ff8766a 100644 --- a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncHttpTest.java +++ b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncHttpTest.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.fluent.test; import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.http; +import static io.serverlessworkflow.fluent.test.TestSerializationUtils.writeAndReadInMemory; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -65,13 +66,14 @@ private RecordedRequest takeRequestOrFail() throws Exception { @DisplayName("Query method with single key-value pair") void test_query_with_single_key_value() throws Exception { var workflow = - FuncWorkflowBuilder.workflow("test-query-single") - .tasks( - http("callHttp") - .GET() - .uri(mockServer.url("/api/endpoint").toString()) - .query("param1", "value1")) - .build(); + writeAndReadInMemory( + FuncWorkflowBuilder.workflow("test-query-single") + .tasks( + http("callHttp") + .GET() + .uri(mockServer.url("/api/endpoint").toString()) + .query("param1", "value1")) + .build()); WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of()); @@ -90,15 +92,16 @@ void test_query_with_single_key_value() throws Exception { @DisplayName("Query method with multiple single key-value pairs (individually tested)") void test_query_with_multiple_single_values() throws Exception { var workflow = - FuncWorkflowBuilder.workflow("test-query-single-multi") - .tasks( - http("callHttp") - .GET() - .uri(mockServer.url("/api/endpoint").toString()) - .query("param1", "value1") - .query("param2", "value2") - .query("param3", "value3")) - .build(); + writeAndReadInMemory( + FuncWorkflowBuilder.workflow("test-query-single-multi") + .tasks( + http("callHttp") + .GET() + .uri(mockServer.url("/api/endpoint").toString()) + .query("param1", "value1") + .query("param2", "value2") + .query("param3", "value3")) + .build()); WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of()); instance.start().join(); @@ -119,13 +122,14 @@ void test_query_with_multiple_single_values() throws Exception { void test_query_with_map() throws Exception { var workflow = - FuncWorkflowBuilder.workflow("test-query-map") - .tasks( - http("callHttp") - .GET() - .uri(mockServer.url("/api/endpoint").toString()) - .query(Map.of("userId", "123", "userName", "john", "status", "active"))) - .build(); + writeAndReadInMemory( + FuncWorkflowBuilder.workflow("test-query-map") + .tasks( + http("callHttp") + .GET() + .uri(mockServer.url("/api/endpoint").toString()) + .query(Map.of("userId", "123", "userName", "john", "status", "active"))) + .build()); WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of()); instance.start().join(); @@ -145,13 +149,14 @@ void test_query_with_map() throws Exception { @DisplayName("Query method with expression string") void test_query_with_expression() throws Exception { var workflow = - FuncWorkflowBuilder.workflow("test-query-expression") - .tasks( - http("callHttp") - .GET() - .uri(mockServer.url("/api/endpoint").toString()) - .query("enabled", "${ .enabled }")) - .build(); + writeAndReadInMemory( + FuncWorkflowBuilder.workflow("test-query-expression") + .tasks( + http("callHttp") + .GET() + .uri(mockServer.url("/api/endpoint").toString()) + .query("enabled", "${ .enabled }")) + .build()); WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of("enabled", true)); instance.start().join(); @@ -165,13 +170,14 @@ void test_query_with_expression() throws Exception { @DisplayName("Query method with empty Map") void test_query_with_empty_map() throws Exception { var workflow = - FuncWorkflowBuilder.workflow("test-query-empty-map") - .tasks( - http("callHttp") - .GET() - .uri(mockServer.url("/api/endpoint").toString()) - .query(Map.of())) - .build(); + writeAndReadInMemory( + FuncWorkflowBuilder.workflow("test-query-empty-map") + .tasks( + http("callHttp") + .GET() + .uri(mockServer.url("/api/endpoint").toString()) + .query(Map.of())) + .build()); WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of()); instance.start().join(); @@ -189,13 +195,14 @@ void test_query_with_empty_map() throws Exception { @DisplayName("Query method with special characters in values") void test_query_with_special_characters() throws Exception { var workflow = - FuncWorkflowBuilder.workflow("test-query-special-chars") - .tasks( - http("callHttp") - .GET() - .uri(mockServer.url("/api/endpoint").toString()) - .query("email", "user@example.com")) - .build(); + writeAndReadInMemory( + FuncWorkflowBuilder.workflow("test-query-special-chars") + .tasks( + http("callHttp") + .GET() + .uri(mockServer.url("/api/endpoint").toString()) + .query("email", "user@example.com")) + .build()); WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of()); instance.start().join(); @@ -213,13 +220,14 @@ void test_query_with_special_characters() throws Exception { @DisplayName("Query method overload - Map with multiple values") void test_query_map_multiple_values() throws Exception { var workflow = - FuncWorkflowBuilder.workflow("test-query-map-multi") - .tasks( - http("callHttp") - .GET() - .uri(mockServer.url("/api/endpoint").toString()) - .query(Map.of("limit", "50", "offset", "0", "sort", "name"))) - .build(); + writeAndReadInMemory( + FuncWorkflowBuilder.workflow("test-query-map-multi") + .tasks( + http("callHttp") + .GET() + .uri(mockServer.url("/api/endpoint").toString()) + .query(Map.of("limit", "50", "offset", "0", "sort", "name"))) + .build()); WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of()); instance.start().join(); @@ -239,15 +247,16 @@ void test_query_map_multiple_values() throws Exception { @DisplayName("Query method with headers and query parameters") void test_query_with_headers_and_query() throws Exception { var workflow = - FuncWorkflowBuilder.workflow("test-query-with-headers") - .tasks( - http("callHttp") - .GET() - .uri(mockServer.url("/api/endpoint").toString()) - .header("Authorization", "Bearer token123") - .header("Accept", "application/json") - .query("userId", "123")) - .build(); + writeAndReadInMemory( + FuncWorkflowBuilder.workflow("test-query-with-headers") + .tasks( + http("callHttp") + .GET() + .uri(mockServer.url("/api/endpoint").toString()) + .header("Authorization", "Bearer token123") + .header("Accept", "application/json") + .query("userId", "123")) + .build()); WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of()); instance.start().join(); diff --git a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncTryCatchTest.java b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncTryCatchTest.java index db33b5a53..90ca95db5 100644 --- a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncTryCatchTest.java +++ b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncTryCatchTest.java @@ -42,6 +42,7 @@ import io.serverlessworkflow.impl.WorkflowError; import io.serverlessworkflow.impl.WorkflowException; import io.serverlessworkflow.impl.WorkflowModel; +import java.io.IOException; import java.util.List; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -60,34 +61,37 @@ public class FuncTryCatchTest { private static final String ORDER_003 = "ORDER#003"; @Test - void booking_compensation_dsl() { + void booking_compensation_dsl() throws IOException { Workflow workflow = - FuncWorkflowBuilder.workflow() - .tasks( - tryCatch( - "tryStockReservation", - t -> - t.tryCatch(function("stockReservation", this::reserveStock)) - .catchError( - err -> err.type(STOCK_ORDER_ERROR), - function("cancelStockReservation", this::cancelReservation) - .then("endFlow"))), - tryCatch( - "tryPaymentProcessing", - t -> - t.tryCatch(function("paymentProcessing", this::processPayment)) - .catchWhen( - "${ .status == 503 }", - function("cancelPayment", this::cancelPayment).then("endFlow"))), - tryCatch( - "tryShipping", - t -> - t.tryCatch(function("scheduleShipping", this::scheduleShipping)) - .catchType( - SHIPPING_ERROR, function("cancelPayment", this::cancelShipping))), - function("endFlow", this::endFlow)) - .build(); + TestSerializationUtils.writeAndReadInMemory( + FuncWorkflowBuilder.workflow() + .tasks( + tryCatch( + "tryStockReservation", + t -> + t.tryCatch(function("stockReservation", this::reserveStock)) + .catchError( + err -> err.type(STOCK_ORDER_ERROR), + function("cancelStockReservation", this::cancelReservation) + .then("endFlow"))), + tryCatch( + "tryPaymentProcessing", + t -> + t.tryCatch(function("paymentProcessing", this::processPayment)) + .catchWhen( + "${ .status == 503 }", + function("cancelPayment", this::cancelPayment) + .then("endFlow"))), + tryCatch( + "tryShipping", + t -> + t.tryCatch(function("scheduleShipping", this::scheduleShipping)) + .catchType( + SHIPPING_ERROR, + function("cancelPayment", this::cancelShipping))), + function("endFlow", this::endFlow)) + .build()); try (WorkflowApplication application = WorkflowApplication.builder().build()) { WorkflowDefinition workflowDefinition = application.workflowDefinition(workflow); diff --git a/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/TestSerializationUtils.java b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/TestSerializationUtils.java new file mode 100644 index 000000000..b0dc16b0b --- /dev/null +++ b/experimental/test/src/test/java/io/serverlessworkflow/fluent/test/TestSerializationUtils.java @@ -0,0 +1,48 @@ +/* + * 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.test; + +import static io.serverlessworkflow.api.WorkflowReader.readWorkflow; +import static io.serverlessworkflow.api.WorkflowWriter.writeWorkflow; + +import io.serverlessworkflow.api.WorkflowFormat; +import io.serverlessworkflow.api.types.Workflow; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class TestSerializationUtils { + + private static final Logger logger = LoggerFactory.getLogger(TestSerializationUtils.class); + + private TestSerializationUtils() {} + + static Workflow writeAndReadInMemory(Workflow workflow) throws IOException { + byte[] bytes; + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + writeWorkflow(out, workflow, WorkflowFormat.YAML); + bytes = out.toByteArray(); + } + if (logger.isDebugEnabled()) { + logger.debug("Workflow string representation is {}", new String(bytes)); + } + try (ByteArrayInputStream in = new ByteArrayInputStream(bytes)) { + return readWorkflow(in, WorkflowFormat.YAML); + } + } +} diff --git a/experimental/test/src/test/resources/logback.xml b/experimental/test/src/test/resources/logback.xml new file mode 100644 index 000000000..cafd295ff --- /dev/null +++ b/experimental/test/src/test/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/reflection/func/ReflectionUtils.java b/experimental/types/src/main/java/io/serverlessworkflow/api/reflection/func/ReflectionUtils.java index 6b30fcdb2..5b1191086 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/reflection/func/ReflectionUtils.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/reflection/func/ReflectionUtils.java @@ -19,8 +19,12 @@ import io.serverlessworkflow.api.types.func.FilterFunction; import java.lang.invoke.MethodType; import java.lang.invoke.SerializedLambda; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Optional; import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Specially used by {@link Function} parameters in the Java Function. @@ -29,6 +33,8 @@ */ public final class ReflectionUtils { + private static final Logger logger = LoggerFactory.getLogger(ReflectionUtils.class); + private ReflectionUtils() {} @SuppressWarnings("unchecked") @@ -68,7 +74,7 @@ public static Class inferInputType(SerializableConsumer fn) { @SuppressWarnings("unchecked") public static Class inferResultType(Object fn) { - return (Class) inferMethodType(fn).returnType(); + return (Class) inferOutputType(inferMethodType(fn)); } /** @@ -78,21 +84,65 @@ public static Class inferResultType(Object fn) { * @param lambdaParamIndex The index of the payload parameter in the interface's apply method */ public static Class inferInputTypeFromAny(Object fn, int lambdaParamIndex) { - return inferMethodType(fn).parameterArray()[lambdaParamIndex]; + return inferInputType(inferMethodType(fn), lambdaParamIndex); } - private static MethodType inferMethodType(Object fn) { + public static Optional serializedFromFuntion(Object fn) { try { - Method m = fn.getClass().getDeclaredMethod("writeReplace"); - m.setAccessible(true); - SerializedLambda sl = (SerializedLambda) m.invoke(fn); + return Optional.of(serializedLambda(fn)); + } catch (ReflectiveOperationException ex) { + logger.debug("Error resolving serialized lambda for {}", fn, ex); + return Optional.empty(); + } + } - ClassLoader cl = fn.getClass().getClassLoader(); + public static Class loadCapturingClass(String capturingClass) throws ClassNotFoundException { + return loadClass(capturingClass.replace('/', '.')); + } - // getInstantiatedMethodType() provides the exact generic signature resolved - // by the compiler, completely bypassing captured variables and method kind switches! + public static Class loadClass(String className) throws ClassNotFoundException { + return Thread.currentThread().getContextClassLoader().loadClass(className); + } + + public static Object functionFromSerialized(SerializedLambda sl) + throws ReflectiveOperationException { + Method deserializeMethod = + loadCapturingClass(sl.getCapturingClass()) + .getDeclaredMethod("$deserializeLambda$", SerializedLambda.class); + deserializeMethod.setAccessible(true); + return deserializeMethod.invoke(null, sl); + } + + public static Optional methodType(Object fn) { + return serializedFromFuntion(fn).map(ReflectionUtils::inferMethodType); + } - return MethodType.fromMethodDescriptorString(sl.getInstantiatedMethodType(), cl); + public static MethodType inferMethodType(SerializedLambda sl) { + // getInstantiatedMethodType() provides the exact generic signature resolved + // by the compiler, completely bypassing captured variables and method kind switches! + return MethodType.fromMethodDescriptorString( + sl.getInstantiatedMethodType(), sl.getClass().getClassLoader()); + } + + private static SerializedLambda serializedLambda(Object fn) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Method m = fn.getClass().getDeclaredMethod("writeReplace"); + m.setAccessible(true); + return (SerializedLambda) m.invoke(fn); + } + + private static Class inferInputType(MethodType type, int index) { + return type.parameterType(index); + } + + private static Class inferOutputType(MethodType type) { + return type.returnType(); + } + + private static MethodType inferMethodType(Object fn) { + try { + SerializedLambda sl = serializedLambda(fn); + return inferMethodType(sl); } catch (ReflectiveOperationException ex) { throw new IllegalStateException( "Cannot infer type from lambda. Pass Class or use a method reference.", ex); diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallJava.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallJava.java index e441ecfb8..f0deed568 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallJava.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallJava.java @@ -15,192 +15,115 @@ */ package io.serverlessworkflow.api.types.func; -import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.reflection.func.ReflectionUtils; +import io.serverlessworkflow.api.types.CallFunction; +import io.serverlessworkflow.api.types.FunctionArguments; +import java.lang.invoke.MethodType; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; -public abstract class CallJava extends TaskBase { +public abstract class CallJava { - private static final long serialVersionUID = 1L; + private CallJava() {} - private final Optional> inputClass; + public static final String JAVA_CALL_KEY = "Java"; + public static final String FUNCTION_NAME_KEY = "function"; + public static final String INPUT_CLASS_KEY = "inputClass"; + public static final String OUTPUT_CLASS_KEY = "outputClass"; + public static final String VAR_NAME_KEY = "varName"; + public static final String INDEX_NAME_KEY = "index"; - protected CallJava() { - this(Optional.empty()); + private static CallFunction buildFunction( + Object function, Optional> inputClass, Optional> outputClass) { + CallFunction result = new CallFunction(); + result.setCall(JAVA_CALL_KEY); + result.withWith( + new FunctionArguments() + .withAdditionalProperty(FUNCTION_NAME_KEY, function) + .withAdditionalProperty(INPUT_CLASS_KEY, inputClass) + .withAdditionalProperty(OUTPUT_CLASS_KEY, outputClass)); + return result; } - protected CallJava(Optional> inputClass) { - this.inputClass = inputClass; + public static CallFunction consumer(Consumer consumer) { + return buildFunction( + consumer, + ReflectionUtils.methodType(consumer).map(m -> m.parameterType(0)), + Optional.empty()); } - public Optional> inputClass() { - return inputClass; + public static CallFunction consumer(Consumer consumer, Class inputClass) { + return buildFunction(consumer, Optional.ofNullable(inputClass), Optional.empty()); } - public static CallJava consumer(Consumer consumer) { - return new CallJavaConsumer<>(consumer, Optional.empty()); + public static CallFunction function(Function function) { + return buildFunction(function, Optional.empty(), Optional.empty()); } - public static CallJava consumer(Consumer consumer, Class inputClass) { - return new CallJavaConsumer<>(consumer, Optional.ofNullable(inputClass)); + public static CallFunction function(Function function, Class inputClass) { + return buildFunction( + function, + Optional.ofNullable(inputClass), + ReflectionUtils.methodType(function).map(MethodType::returnType)); } - public static CallJavaFunction function(Function function) { - return new CallJavaFunction<>(function, Optional.empty(), Optional.empty()); - } - - public static CallJavaFunction function( - Function function, Class inputClass) { - return new CallJavaFunction<>(function, Optional.ofNullable(inputClass), Optional.empty()); - } - - public static CallJavaFunction function( + public static CallFunction function( Function function, Class inputClass, Class outputClass) { - return new CallJavaFunction<>( + return buildFunction( function, Optional.ofNullable(inputClass), Optional.ofNullable(outputClass)); } - public static CallJava loopFunction( + public static CallFunction loopFunction( LoopFunctionIndex function, String varName, String indexName) { - return new CallJavaLoopFunctionIndex<>(function, varName, indexName); - } - - public static CallJava loopFunction(LoopFunction function, String varName) { - return new CallJavaLoopFunction<>(function, varName); - } - - public static CallJava function(ContextFunction function, Class inputClass) { - return new CallJavaContextFunction<>( - function, Optional.ofNullable(inputClass), Optional.empty()); - } - - public static CallJava function( + Optional methodType = ReflectionUtils.methodType(function); + CallFunction result = + buildFunction( + function, + methodType.map(m -> m.parameterType(0)), + methodType.map(MethodType::returnType)); + result + .getWith() + .withAdditionalProperty(VAR_NAME_KEY, varName) + .withAdditionalProperty(INDEX_NAME_KEY, indexName); + return result; + } + + public static CallFunction loopFunction( + LoopFunction function, String varName) { + Optional methodType = ReflectionUtils.methodType(function); + CallFunction result = + buildFunction( + function, + methodType.map(m -> m.parameterType(0)), + methodType.map(MethodType::returnType)); + result.getWith().withAdditionalProperty(VAR_NAME_KEY, varName); + return result; + } + + public static CallFunction function(ContextFunction function, Class inputClass) { + return buildFunction( + function, + Optional.ofNullable(inputClass), + ReflectionUtils.methodType(function).map(MethodType::returnType)); + } + + public static CallFunction function( ContextFunction function, Class inputClass, Class outputClass) { - return new CallJavaContextFunction<>( + return buildFunction( function, Optional.ofNullable(inputClass), Optional.ofNullable(outputClass)); } - public static CallJava function(FilterFunction function, Class inputClass) { - return new CallJavaFilterFunction<>( - function, Optional.ofNullable(inputClass), Optional.empty()); + public static CallFunction function(FilterFunction function, Class inputClass) { + return buildFunction( + function, + Optional.ofNullable(inputClass), + ReflectionUtils.methodType(function).map(MethodType::returnType)); } - public static CallJava function( + public static CallFunction function( FilterFunction function, Class inputClass, Class outputClass) { - return new CallJavaFilterFunction<>( + return buildFunction( function, Optional.ofNullable(inputClass), Optional.ofNullable(outputClass)); } - - public static class CallJavaConsumer extends CallJava { - private static final long serialVersionUID = 1L; - private final Consumer consumer; - - public CallJavaConsumer(Consumer consumer, Optional> inputClass) { - super(inputClass); - this.consumer = consumer; - } - - public Consumer consumer() { - return consumer; - } - } - - public static class CallJavaFunction extends CallAbstractJavaFunction { - - private static final long serialVersionUID = 1L; - private final Function function; - - public CallJavaFunction( - Function function, Optional> inputClass, Optional> outputClass) { - super(inputClass, outputClass); - this.function = function; - } - - public Function function() { - return function; - } - } - - public static class CallJavaContextFunction extends CallAbstractJavaFunction { - private static final long serialVersionUID = 1L; - private final ContextFunction function; - - public CallJavaContextFunction( - ContextFunction function, - Optional> inputClass, - Optional> outputClass) { - super(inputClass, outputClass); - this.function = function; - } - - public ContextFunction function() { - return function; - } - } - - public static class CallJavaFilterFunction extends CallAbstractJavaFunction { - private static final long serialVersionUID = 1L; - private final FilterFunction function; - - public CallJavaFilterFunction( - FilterFunction function, - Optional> inputClass, - Optional> outputClass) { - super(inputClass, outputClass); - this.function = function; - } - - public FilterFunction function() { - return function; - } - } - - public static class CallJavaLoopFunction extends CallAbstractJavaFunction { - - private static final long serialVersionUID = 1L; - private LoopFunction function; - private String varName; - - public CallJavaLoopFunction(LoopFunction function, String varName) { - - this.function = function; - this.varName = varName; - } - - public LoopFunction function() { - return function; - } - - public String varName() { - return varName; - } - } - - public static class CallJavaLoopFunctionIndex extends CallAbstractJavaFunction { - - private static final long serialVersionUID = 1L; - private final LoopFunctionIndex function; - private final String varName; - private final String indexName; - - public CallJavaLoopFunctionIndex( - LoopFunctionIndex function, String varName, String indexName) { - this.function = function; - this.varName = varName; - this.indexName = indexName; - } - - public LoopFunctionIndex function() { - return function; - } - - public String varName() { - return varName; - } - - public String indexName() { - return indexName; - } - } } 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 deleted file mode 100644 index b18f8d886..000000000 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ExportAsFunction.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.api.types.func; - -import io.serverlessworkflow.api.types.ExportAs; -import java.util.Objects; -import java.util.function.Function; - -public class ExportAsFunction extends ExportAs { - - 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; - } - - public ExportAs withFunction(FilterFunction value) { - setObject(value); - return this; - } - - public ExportAs withFunction(FilterFunction value, Class argClass) { - setObject(new TypedFilterFunction<>(value, argClass)); - return this; - } - - public ExportAs withFunction(ContextFunction value) { - setObject(value); - return this; - } - - public ExportAs withFunction(ContextFunction value, Class argClass) { - setObject(new TypedContextFunction<>(value, argClass)); - return this; - } -} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/FilterPredicate.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/FilterPredicate.java index d81b3ac48..cdbf6f57d 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/FilterPredicate.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/FilterPredicate.java @@ -17,8 +17,9 @@ import io.serverlessworkflow.impl.TaskContextData; import io.serverlessworkflow.impl.WorkflowContextData; +import java.io.Serializable; @FunctionalInterface -public interface FilterPredicate { +public interface FilterPredicate extends Serializable { boolean test(T value, WorkflowContextData workflow, TaskContextData task); } 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 d6932f2e3..d7c01ca94 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 @@ -15,21 +15,34 @@ */ package io.serverlessworkflow.api.types.func; +import io.serverlessworkflow.api.reflection.func.ReflectionUtils; import io.serverlessworkflow.api.types.ForTask; -import java.io.IOException; -import java.io.ObjectInputStream; +import io.serverlessworkflow.api.types.ForTaskConfiguration; +import io.serverlessworkflow.api.types.TaskMetadata; +import java.lang.invoke.MethodType; import java.util.Collection; import java.util.Optional; import java.util.function.Function; -public class ForTaskFunction extends ForTask { +public class ForTaskFunction { - private static final long serialVersionUID = 1L; - private LoopPredicateIndexFilter whilePredicate; - private Optional> whileClass = Optional.empty(); - private Optional> itemClass = Optional.empty(); - private Optional> forClass = Optional.empty(); - private Function collection; + private final ForTask forTask; + private TaskMetadata metadata; + + public static final String WHILE_PREDICATE = "whilePredicate"; + public static final String WHILE_CLASS = "whileClass"; + public static final String ITEM_CLASS = "itemClass"; + public static final String FOR_CLASS = "forClass"; + public static final String COLLECTION = "inCollection"; + + public ForTaskFunction(ForTask forTask) { + this.forTask = forTask; + this.metadata = forTask.getMetadata(); + if (metadata == null) { + metadata = new TaskMetadata(); + forTask.setMetadata(metadata); + } + } public ForTaskFunction withWhile(LoopPredicate whilePredicate) { return withWhile(toPredicate(whilePredicate)); @@ -63,13 +76,19 @@ public ForTaskFunction withWhile( } public ForTaskFunction withWhile(LoopPredicateIndexContext whilePredicate) { - return withWhile(toPredicate(whilePredicate), Optional.empty(), Optional.empty()); + Optional methodType = ReflectionUtils.methodType(whilePredicate); + return withWhile( + toPredicate(whilePredicate), + methodType.map(m -> m.parameterType(0)), + methodType.map(MethodType::returnType)); } public ForTaskFunction withWhile( LoopPredicateIndexContext whilePredicate, Class modelClass) { return withWhile( - toPredicate(whilePredicate), Optional.ofNullable(modelClass), Optional.empty()); + toPredicate(whilePredicate), + Optional.ofNullable(modelClass), + ReflectionUtils.methodType(whilePredicate).map(MethodType::returnType)); } public ForTaskFunction withWhile( @@ -81,12 +100,19 @@ public ForTaskFunction withWhile( } public ForTaskFunction withWhile(LoopPredicateIndexFilter whilePredicate) { - return withWhile(whilePredicate, Optional.empty(), Optional.empty()); + Optional methodType = ReflectionUtils.methodType(whilePredicate); + return withWhile( + whilePredicate, + methodType.map(m -> m.parameterType(0)), + methodType.map(MethodType::returnType)); } public ForTaskFunction withWhile( LoopPredicateIndexFilter whilePredicate, Class modelClass) { - return withWhile(whilePredicate, Optional.ofNullable(modelClass), Optional.empty()); + return withWhile( + whilePredicate, + Optional.ofNullable(modelClass), + ReflectionUtils.methodType(whilePredicate).map(MethodType::returnType)); } public ForTaskFunction withWhile( @@ -113,9 +139,9 @@ private ForTaskFunction withWhile( LoopPredicateIndexFilter whilePredicate, Optional> modelClass, Optional> itemClass) { - this.whilePredicate = whilePredicate; - this.whileClass = modelClass; - this.itemClass = itemClass; + metadata.withAdditionalProperty(WHILE_PREDICATE, whilePredicate); + metadata.withAdditionalProperty(WHILE_CLASS, modelClass); + metadata.withAdditionalProperty(ITEM_CLASS, itemClass); return this; } @@ -125,46 +151,43 @@ public ForTaskFunction withCollection(Function> collecti public ForTaskFunction withCollection( Function> collection, Class colArgClass) { - this.collection = collection; - this.forClass = Optional.ofNullable(colArgClass); + metadata.withAdditionalProperty(COLLECTION, collection); + metadata.withAdditionalProperty(FOR_CLASS, Optional.ofNullable(colArgClass)); + ForTaskConfiguration forConfig = forTask.getFor(); + if (forConfig == null) { + forConfig = new ForTaskConfiguration(); + forTask.setFor(forConfig); + } + if (forConfig.getIn() == null) { + forConfig.setIn("Handling item collection with metadata key " + ForTaskFunction.COLLECTION); + } return this; } public LoopPredicateIndexFilter getWhilePredicate() { - return whilePredicate; + return (LoopPredicateIndexFilter) metadata.getAdditionalProperties().get(WHILE_PREDICATE); } public Optional> getWhileClass() { - return whileClass; + return (Optional>) + metadata.getAdditionalProperties().getOrDefault(WHILE_CLASS, Optional.empty()); } public Optional> getForClass() { - return forClass; + return (Optional>) + metadata.getAdditionalProperties().getOrDefault(FOR_CLASS, Optional.empty()); } public Optional> getItemClass() { - return itemClass; + return (Optional>) + metadata.getAdditionalProperties().getOrDefault(ITEM_CLASS, Optional.empty()); } - public Function> getCollection() { - return collection; - } - - private void normalizeOptionalFields() { - if (whileClass == null) { - whileClass = Optional.empty(); - } - if (itemClass == null) { - itemClass = Optional.empty(); - } - if (forClass == null) { - forClass = Optional.empty(); - } + public Function> getInCollection() { + return (Function>) metadata.getAdditionalProperties().get(COLLECTION); } - private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { - input.defaultReadObject(); - // Preserve compatibility with older serialized instances that may have null optionals. - normalizeOptionalFields(); + public ForTask task() { + return forTask; } } 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 deleted file mode 100644 index bfa5bf12f..000000000 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/InputFromFunction.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.api.types.func; - -import io.serverlessworkflow.api.types.InputFrom; -import java.util.Objects; -import java.util.function.Function; - -public class InputFromFunction extends InputFrom { - - 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; - } - - public InputFrom withFunction(FilterFunction value) { - setObject(value); - return this; - } - - public InputFrom withFunction(FilterFunction value, Class argClass) { - setObject(new TypedFilterFunction<>(value, argClass)); - return this; - } - - public InputFrom withFunction(ContextFunction value) { - setObject(value); - return this; - } - - public InputFrom withFunction(ContextFunction value, Class argClass) { - setObject(new TypedContextFunction<>(value, argClass)); - return this; - } -} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopFunction.java index 094cff1c1..93950614e 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopFunction.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopFunction.java @@ -15,7 +15,8 @@ */ package io.serverlessworkflow.api.types.func; +import java.io.Serializable; import java.util.function.BiFunction; @FunctionalInterface -public interface LoopFunction extends BiFunction {} +public interface LoopFunction extends Serializable, BiFunction {} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopFunctionIndex.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopFunctionIndex.java index b5831d098..2405e36be 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopFunctionIndex.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopFunctionIndex.java @@ -15,7 +15,9 @@ */ package io.serverlessworkflow.api.types.func; +import java.io.Serializable; + @FunctionalInterface -public interface LoopFunctionIndex { +public interface LoopFunctionIndex extends Serializable { R apply(T model, V item, Integer index); } diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicate.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicate.java index 38b6b3041..8695740eb 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicate.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicate.java @@ -15,7 +15,8 @@ */ package io.serverlessworkflow.api.types.func; +import java.io.Serializable; import java.util.function.BiPredicate; @FunctionalInterface -public interface LoopPredicate extends BiPredicate {} +public interface LoopPredicate extends Serializable, BiPredicate {} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndex.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndex.java index 0e0a39a0d..09d724f50 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndex.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndex.java @@ -15,7 +15,9 @@ */ package io.serverlessworkflow.api.types.func; +import java.io.Serializable; + @FunctionalInterface -public interface LoopPredicateIndex { +public interface LoopPredicateIndex extends Serializable { boolean test(T model, V item, Integer index); } diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndexContext.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndexContext.java index 470bfbca5..26ddabb6a 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndexContext.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndexContext.java @@ -16,8 +16,9 @@ package io.serverlessworkflow.api.types.func; import io.serverlessworkflow.impl.WorkflowContextData; +import java.io.Serializable; @FunctionalInterface -public interface LoopPredicateIndexContext { +public interface LoopPredicateIndexContext extends Serializable { boolean test(T model, V item, Integer index, WorkflowContextData context); } diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndexFilter.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndexFilter.java index 808719c50..8cd22d673 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndexFilter.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/LoopPredicateIndexFilter.java @@ -17,8 +17,9 @@ import io.serverlessworkflow.impl.TaskContextData; import io.serverlessworkflow.impl.WorkflowContextData; +import java.io.Serializable; @FunctionalInterface -public interface LoopPredicateIndexFilter { +public interface LoopPredicateIndexFilter extends Serializable { boolean test(T model, V item, Integer index, WorkflowContextData workflow, TaskContextData task); } 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 deleted file mode 100644 index 19d61335b..000000000 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/OutputAsFunction.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.api.types.func; - -import io.serverlessworkflow.api.types.OutputAs; -import java.util.Objects; -import java.util.function.Function; - -public class OutputAsFunction extends OutputAs { - - 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; - } - - public OutputAs withFunction(FilterFunction value) { - setObject(value); - return this; - } - - public OutputAs withFunction(FilterFunction value, Class argClass) { - setObject(new TypedFilterFunction<>(value, argClass)); - return this; - } - - public OutputAs withFunction(ContextFunction value) { - setObject(value); - return this; - } - - public OutputAs withFunction(ContextFunction value, Class argClass) { - setObject(new TypedContextFunction<>(value, argClass)); - return this; - } -} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java index 0d305d8a8..8d1f16be9 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java @@ -37,7 +37,7 @@ public abstract class BaseWorkflowBuilder< public static final String DSL = "1.0.0"; public static final String DEFAULT_VERSION = "0.0.1"; - public static final String DEFAULT_NAMESPACE = "org.acme"; + public static final String DEFAULT_NAMESPACE = "org-acme"; protected final Workflow workflow; private final Document document; diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java index b316de7fe..c275762b8 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java @@ -76,7 +76,7 @@ void testWorkflowDocumentDefaults() { assertNotNull(wf, "Workflow should not be null"); Document doc = wf.getDocument(); assertNotNull(doc, "Document should not be null"); - assertEquals("org.acme", doc.getNamespace(), "Default namespace should be org.acme"); + assertEquals("org-acme", doc.getNamespace(), "Default namespace should be org.acme"); assertEquals("0.0.1", doc.getVersion(), "Default version should be 0.0.1"); assertEquals("1.0.0", doc.getDsl(), "DSL version should be set to 1.0.0"); assertNotNull(doc.getName(), "Name should be auto-generated"); 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 b4c024bee..249ee0397 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 @@ -37,15 +37,11 @@ public class ForExecutor extends RegularTaskExecutor { private final TaskExecutor taskExecutor; public static class ForExecutorBuilder extends RegularTaskExecutorBuilder { - private WorkflowValueResolver> collectionExpr; - private Optional whileExpr; private TaskExecutor taskExecutor; protected ForExecutorBuilder( WorkflowMutablePosition position, ForTask task, WorkflowDefinition definition) { super(position, task, definition); - this.collectionExpr = buildCollectionFilter(); - this.whileExpr = buildWhileFilter(); this.taskExecutor = TaskExecutorHelper.createExecutorList(position, task.getDo(), definition); } @@ -67,8 +63,8 @@ public ForExecutor buildInstance() { protected ForExecutor(ForExecutorBuilder builder) { super(builder); - this.collectionExpr = builder.collectionExpr; - this.whileExpr = builder.whileExpr; + this.collectionExpr = builder.buildCollectionFilter(); + this.whileExpr = builder.buildWhileFilter(); this.taskExecutor = builder.taskExecutor; }