From 1a91185d2260e5e51c5fbb662cd7eca55f1900d1 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Tue, 30 Jun 2026 17:25:57 +0200 Subject: [PATCH 1/3] [Fix #1489] Serializing Java defined workflows Signed-off-by: fjtirado --- experimental/fluent/func/pom.xml | 31 ++++- .../fluent/func/FuncTaskItemListBuilder.java | 5 + .../fluent/func/FuncDSLSerializationTest.java | 115 ++++++++++++++++++ .../func/src/test/resources/logback_test.xml | 19 +++ experimental/fluent/jackson/pom.xml | 32 +++++ .../jackson/CallJavaFunctionDeserializer.java | 78 ++++++++++++ .../jackson/CallJavaFunctionSerializer.java | 46 +++++++ .../jackson/CallTaskFunctionSerializer.java | 28 +++++ .../jackson/FuncJacksonModule.java | 33 +++++ .../jackson/FuncUnionCustomizer.java | 31 +++++ .../com.fasterxml.jackson.databind.Module | 1 + ...lessworkflow.serialization.UnionCustomizer | 1 + experimental/fluent/pom.xml | 1 + experimental/pom.xml | 5 + .../api/reflection/func/ReflectionUtils.java | 57 +++++++-- .../api/types/func/CallTaskJava.java | 8 ++ .../fluent/spec/BaseWorkflowBuilder.java | 2 +- .../fluent/spec/WorkflowBuilderTest.java | 2 +- .../serialization/DeserializeHelper.java | 51 ++++++-- .../serialization/UnionCustomizer.java | 23 ++++ 20 files changed, 544 insertions(+), 25 deletions(-) create mode 100644 experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLSerializationTest.java create mode 100644 experimental/fluent/func/src/test/resources/logback_test.xml create mode 100644 experimental/fluent/jackson/pom.xml create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionDeserializer.java create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionSerializer.java create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallTaskFunctionSerializer.java create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncJacksonModule.java create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncUnionCustomizer.java create mode 100644 experimental/fluent/jackson/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module create mode 100644 experimental/fluent/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.serialization.UnionCustomizer create mode 100644 serialization/src/main/java/io/serverlessworkflow/serialization/UnionCustomizer.java diff --git a/experimental/fluent/func/pom.xml b/experimental/fluent/func/pom.xml index dcf2aa96f..f405a8ab4 100644 --- a/experimental/fluent/func/pom.xml +++ b/experimental/fluent/func/pom.xml @@ -33,17 +33,42 @@ io.cloudevents cloudevents-core - org.junit.jupiter - junit-jupiter-api + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.assertj + assertj-core test org.mockito mockito-core - ${version.org.mockito} test + + io.serverlessworkflow + serverlessworkflow-api + test + + + ch.qos.logback + logback-classic + test + + + + io.serverlessworkflow + serverlessworkflow-experimental-fluent-serialization-jackson + test + + \ No newline at end of file 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/test/java/io/serverlessworkflow/fluent/func/FuncDSLSerializationTest.java b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLSerializationTest.java new file mode 100644 index 000000000..e414eeaaa --- /dev/null +++ b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLSerializationTest.java @@ -0,0 +1,115 @@ +/* + * 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; + +import static io.serverlessworkflow.api.WorkflowReader.readWorkflow; +import static io.serverlessworkflow.api.WorkflowWriter.workflowAsBytes; +import static io.serverlessworkflow.api.WorkflowWriter.workflowAsString; +import static io.serverlessworkflow.api.WorkflowWriter.writeWorkflow; +import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.function; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.serverlessworkflow.api.WorkflowFormat; +import io.serverlessworkflow.api.types.Workflow; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FuncDSLSerializationTest { + + private static final Logger logger = LoggerFactory.getLogger(FuncDSLSerializationTest.class); + + @ParameterizedTest + @MethodSource("workflows") + public void testSpecFeaturesParsing(Workflow workflow) throws IOException { + assertWorkflow(workflow); + assertWorkflowEquals(workflow, writeAndReadInMemory(workflow)); + } + + static Stream workflows() { + return Stream.of( + FuncWorkflowBuilder.workflow("waitCompletable") + .tasks(function(FuncDSLSerializationTest::inc)) + .build(), + FuncWorkflowBuilder.workflow("hello") + .tasks(t -> t.set("sayHelloWorld", b -> b.expr(Map.of("result", "hello world!")))) + .build()); + } + + private static Workflow writeAndReadInMemory(Workflow workflow) throws IOException { + byte[] bytes; + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + writeWorkflow(out, workflow, WorkflowFormat.JSON); + bytes = out.toByteArray(); + } + logger.debug("Serialized json is " + new String(bytes)); + try (ByteArrayInputStream in = new ByteArrayInputStream(bytes)) { + return readWorkflow(in, WorkflowFormat.JSON); + } + } + + private static void assertWorkflow(Workflow workflow) { + assertNotNull(workflow); + assertNotNull(workflow.getDocument()); + assertNotNull(workflow.getDo()); + } + + 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++; + } + + // private static class MyFunction implements SerializableFunction { + // + // private static final long serialVersionUID = 1L; + // + // @Override + // public Integer apply(Integer arg0) { + // return arg0++; + // } + // } + // + // public static void main(String[] args) throws JsonProcessingException { + // System.out.println( + // WorkflowFormat.JSON + // .mapper() + // .writeValueAsString( + // new CallJava.CallJavaFunction( + // FuncDSLSerializationTest::inc, + // Optional.of(Integer.class), + // Optional.of(Integer.class)))); + // + // System.out.println( + // WorkflowFormat.JSON + // .mapper() + // .writeValueAsString( + // new CallJava.CallJavaFunction( + // new MyFunction(), Optional.of(Integer.class), Optional.of(Integer.class)))); + // } +} diff --git a/experimental/fluent/func/src/test/resources/logback_test.xml b/experimental/fluent/func/src/test/resources/logback_test.xml new file mode 100644 index 000000000..c3b6bcb5b --- /dev/null +++ b/experimental/fluent/func/src/test/resources/logback_test.xml @@ -0,0 +1,19 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/experimental/fluent/jackson/pom.xml b/experimental/fluent/jackson/pom.xml new file mode 100644 index 000000000..363d1d99b --- /dev/null +++ b/experimental/fluent/jackson/pom.xml @@ -0,0 +1,32 @@ + + 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 + + + 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/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionDeserializer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionDeserializer.java new file mode 100644 index 000000000..a9d96bc86 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionDeserializer.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.fluent.func.serialization.jackson; + +import com.fasterxml.jackson.core.JsonParseException; +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.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import io.serverlessworkflow.api.reflection.func.ReflectionUtils; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.api.types.func.CallJava.CallJavaFunction; +import java.io.IOException; +import java.lang.invoke.SerializedLambda; +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CallJavaFunctionDeserializer extends JsonDeserializer { + + private static Logger logger = LoggerFactory.getLogger(CallJavaFunctionDeserializer.class); + + @Override + public CallJavaFunction deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException { + + TreeNode content = p.readValueAsTree(); + TreeNode callNode = content.get("call"); + if (!(callNode instanceof TextNode textNode) || !textNode.asText().equals("Java")) { + throw new JsonParseException( + "Expecting a call property which value shoud be Java but instead it was " + callNode); + } + TreeNode functionNode = content.get("function"); + if (functionNode == null) { + throw new JsonParseException("A CallJava function should have a function property"); + } + if (functionNode instanceof ObjectNode objectNode) { + SerializedLambda serializedLambda = ctxt.readTreeAsValue(objectNode, SerializedLambda.class); + try { + Method readResolve = SerializedLambda.class.getDeclaredMethod("readResolve"); + readResolve.setAccessible(true); + + return new CallJavaFunction( + (Function) readResolve.invoke(serializedLambda), + Optional.of(ReflectionUtils.inputType(serializedLambda)), + Optional.of(ReflectionUtils.outputType(serializedLambda))); + } catch (ReflectiveOperationException e) { + throw new IOException( + "Reflection exception while converting SerializedLamda " + + serializedLambda + + "into a funcion"); + } + + } else { + logger.error( + "function property: {} , does not contain enough information to be properly unmarshall, using an function that does nothing", + functionNode); + return new CallJavaFunction<>(v -> v, Optional.empty(), Optional.empty()); + } + } +} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionSerializer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionSerializer.java new file mode 100644 index 000000000..f6deb0f93 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionSerializer.java @@ -0,0 +1,46 @@ +/* + * 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 io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.api.types.func.CallJava.CallJavaFunction; +import java.io.IOException; +import java.lang.invoke.SerializedLambda; +import java.util.Optional; + +public class CallJavaFunctionSerializer extends JsonSerializer { + + @Override + public void serialize(CallJavaFunction value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeStartObject(); + gen.writeStringField("call", "Java"); + gen.writeObjectFieldStart("with"); + Optional serializedLambda = + ReflectionUtils.getSerializedLambda(value.function()); + if (serializedLambda.isPresent()) { + gen.writeObjectField("function", serializedLambda.orElse(null)); + } else { + gen.writeStringField("function", value.function().toString()); + } + gen.writeEndObject(); + gen.writeEndObject(); + } +} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallTaskFunctionSerializer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallTaskFunctionSerializer.java new file mode 100644 index 000000000..88904b46d --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallTaskFunctionSerializer.java @@ -0,0 +1,28 @@ +package io.serverlessworkflow.fluent.func.serialization.jackson; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +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.jackson.CallTaskSerializer; + +public class CallTaskFunctionSerializer extends CallTaskSerializer { + + @Override + public void serialize(CallTask value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + + if (value instanceof CallTaskJava javaCall) { + + javaCall.getCallJava(); + + } else { + super.serialize(value, gen, serializers); + } + } + +} 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..63ea3ea9a --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncJacksonModule.java @@ -0,0 +1,33 @@ +/* + * 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.module.SimpleModule; + +import io.serverlessworkflow.api.types.CallTask; +import io.serverlessworkflow.api.types.func.CallJava; + +public class FuncJacksonModule extends SimpleModule { + + private static final long serialVersionUID = 1L; + + public void setupModule(com.fasterxml.jackson.databind.Module.SetupContext context) { + super.addSerializer(CallTask.class, new CallTaskFunctionSerializer()); +// super.addDeserializer(CallJava.CallJavaFunction.class, new CallJavaFunctionDeserializer()); + // super.addSerializer(CallJava.CallJavaFunction.class, new CallJavaFunctionSerializer()); + super.setupModule(context); + } +} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncUnionCustomizer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncUnionCustomizer.java new file mode 100644 index 000000000..2aacf5c18 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncUnionCustomizer.java @@ -0,0 +1,31 @@ +/* + * 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.CallTask; +import io.serverlessworkflow.api.types.func.CallJava.CallJavaFunction; +import io.serverlessworkflow.serialization.UnionCustomizer; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class FuncUnionCustomizer implements UnionCustomizer { + + @Override + public Map, Collection>> additionalClasses() { + return Map.of(CallTask.class, List.of(CallJavaFunction.class)); + } +} 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/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.serialization.UnionCustomizer b/experimental/fluent/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.serialization.UnionCustomizer new file mode 100644 index 000000000..1f872175c --- /dev/null +++ b/experimental/fluent/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.serialization.UnionCustomizer @@ -0,0 +1 @@ +io.serverlessworkflow.fluent.func.serialization.jackson.FuncUnionCustomizer \ 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/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/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..2366b3716 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,52 @@ 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 getSerializedLambda(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(); + private static SerializedLambda serializedLambda(Object fn) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Method m = fn.getClass().getDeclaredMethod("writeReplace"); + m.setAccessible(true); + return (SerializedLambda) m.invoke(fn); + } + + public static Class outputType(SerializedLambda lambda) { + return inferOutputType(inferMethodType(lambda)); + } - // getInstantiatedMethodType() provides the exact generic signature resolved - // by the compiler, completely bypassing captured variables and method kind switches! + public static Class inputType(SerializedLambda lambda) { + return inferInputType(inferMethodType(lambda), 0); + } - return MethodType.fromMethodDescriptorString(sl.getInstantiatedMethodType(), cl); + 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(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 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/CallTaskJava.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java index cde6281f2..3f53f90f1 100644 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java @@ -15,12 +15,15 @@ */ package io.serverlessworkflow.api.types.func; +import io.serverlessworkflow.annotations.OneOfSetter; import io.serverlessworkflow.api.types.CallTask; public class CallTaskJava extends CallTask { private CallJava callJava; + public CallTaskJava() {} + public CallTaskJava(CallJava callJava) { this.callJava = callJava; } @@ -29,6 +32,11 @@ public CallJava getCallJava() { return callJava; } + @OneOfSetter(CallJava.class) + public void setCallJava(CallJava callJava) { + this.callJava = callJava; + } + @Override public Object get() { return callJava != null ? callJava : super.get(); 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/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java b/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java index 041474d91..aa826f5f6 100644 --- a/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java +++ b/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java @@ -26,30 +26,44 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.ServiceLoader; +import java.util.ServiceLoader.Provider; +import java.util.stream.Collectors; public class DeserializeHelper { + private static Map, Collection>> additionalClasses = + ServiceLoader.load(UnionCustomizer.class).stream() + .map(Provider::get) + .map(UnionCustomizer::additionalClasses) + .flatMap(map -> map.entrySet().stream()) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + public static T deserializeOneOf( JsonParser p, Class targetClass, Collection> oneOfTypes) throws IOException { TreeNode node = p.readValueAsTree(); try { T result = targetClass.getDeclaredConstructor().newInstance(); - Collection exceptions = new ArrayList<>(); - for (Class oneOfType : oneOfTypes) { - try { - assingIt(p, result, node, targetClass, oneOfType); - break; - } catch (IOException | ConstraintViolationException | InvocationTargetException ex) { - exceptions.add(ex); - } + Collection> possibleTypes; + Collection> additional = additionalClasses.get(targetClass); + if (additional != null && !additional.isEmpty()) { + possibleTypes = new HashSet>(oneOfTypes); + possibleTypes.addAll(additional); + } else { + possibleTypes = oneOfTypes; } - if (exceptions.size() == oneOfTypes.size()) { + Collection exceptions = + deserializeOneOf(result, node, p, targetClass, possibleTypes); + if (exceptions.size() == possibleTypes.size()) { JsonMappingException ex = new JsonMappingException( p, String.format( "Error deserializing class %s, all oneOf alternatives %s has failed ", - targetClass, oneOfTypes)); + targetClass, possibleTypes)); exceptions.forEach(ex::addSuppressed); throw ex; } @@ -59,6 +73,23 @@ public static T deserializeOneOf( } } + private static Collection deserializeOneOf( + T result, TreeNode node, JsonParser p, Class targetClass, Collection> oneOfTypes) + throws ReflectiveOperationException { + Collection exceptions = new ArrayList<>(); + for (Class oneOfType : oneOfTypes) { + try { + assingIt(p, result, node, targetClass, oneOfType); + break; + } catch (IOException | ConstraintViolationException ex) { + exceptions.add(ex); + } catch (InvocationTargetException ex) { + exceptions.add(ex.getCause()); + } + } + return exceptions; + } + private static void assingIt( JsonParser p, T result, TreeNode node, Class targetClass, Class type) throws JsonProcessingException, ReflectiveOperationException { diff --git a/serialization/src/main/java/io/serverlessworkflow/serialization/UnionCustomizer.java b/serialization/src/main/java/io/serverlessworkflow/serialization/UnionCustomizer.java new file mode 100644 index 000000000..b9f9155fe --- /dev/null +++ b/serialization/src/main/java/io/serverlessworkflow/serialization/UnionCustomizer.java @@ -0,0 +1,23 @@ +/* + * 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.serialization; + +import java.util.Collection; +import java.util.Map; + +public interface UnionCustomizer { + Map, Collection>> additionalClasses(); +} From 0c4c559ae6c6633938c308ed8f055b8e1066d166 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Wed, 1 Jul 2026 21:52:22 +0200 Subject: [PATCH 2/3] [Fix #1489] Alternative approach Signed-off-by: fjtirado --- .../api/WorkflowWriter.java | 8 +- experimental/fluent/func/pom.xml | 31 +---- .../fluent/func/FuncCallTaskBuilder.java | 34 +++--- .../fluent/func/FuncForTaskBuilder.java | 31 ++--- .../fluent/func/FuncForkTaskBuilder.java | 5 +- .../fluent/func/dsl/FuncDSL.java | 2 +- .../fluent/func/FuncDSLSerializationTest.java | 115 ------------------ .../func/src/test/resources/logback_test.xml | 19 --- experimental/fluent/jackson/pom.xml | 6 +- ....java => AbstractMapValueTransformer.java} | 19 +-- .../jackson/CallJavaDeserializer.java | 66 ++++++++++ .../jackson/CallJavaFunctionDeserializer.java | 78 ------------ .../jackson/CallTaskFunctionSerializer.java | 28 ----- .../jackson/FuncJacksonModule.java | 69 ++++++++++- .../jackson/GlobalConverterRegistry.java | 44 +++++++ ...va => SerializableFunctionSerializer.java} | 18 +-- .../jackson/SerializationUtils.java | 66 ++++++++++ .../jackson/SerializedLambdaDeserializer.java | 95 +++++++++++++++ .../jackson/SerializedLambdaWriter.java | 56 +++++++++ .../jackson/TaskMetadataMixIn.java | 10 +- .../jackson/TaskMetadataTransformer.java | 38 ++++++ ...lessworkflow.serialization.UnionCustomizer | 1 - .../func/JavaForExecutorBuilder.java | 46 +++---- .../func/CallJavaContextFunctionTest.java | 5 +- .../impl/executors/func/CallTest.java | 57 +++++---- .../func/ForTaskFunctionRegressionTest.java | 79 +++--------- experimental/test/pom.xml | 20 +++ .../fluent/test/ForEachFuncTest.java | 11 +- .../fluent/test/FuncDSLSerializationTest.java | 107 ++++++++++++++++ .../fluent/test/TestSerializationUtils.java | 48 ++++++++ .../test/src/test/resources/logback.xml | 15 +++ .../api/reflection/func/ReflectionUtils.java | 45 ++++--- .../types/func/CallAbstractJavaFunction.java | 39 ------ .../api/types/func/CallJava.java | 98 ++++++++++++++- .../api/types/func/CallTaskJava.java | 44 ------- .../api/types/func/FilterPredicate.java | 3 +- .../api/types/func/ForTaskFunction.java | 103 ++++++++++------ .../api/types/func/LoopFunction.java | 3 +- .../api/types/func/LoopFunctionIndex.java | 4 +- .../api/types/func/LoopPredicate.java | 3 +- .../api/types/func/LoopPredicateIndex.java | 4 +- .../types/func/LoopPredicateIndexContext.java | 3 +- .../types/func/LoopPredicateIndexFilter.java | 3 +- .../impl/executors/ForExecutor.java | 8 +- .../serialization/DeserializeHelper.java | 51 ++------ 45 files changed, 989 insertions(+), 649 deletions(-) delete mode 100644 experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLSerializationTest.java delete mode 100644 experimental/fluent/func/src/test/resources/logback_test.xml rename experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/{FuncUnionCustomizer.java => AbstractMapValueTransformer.java} (61%) create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaDeserializer.java delete mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionDeserializer.java delete mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallTaskFunctionSerializer.java create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/GlobalConverterRegistry.java rename experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/{CallJavaFunctionSerializer.java => SerializableFunctionSerializer.java} (60%) create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializationUtils.java create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializedLambdaDeserializer.java create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializedLambdaWriter.java rename serialization/src/main/java/io/serverlessworkflow/serialization/UnionCustomizer.java => experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataMixIn.java (73%) create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataTransformer.java delete mode 100644 experimental/fluent/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.serialization.UnionCustomizer create mode 100644 experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncDSLSerializationTest.java create mode 100644 experimental/test/src/test/java/io/serverlessworkflow/fluent/test/TestSerializationUtils.java create mode 100644 experimental/test/src/test/resources/logback.xml delete mode 100644 experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallAbstractJavaFunction.java delete mode 100644 experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java 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/pom.xml b/experimental/fluent/func/pom.xml index f405a8ab4..dcf2aa96f 100644 --- a/experimental/fluent/func/pom.xml +++ b/experimental/fluent/func/pom.xml @@ -33,42 +33,17 @@ io.cloudevents cloudevents-core + org.junit.jupiter - junit-jupiter-engine - test - - - org.junit.jupiter - junit-jupiter-params - test - - - org.assertj - assertj-core + junit-jupiter-api test org.mockito mockito-core + ${version.org.mockito} test - - io.serverlessworkflow - serverlessworkflow-api - test - - - ch.qos.logback - logback-classic - test - - - - io.serverlessworkflow - serverlessworkflow-experimental-fluent-serialization-jackson - test - - \ No newline at end of file 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..3e92be04a 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,8 @@ */ package io.serverlessworkflow.fluent.func; +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,12 +29,9 @@ 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() { @@ -51,8 +48,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 +64,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 +80,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/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/test/java/io/serverlessworkflow/fluent/func/FuncDSLSerializationTest.java b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLSerializationTest.java deleted file mode 100644 index e414eeaaa..000000000 --- a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLSerializationTest.java +++ /dev/null @@ -1,115 +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.fluent.func; - -import static io.serverlessworkflow.api.WorkflowReader.readWorkflow; -import static io.serverlessworkflow.api.WorkflowWriter.workflowAsBytes; -import static io.serverlessworkflow.api.WorkflowWriter.workflowAsString; -import static io.serverlessworkflow.api.WorkflowWriter.writeWorkflow; -import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.function; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import io.serverlessworkflow.api.WorkflowFormat; -import io.serverlessworkflow.api.types.Workflow; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Map; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class FuncDSLSerializationTest { - - private static final Logger logger = LoggerFactory.getLogger(FuncDSLSerializationTest.class); - - @ParameterizedTest - @MethodSource("workflows") - public void testSpecFeaturesParsing(Workflow workflow) throws IOException { - assertWorkflow(workflow); - assertWorkflowEquals(workflow, writeAndReadInMemory(workflow)); - } - - static Stream workflows() { - return Stream.of( - FuncWorkflowBuilder.workflow("waitCompletable") - .tasks(function(FuncDSLSerializationTest::inc)) - .build(), - FuncWorkflowBuilder.workflow("hello") - .tasks(t -> t.set("sayHelloWorld", b -> b.expr(Map.of("result", "hello world!")))) - .build()); - } - - private static Workflow writeAndReadInMemory(Workflow workflow) throws IOException { - byte[] bytes; - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - writeWorkflow(out, workflow, WorkflowFormat.JSON); - bytes = out.toByteArray(); - } - logger.debug("Serialized json is " + new String(bytes)); - try (ByteArrayInputStream in = new ByteArrayInputStream(bytes)) { - return readWorkflow(in, WorkflowFormat.JSON); - } - } - - private static void assertWorkflow(Workflow workflow) { - assertNotNull(workflow); - assertNotNull(workflow.getDocument()); - assertNotNull(workflow.getDo()); - } - - 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++; - } - - // private static class MyFunction implements SerializableFunction { - // - // private static final long serialVersionUID = 1L; - // - // @Override - // public Integer apply(Integer arg0) { - // return arg0++; - // } - // } - // - // public static void main(String[] args) throws JsonProcessingException { - // System.out.println( - // WorkflowFormat.JSON - // .mapper() - // .writeValueAsString( - // new CallJava.CallJavaFunction( - // FuncDSLSerializationTest::inc, - // Optional.of(Integer.class), - // Optional.of(Integer.class)))); - // - // System.out.println( - // WorkflowFormat.JSON - // .mapper() - // .writeValueAsString( - // new CallJava.CallJavaFunction( - // new MyFunction(), Optional.of(Integer.class), Optional.of(Integer.class)))); - // } -} diff --git a/experimental/fluent/func/src/test/resources/logback_test.xml b/experimental/fluent/func/src/test/resources/logback_test.xml deleted file mode 100644 index c3b6bcb5b..000000000 --- a/experimental/fluent/func/src/test/resources/logback_test.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - - \ No newline at end of file diff --git a/experimental/fluent/jackson/pom.xml b/experimental/fluent/jackson/pom.xml index 363d1d99b..10b764f89 100644 --- a/experimental/fluent/jackson/pom.xml +++ b/experimental/fluent/jackson/pom.xml @@ -8,10 +8,14 @@ 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 diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncUnionCustomizer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/AbstractMapValueTransformer.java similarity index 61% rename from experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncUnionCustomizer.java rename to experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/AbstractMapValueTransformer.java index 2aacf5c18..42a022a37 100644 --- a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FuncUnionCustomizer.java +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/AbstractMapValueTransformer.java @@ -15,17 +15,20 @@ */ package io.serverlessworkflow.fluent.func.serialization.jackson; -import io.serverlessworkflow.api.types.CallTask; -import io.serverlessworkflow.api.types.func.CallJava.CallJavaFunction; -import io.serverlessworkflow.serialization.UnionCustomizer; -import java.util.Collection; -import java.util.List; +import com.fasterxml.jackson.databind.util.StdConverter; import java.util.Map; -public class FuncUnionCustomizer implements UnionCustomizer { +public abstract class AbstractMapValueTransformer extends StdConverter { @Override - public Map, Collection>> additionalClasses() { - return Map.of(CallTask.class, List.of(CallJavaFunction.class)); + 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 abstract Map map(T value); } diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaDeserializer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaDeserializer.java new file mode 100644 index 000000000..63988b53d --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaDeserializer.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 com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import io.serverlessworkflow.api.types.CallFunction; +import io.serverlessworkflow.api.types.FunctionArguments; +import io.serverlessworkflow.api.types.func.CallJava; +import java.io.IOException; +import java.util.Map; + +public class CallJavaDeserializer extends JsonDeserializer + implements ResolvableDeserializer { + + private JsonDeserializer defaultDeserializer; + + public CallJavaDeserializer(JsonDeserializer defaultDeserializer) { + this.defaultDeserializer = defaultDeserializer; + } + + @Override + public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + CallFunction original = (CallFunction) defaultDeserializer.deserialize(p, ctxt); + if (original.getCall().equals(CallJava.JAVA_CALL_KEY)) { + FunctionArguments args = original.getWith(); + if (args != null) { + Map props = args.getAdditionalProperties(); + try { + return (T) + CallJava.fromFunctionProperties( + props, + SerializationUtils.getSerializedLambda(props.get(CallJava.FUNCTION_NAME_KEY)) + .orElseThrow( + () -> + new IOException( + "Expecting function object to be a Map. Please check if you are using serializable lambdas in your workflow definition"))); + } catch (ReflectiveOperationException e) { + throw new IOException("Error unmarshalling java call with args " + args, e); + } + } + } + return (T) original; + } + + @Override + public void resolve(DeserializationContext ctxt) throws JsonMappingException { + ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt); + } +} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionDeserializer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionDeserializer.java deleted file mode 100644 index a9d96bc86..000000000 --- a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionDeserializer.java +++ /dev/null @@ -1,78 +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.fluent.func.serialization.jackson; - -import com.fasterxml.jackson.core.JsonParseException; -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.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; -import io.serverlessworkflow.api.reflection.func.ReflectionUtils; -import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallJava.CallJavaFunction; -import java.io.IOException; -import java.lang.invoke.SerializedLambda; -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.function.Function; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CallJavaFunctionDeserializer extends JsonDeserializer { - - private static Logger logger = LoggerFactory.getLogger(CallJavaFunctionDeserializer.class); - - @Override - public CallJavaFunction deserialize(JsonParser p, DeserializationContext ctxt) - throws IOException { - - TreeNode content = p.readValueAsTree(); - TreeNode callNode = content.get("call"); - if (!(callNode instanceof TextNode textNode) || !textNode.asText().equals("Java")) { - throw new JsonParseException( - "Expecting a call property which value shoud be Java but instead it was " + callNode); - } - TreeNode functionNode = content.get("function"); - if (functionNode == null) { - throw new JsonParseException("A CallJava function should have a function property"); - } - if (functionNode instanceof ObjectNode objectNode) { - SerializedLambda serializedLambda = ctxt.readTreeAsValue(objectNode, SerializedLambda.class); - try { - Method readResolve = SerializedLambda.class.getDeclaredMethod("readResolve"); - readResolve.setAccessible(true); - - return new CallJavaFunction( - (Function) readResolve.invoke(serializedLambda), - Optional.of(ReflectionUtils.inputType(serializedLambda)), - Optional.of(ReflectionUtils.outputType(serializedLambda))); - } catch (ReflectiveOperationException e) { - throw new IOException( - "Reflection exception while converting SerializedLamda " - + serializedLambda - + "into a funcion"); - } - - } else { - logger.error( - "function property: {} , does not contain enough information to be properly unmarshall, using an function that does nothing", - functionNode); - return new CallJavaFunction<>(v -> v, Optional.empty(), Optional.empty()); - } - } -} diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallTaskFunctionSerializer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallTaskFunctionSerializer.java deleted file mode 100644 index 88904b46d..000000000 --- a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallTaskFunctionSerializer.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.serverlessworkflow.fluent.func.serialization.jackson; - -import java.io.IOException; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -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.jackson.CallTaskSerializer; - -public class CallTaskFunctionSerializer extends CallTaskSerializer { - - @Override - public void serialize(CallTask value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - - if (value instanceof CallTaskJava javaCall) { - - javaCall.getCallJava(); - - } else { - super.serialize(value, gen, serializers); - } - } - -} 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 index 63ea3ea9a..cdcef5ea9 100644 --- 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 @@ -15,19 +15,76 @@ */ package io.serverlessworkflow.fluent.func.serialization.jackson; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; import com.fasterxml.jackson.databind.module.SimpleModule; - -import io.serverlessworkflow.api.types.CallTask; -import io.serverlessworkflow.api.types.func.CallJava; +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.CallFunction; +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) { - super.addSerializer(CallTask.class, new CallTaskFunctionSerializer()); -// super.addDeserializer(CallJava.CallJavaFunction.class, new CallJavaFunctionDeserializer()); - // super.addSerializer(CallJava.CallJavaFunction.class, new CallJavaFunctionSerializer()); + 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.setDeserializerModifier( + new BeanDeserializerModifier() { + @Override + public JsonDeserializer modifyDeserializer( + DeserializationConfig config, + BeanDescription beanDesc, + JsonDeserializer deserializer) { + if (beanDesc.getBeanClass().equals(CallFunction.class)) { + return new CallJavaDeserializer<>(deserializer); + } + return deserializer; + } + }); + 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/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/CallJavaFunctionSerializer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializableFunctionSerializer.java similarity index 60% rename from experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionSerializer.java rename to experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializableFunctionSerializer.java index f6deb0f93..8e90f5427 100644 --- a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaFunctionSerializer.java +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializableFunctionSerializer.java @@ -19,28 +19,20 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import io.serverlessworkflow.api.reflection.func.ReflectionUtils; -import io.serverlessworkflow.api.types.func.CallJava; -import io.serverlessworkflow.api.types.func.CallJava.CallJavaFunction; import java.io.IOException; import java.lang.invoke.SerializedLambda; import java.util.Optional; -public class CallJavaFunctionSerializer extends JsonSerializer { +public class SerializableFunctionSerializer extends JsonSerializer { @Override - public void serialize(CallJavaFunction value, JsonGenerator gen, SerializerProvider serializers) + public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - gen.writeStartObject(); - gen.writeStringField("call", "Java"); - gen.writeObjectFieldStart("with"); - Optional serializedLambda = - ReflectionUtils.getSerializedLambda(value.function()); + Optional serializedLambda = ReflectionUtils.serializedFromFuntion(value); if (serializedLambda.isPresent()) { - gen.writeObjectField("function", serializedLambda.orElse(null)); + gen.writeObject(serializedLambda.orElseThrow()); } else { - gen.writeStringField("function", value.function().toString()); + gen.writeString(value.toString()); } - gen.writeEndObject(); - gen.writeEndObject(); } } 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..2a07fbd81 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializedLambdaWriter.java @@ -0,0 +1,56 @@ +/* + * 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.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 { + 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(); + } + } +} diff --git a/serialization/src/main/java/io/serverlessworkflow/serialization/UnionCustomizer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataMixIn.java similarity index 73% rename from serialization/src/main/java/io/serverlessworkflow/serialization/UnionCustomizer.java rename to experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataMixIn.java index b9f9155fe..555253ab7 100644 --- a/serialization/src/main/java/io/serverlessworkflow/serialization/UnionCustomizer.java +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/TaskMetadataMixIn.java @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.serialization; +package io.serverlessworkflow.fluent.func.serialization.jackson; -import java.util.Collection; -import java.util.Map; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -public interface UnionCustomizer { - Map, Collection>> additionalClasses(); -} +@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/io.serverlessworkflow.serialization.UnionCustomizer b/experimental/fluent/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.serialization.UnionCustomizer deleted file mode 100644 index 1f872175c..000000000 --- a/experimental/fluent/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.serialization.UnionCustomizer +++ /dev/null @@ -1 +0,0 @@ -io.serverlessworkflow.fluent.func.serialization.jackson.FuncUnionCustomizer \ No newline at end of file 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/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..bbd7c74f6 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,11 @@ 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.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 +30,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 +73,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 +101,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 +158,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 +181,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,11 +205,12 @@ 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); 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/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/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/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 2366b3716..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 @@ -87,7 +87,7 @@ public static Class inferInputTypeFromAny(Object fn, int lambdaParamIndex) { return inferInputType(inferMethodType(fn), lambdaParamIndex); } - public static Optional getSerializedLambda(Object fn) { + public static Optional serializedFromFuntion(Object fn) { try { return Optional.of(serializedLambda(fn)); } catch (ReflectiveOperationException ex) { @@ -96,6 +96,34 @@ public static Optional getSerializedLambda(Object fn) { } } + public static Class loadCapturingClass(String capturingClass) throws ClassNotFoundException { + return loadClass(capturingClass.replace('/', '.')); + } + + 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); + } + + 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"); @@ -103,14 +131,6 @@ private static SerializedLambda serializedLambda(Object fn) return (SerializedLambda) m.invoke(fn); } - public static Class outputType(SerializedLambda lambda) { - return inferOutputType(inferMethodType(lambda)); - } - - public static Class inputType(SerializedLambda lambda) { - return inferInputType(inferMethodType(lambda), 0); - } - private static Class inferInputType(MethodType type, int index) { return type.parameterType(index); } @@ -119,13 +139,6 @@ private static Class inferOutputType(MethodType type) { return type.returnType(); } - private 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 MethodType inferMethodType(Object fn) { try { SerializedLambda sl = serializedLambda(fn); diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallAbstractJavaFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallAbstractJavaFunction.java deleted file mode 100644 index 0a8af1201..000000000 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallAbstractJavaFunction.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.api.types.func; - -import java.util.Optional; - -public abstract class CallAbstractJavaFunction extends CallJava { - - private static final long serialVersionUID = 1L; - - private final Optional> outputClass; - - protected CallAbstractJavaFunction() { - this(Optional.empty(), Optional.empty()); - } - - protected CallAbstractJavaFunction( - Optional> inputClass, Optional> outputClass) { - super(inputClass); - this.outputClass = outputClass; - } - - public Optional> outputClass() { - return outputClass; - } -} 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..8f4a500a5 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,22 +15,28 @@ */ 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.lang.invoke.SerializedLambda; +import java.util.Map; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; -public abstract class CallJava extends TaskBase { +public abstract class CallJava extends CallFunction { private static final long serialVersionUID = 1L; + public static final String JAVA_CALL_KEY = "Java"; + public static final String FUNCTION_NAME_KEY = "function"; + private static final String VAR_NAME_KEY = "varName"; + private static final String INDEX_NAME_KEY = "index"; private final Optional> inputClass; - protected CallJava() { - this(Optional.empty()); - } - protected CallJava(Optional> inputClass) { + super.setCall(JAVA_CALL_KEY); this.inputClass = inputClass; } @@ -92,6 +98,39 @@ public static CallJava function( function, Optional.ofNullable(inputClass), Optional.ofNullable(outputClass)); } + @FunctionalInterface + public interface SerializedLambdaUnmarshaller { + SerializedLambda apply(Object serializedFunction) throws ReflectiveOperationException; + } + + public static CallJava fromFunctionProperties( + Map props, SerializedLambda sl) throws ReflectiveOperationException { + Object obj = ReflectionUtils.functionFromSerialized(sl); + MethodType methodType = ReflectionUtils.inferMethodType(sl); + Optional> input = Optional.of(methodType.parameterType(0)); + Optional> output = Optional.of(methodType.returnType()); + if (obj instanceof ContextFunction fn) { + return new CallJavaContextFunction(fn, input, output); + } else if (obj instanceof FilterFunction fn) { + return new CallJavaFilterFunction(fn, input, output); + } else if (obj instanceof LoopFunction loop) { + return new CallJavaLoopFunction(loop, (String) props.get(VAR_NAME_KEY), input, output); + } else if (obj instanceof LoopFunctionIndex loop) { + return new CallJavaLoopFunctionIndex( + loop, + (String) props.get(VAR_NAME_KEY), + (String) props.get(INDEX_NAME_KEY), + input, + output); + } else if (obj instanceof Function fn) { + return new CallJavaFunction(fn, input, output); + } else if (obj instanceof Consumer consumer) { + return new CallJavaConsumer(consumer, input); + } else { + throw new UnsupportedOperationException("Unrecognized function " + obj); + } + } + public static class CallJavaConsumer extends CallJava { private static final long serialVersionUID = 1L; private final Consumer consumer; @@ -106,6 +145,23 @@ public Consumer consumer() { } } + public abstract static class CallAbstractJavaFunction extends CallJava { + + private static final long serialVersionUID = 1L; + + private final Optional> outputClass; + + protected CallAbstractJavaFunction( + Optional> inputClass, Optional> outputClass) { + super(inputClass); + this.outputClass = outputClass; + } + + public Optional> outputClass() { + return outputClass; + } + } + public static class CallJavaFunction extends CallAbstractJavaFunction { private static final long serialVersionUID = 1L; @@ -115,6 +171,7 @@ public CallJavaFunction( Function function, Optional> inputClass, Optional> outputClass) { super(inputClass, outputClass); this.function = function; + this.withWith(new FunctionArguments().withAdditionalProperty(FUNCTION_NAME_KEY, function)); } public Function function() { @@ -132,6 +189,7 @@ public CallJavaContextFunction( Optional> outputClass) { super(inputClass, outputClass); this.function = function; + this.withWith(new FunctionArguments().withAdditionalProperty(FUNCTION_NAME_KEY, function)); } public ContextFunction function() { @@ -149,6 +207,7 @@ public CallJavaFilterFunction( Optional> outputClass) { super(inputClass, outputClass); this.function = function; + this.withWith(new FunctionArguments().withAdditionalProperty(FUNCTION_NAME_KEY, function)); } public FilterFunction function() { @@ -163,9 +222,21 @@ public static class CallJavaLoopFunction extends CallAbstractJavaFuncti private String varName; public CallJavaLoopFunction(LoopFunction function, String varName) { + this(function, varName, Optional.empty(), Optional.empty()); + } + public CallJavaLoopFunction( + LoopFunction function, + String varName, + Optional> inputClass, + Optional> outputClass) { + super(inputClass, outputClass); this.function = function; this.varName = varName; + this.withWith( + new FunctionArguments() + .withAdditionalProperty(FUNCTION_NAME_KEY, function) + .withAdditionalProperty(VAR_NAME_KEY, varName)); } public LoopFunction function() { @@ -186,9 +257,24 @@ public static class CallJavaLoopFunctionIndex extends CallAbstractJavaF public CallJavaLoopFunctionIndex( LoopFunctionIndex function, String varName, String indexName) { + this(function, varName, indexName, Optional.empty(), Optional.empty()); + } + + public CallJavaLoopFunctionIndex( + LoopFunctionIndex function, + String varName, + String indexName, + Optional> inputClass, + Optional> outputClass) { + super(inputClass, outputClass); this.function = function; this.varName = varName; this.indexName = indexName; + this.withWith( + new FunctionArguments() + .withAdditionalProperty(FUNCTION_NAME_KEY, function) + .withAdditionalProperty(VAR_NAME_KEY, varName) + .withAdditionalProperty(INDEX_NAME_KEY, indexName)); } public LoopFunctionIndex function() { diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java deleted file mode 100644 index 3f53f90f1..000000000 --- a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java +++ /dev/null @@ -1,44 +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.annotations.OneOfSetter; -import io.serverlessworkflow.api.types.CallTask; - -public class CallTaskJava extends CallTask { - - private CallJava callJava; - - public CallTaskJava() {} - - public CallTaskJava(CallJava callJava) { - this.callJava = callJava; - } - - public CallJava getCallJava() { - return callJava; - } - - @OneOfSetter(CallJava.class) - public void setCallJava(CallJava callJava) { - this.callJava = callJava; - } - - @Override - public Object get() { - return callJava != null ? callJava : super.get(); - } -} 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/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/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; } diff --git a/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java b/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java index aa826f5f6..041474d91 100644 --- a/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java +++ b/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java @@ -26,44 +26,30 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.ServiceLoader; -import java.util.ServiceLoader.Provider; -import java.util.stream.Collectors; public class DeserializeHelper { - private static Map, Collection>> additionalClasses = - ServiceLoader.load(UnionCustomizer.class).stream() - .map(Provider::get) - .map(UnionCustomizer::additionalClasses) - .flatMap(map -> map.entrySet().stream()) - .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); - public static T deserializeOneOf( JsonParser p, Class targetClass, Collection> oneOfTypes) throws IOException { TreeNode node = p.readValueAsTree(); try { T result = targetClass.getDeclaredConstructor().newInstance(); - Collection> possibleTypes; - Collection> additional = additionalClasses.get(targetClass); - if (additional != null && !additional.isEmpty()) { - possibleTypes = new HashSet>(oneOfTypes); - possibleTypes.addAll(additional); - } else { - possibleTypes = oneOfTypes; + Collection exceptions = new ArrayList<>(); + for (Class oneOfType : oneOfTypes) { + try { + assingIt(p, result, node, targetClass, oneOfType); + break; + } catch (IOException | ConstraintViolationException | InvocationTargetException ex) { + exceptions.add(ex); + } } - Collection exceptions = - deserializeOneOf(result, node, p, targetClass, possibleTypes); - if (exceptions.size() == possibleTypes.size()) { + if (exceptions.size() == oneOfTypes.size()) { JsonMappingException ex = new JsonMappingException( p, String.format( "Error deserializing class %s, all oneOf alternatives %s has failed ", - targetClass, possibleTypes)); + targetClass, oneOfTypes)); exceptions.forEach(ex::addSuppressed); throw ex; } @@ -73,23 +59,6 @@ public static T deserializeOneOf( } } - private static Collection deserializeOneOf( - T result, TreeNode node, JsonParser p, Class targetClass, Collection> oneOfTypes) - throws ReflectiveOperationException { - Collection exceptions = new ArrayList<>(); - for (Class oneOfType : oneOfTypes) { - try { - assingIt(p, result, node, targetClass, oneOfType); - break; - } catch (IOException | ConstraintViolationException ex) { - exceptions.add(ex); - } catch (InvocationTargetException ex) { - exceptions.add(ex.getCause()); - } - } - return exceptions; - } - private static void assingIt( JsonParser p, T result, TreeNode node, Class targetClass, Class type) throws JsonProcessingException, ReflectiveOperationException { From 0a2cf2b0d081a8659f86b52538d4b8faff414bb3 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Mon, 6 Jul 2026 17:29:24 +0200 Subject: [PATCH 3/3] [Fix #1489] Same approach for all classes Signed-off-by: fjtirado --- .../fluent/func/FuncCallTaskBuilder.java | 3 +- .../func/spi/FuncTaskTransformations.java | 23 +- .../fluent/func/spi/FuncTransformations.java | 39 ++- .../fluent/func/FuncDSLTest.java | 14 +- .../fluent/func/FuncDSLUniqueIdTest.java | 12 +- .../jackson/CallJavaDeserializer.java | 66 ---- .../jackson/FuncJacksonModule.java | 20 +- .../jackson/FunctionArgumentsMixIn.java | 21 ++ .../jackson/FunctionArgumentsTransformer.java | 35 ++ .../jackson/SerializedLambdaWriter.java | 46 ++- .../func/AbstractJavaCallExecutor.java | 4 - .../func/JavaCallFunctionBuilder.java | 78 +++++ .../func/JavaConsumerCallExecutorBuilder.java | 39 --- ...avaContextFunctionCallExecutorBuilder.java | 43 --- ...JavaFilterFunctionCallExecutorBuilder.java | 43 --- .../func/JavaFunctionCallExecutorBuilder.java | 42 --- .../func/JavaLoopFunctionCallExecutor.java | 8 +- .../JavaLoopFunctionCallExecutorBuilder.java | 39 --- .../JavaLoopFunctionIndexCallExecutor.java | 8 +- ...aLoopFunctionIndexCallExecutorBuilder.java | 42 --- ...orkflow.impl.executors.CallableTaskBuilder | 8 +- .../impl/executors/func/CallTest.java | 8 +- .../impl/executors/func/ModelTest.java | 37 ++- .../fluent/test/ForkFuncTest.java | 24 +- .../fluent/test/FuncCallAsyncTest.java | 13 +- .../fluent/test/FuncDoTaskTest.java | 7 +- .../fluent/test/FuncHttpTest.java | 129 ++++---- .../fluent/test/FuncTryCatchTest.java | 56 ++-- .../api/types/func/CallJava.java | 313 +++++------------- .../api/types/func/ExportAsFunction.java | 54 --- .../api/types/func/InputFromFunction.java | 54 --- .../api/types/func/OutputAsFunction.java | 54 --- 32 files changed, 473 insertions(+), 909 deletions(-) delete mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaDeserializer.java create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsMixIn.java create mode 100644 experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsTransformer.java create mode 100644 experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaCallFunctionBuilder.java delete mode 100644 experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaConsumerCallExecutorBuilder.java delete mode 100644 experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaContextFunctionCallExecutorBuilder.java delete mode 100644 experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFilterFunctionCallExecutorBuilder.java delete mode 100644 experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFunctionCallExecutorBuilder.java delete mode 100644 experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutorBuilder.java delete mode 100644 experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutorBuilder.java delete mode 100644 experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ExportAsFunction.java delete mode 100644 experimental/types/src/main/java/io/serverlessworkflow/api/types/func/InputFromFunction.java delete mode 100644 experimental/types/src/main/java/io/serverlessworkflow/api/types/func/OutputAsFunction.java 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 3e92be04a..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,6 +15,7 @@ */ 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.ContextFunction; @@ -38,7 +39,7 @@ protected FuncCallTaskBuilder self() { return this; } - public FuncCallTaskBuilder function(Function function) { + public FuncCallTaskBuilder function(SerializableFunction function) { return function(function, null); } 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/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaDeserializer.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaDeserializer.java deleted file mode 100644 index 63988b53d..000000000 --- a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/CallJavaDeserializer.java +++ /dev/null @@ -1,66 +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.fluent.func.serialization.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; -import io.serverlessworkflow.api.types.CallFunction; -import io.serverlessworkflow.api.types.FunctionArguments; -import io.serverlessworkflow.api.types.func.CallJava; -import java.io.IOException; -import java.util.Map; - -public class CallJavaDeserializer extends JsonDeserializer - implements ResolvableDeserializer { - - private JsonDeserializer defaultDeserializer; - - public CallJavaDeserializer(JsonDeserializer defaultDeserializer) { - this.defaultDeserializer = defaultDeserializer; - } - - @Override - public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - CallFunction original = (CallFunction) defaultDeserializer.deserialize(p, ctxt); - if (original.getCall().equals(CallJava.JAVA_CALL_KEY)) { - FunctionArguments args = original.getWith(); - if (args != null) { - Map props = args.getAdditionalProperties(); - try { - return (T) - CallJava.fromFunctionProperties( - props, - SerializationUtils.getSerializedLambda(props.get(CallJava.FUNCTION_NAME_KEY)) - .orElseThrow( - () -> - new IOException( - "Expecting function object to be a Map. Please check if you are using serializable lambdas in your workflow definition"))); - } catch (ReflectiveOperationException e) { - throw new IOException("Error unmarshalling java call with args " + args, e); - } - } - } - return (T) original; - } - - @Override - public void resolve(DeserializationContext ctxt) throws JsonMappingException { - ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt); - } -} 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 index cdcef5ea9..7e7ebbcc6 100644 --- 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 @@ -16,17 +16,14 @@ package io.serverlessworkflow.fluent.func.serialization.jackson; import com.fasterxml.jackson.databind.BeanDescription; -import com.fasterxml.jackson.databind.DeserializationConfig; -import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.SerializationConfig; -import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; 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.CallFunction; +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; @@ -58,19 +55,7 @@ public void setupModule(com.fasterxml.jackson.databind.Module.SetupContext conte super.addSerializer(LoopPredicateIndexFilter.class, serializer); super.addDeserializer(SerializedLambda.class, new SerializedLambdaDeserializer()); super.setMixInAnnotation(TaskMetadata.class, TaskMetadataMixIn.class); - super.setDeserializerModifier( - new BeanDeserializerModifier() { - @Override - public JsonDeserializer modifyDeserializer( - DeserializationConfig config, - BeanDescription beanDesc, - JsonDeserializer deserializer) { - if (beanDesc.getBeanClass().equals(CallFunction.class)) { - return new CallJavaDeserializer<>(deserializer); - } - return deserializer; - } - }); + super.setMixInAnnotation(FunctionArguments.class, FunctionArgumentsMixIn.class); super.setSerializerModifier( new BeanSerializerModifier() { @Override @@ -81,7 +66,6 @@ public List changeProperties( if (beanDesc.getBeanClass().equals(SerializedLambda.class)) { beanProperties.add(new SerializedLambdaWriter(beanProperties.get(0))); } - return beanProperties; } }); diff --git a/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsMixIn.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsMixIn.java new file mode 100644 index 000000000..431e32977 --- /dev/null +++ b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/FunctionArgumentsMixIn.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 = 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/SerializedLambdaWriter.java b/experimental/fluent/jackson/src/main/java/io/serverlessworkflow/fluent/func/serialization/jackson/SerializedLambdaWriter.java index 2a07fbd81..97436974f 100644 --- 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 @@ -16,6 +16,8 @@ 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; @@ -33,24 +35,36 @@ public SerializedLambdaWriter(BeanPropertyWriter base) { @Override public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws IOException { - 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); + 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.writeEndObject(); + gen.writeEndArray(); + } + } finally { + if (isEnabled) { + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, true); } - gen.writeEndArray(); } } } 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/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/CallTest.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/CallTest.java index bbd7c74f6..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,6 +17,7 @@ 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; @@ -217,10 +218,9 @@ void testIfWithModel() throws InterruptedException, ExecutionException { } } - 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/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/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/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/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 8f4a500a5..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 @@ -19,274 +19,111 @@ import io.serverlessworkflow.api.types.CallFunction; import io.serverlessworkflow.api.types.FunctionArguments; import java.lang.invoke.MethodType; -import java.lang.invoke.SerializedLambda; -import java.util.Map; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; -public abstract class CallJava extends CallFunction { +public abstract class CallJava { + + private CallJava() {} - private static final long serialVersionUID = 1L; public static final String JAVA_CALL_KEY = "Java"; public static final String FUNCTION_NAME_KEY = "function"; - private static final String VAR_NAME_KEY = "varName"; - private static final String INDEX_NAME_KEY = "index"; - - private final Optional> inputClass; - - protected CallJava(Optional> inputClass) { - super.setCall(JAVA_CALL_KEY); - this.inputClass = inputClass; - } + 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"; - public Optional> inputClass() { - return inputClass; + 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; } - public static CallJava consumer(Consumer consumer) { - return new CallJavaConsumer<>(consumer, Optional.empty()); + public static CallFunction consumer(Consumer consumer) { + return buildFunction( + consumer, + ReflectionUtils.methodType(consumer).map(m -> m.parameterType(0)), + Optional.empty()); } - public static CallJava consumer(Consumer consumer, Class inputClass) { - return new CallJavaConsumer<>(consumer, Optional.ofNullable(inputClass)); + public static CallFunction consumer(Consumer consumer, Class inputClass) { + return buildFunction(consumer, Optional.ofNullable(inputClass), Optional.empty()); } - public static CallJavaFunction function(Function function) { - return new CallJavaFunction<>(function, Optional.empty(), Optional.empty()); + public static CallFunction function(Function function) { + return buildFunction(function, Optional.empty(), Optional.empty()); } - public static CallJavaFunction function( - Function function, Class inputClass) { - return new CallJavaFunction<>(function, Optional.ofNullable(inputClass), Optional.empty()); + public static CallFunction function(Function function, Class inputClass) { + return buildFunction( + function, + Optional.ofNullable(inputClass), + ReflectionUtils.methodType(function).map(MethodType::returnType)); } - 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)); } - - @FunctionalInterface - public interface SerializedLambdaUnmarshaller { - SerializedLambda apply(Object serializedFunction) throws ReflectiveOperationException; - } - - public static CallJava fromFunctionProperties( - Map props, SerializedLambda sl) throws ReflectiveOperationException { - Object obj = ReflectionUtils.functionFromSerialized(sl); - MethodType methodType = ReflectionUtils.inferMethodType(sl); - Optional> input = Optional.of(methodType.parameterType(0)); - Optional> output = Optional.of(methodType.returnType()); - if (obj instanceof ContextFunction fn) { - return new CallJavaContextFunction(fn, input, output); - } else if (obj instanceof FilterFunction fn) { - return new CallJavaFilterFunction(fn, input, output); - } else if (obj instanceof LoopFunction loop) { - return new CallJavaLoopFunction(loop, (String) props.get(VAR_NAME_KEY), input, output); - } else if (obj instanceof LoopFunctionIndex loop) { - return new CallJavaLoopFunctionIndex( - loop, - (String) props.get(VAR_NAME_KEY), - (String) props.get(INDEX_NAME_KEY), - input, - output); - } else if (obj instanceof Function fn) { - return new CallJavaFunction(fn, input, output); - } else if (obj instanceof Consumer consumer) { - return new CallJavaConsumer(consumer, input); - } else { - throw new UnsupportedOperationException("Unrecognized function " + obj); - } - } - - 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 abstract static class CallAbstractJavaFunction extends CallJava { - - private static final long serialVersionUID = 1L; - - private final Optional> outputClass; - - protected CallAbstractJavaFunction( - Optional> inputClass, Optional> outputClass) { - super(inputClass); - this.outputClass = outputClass; - } - - public Optional> outputClass() { - return outputClass; - } - } - - 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; - this.withWith(new FunctionArguments().withAdditionalProperty(FUNCTION_NAME_KEY, 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; - this.withWith(new FunctionArguments().withAdditionalProperty(FUNCTION_NAME_KEY, 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; - this.withWith(new FunctionArguments().withAdditionalProperty(FUNCTION_NAME_KEY, 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, varName, Optional.empty(), Optional.empty()); - } - - public CallJavaLoopFunction( - LoopFunction function, - String varName, - Optional> inputClass, - Optional> outputClass) { - super(inputClass, outputClass); - this.function = function; - this.varName = varName; - this.withWith( - new FunctionArguments() - .withAdditionalProperty(FUNCTION_NAME_KEY, function) - .withAdditionalProperty(VAR_NAME_KEY, 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, varName, indexName, Optional.empty(), Optional.empty()); - } - - public CallJavaLoopFunctionIndex( - LoopFunctionIndex function, - String varName, - String indexName, - Optional> inputClass, - Optional> outputClass) { - super(inputClass, outputClass); - this.function = function; - this.varName = varName; - this.indexName = indexName; - this.withWith( - new FunctionArguments() - .withAdditionalProperty(FUNCTION_NAME_KEY, function) - .withAdditionalProperty(VAR_NAME_KEY, varName) - .withAdditionalProperty(INDEX_NAME_KEY, 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/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/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; - } -}