From 6bcc6686c585231ccb003c4010117016c224b086 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Thu, 11 Jul 2024 19:15:53 +0200 Subject: [PATCH 1/2] [Fix #379] Generating serializers/deserializers Signed-off-by: Francisco Javier Tirado Sarti --- .../api/CallTaskDeserializer.java | 38 --------- .../api/CallTaskSerializer.java | 30 ------- .../api/ObjectMapperFactory.java | 18 +---- .../api/SwitchItemDeserializer.java | 31 -------- .../api/SwitchItemSerializer.java | 33 -------- .../api/TaskDeserializer.java | 58 -------------- .../api/TaskItemDeserializer.java | 31 -------- .../api/TaskItemSerializer.java | 33 -------- .../api/TaskSerializer.java | 31 -------- .../DeserializeHelper.java | 5 +- .../SerializeHelper.java | 2 +- .../generator/AllAnyOneOfSchemaRule.java | 59 +++++++++++++- .../generator/GeneratorUtils.java | 79 +++++++++++++++++-- .../generator/UnevaluatedPropertiesRule.java | 55 ++++++++++++- 14 files changed, 185 insertions(+), 318 deletions(-) delete mode 100644 api/src/main/java/io/serverlessworkflow/api/CallTaskDeserializer.java delete mode 100644 api/src/main/java/io/serverlessworkflow/api/CallTaskSerializer.java delete mode 100644 api/src/main/java/io/serverlessworkflow/api/SwitchItemDeserializer.java delete mode 100644 api/src/main/java/io/serverlessworkflow/api/SwitchItemSerializer.java delete mode 100644 api/src/main/java/io/serverlessworkflow/api/TaskDeserializer.java delete mode 100644 api/src/main/java/io/serverlessworkflow/api/TaskItemDeserializer.java delete mode 100644 api/src/main/java/io/serverlessworkflow/api/TaskItemSerializer.java delete mode 100644 api/src/main/java/io/serverlessworkflow/api/TaskSerializer.java rename api/src/main/java/io/serverlessworkflow/{api => serialization}/DeserializeHelper.java (92%) rename api/src/main/java/io/serverlessworkflow/{api => serialization}/SerializeHelper.java (96%) diff --git a/api/src/main/java/io/serverlessworkflow/api/CallTaskDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/CallTaskDeserializer.java deleted file mode 100644 index 097c77b5..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/CallTaskDeserializer.java +++ /dev/null @@ -1,38 +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; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import io.serverlessworkflow.api.types.CallAsyncAPI; -import io.serverlessworkflow.api.types.CallGRPC; -import io.serverlessworkflow.api.types.CallHTTP; -import io.serverlessworkflow.api.types.CallOpenAPI; -import io.serverlessworkflow.api.types.CallTask; -import java.io.IOException; -import java.util.List; - -class CallTaskDeserializer extends JsonDeserializer { - - @Override - public CallTask deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return DeserializeHelper.deserializeOneOf( - p, - CallTask.class, - List.of(CallHTTP.class, CallAsyncAPI.class, CallOpenAPI.class, CallGRPC.class)); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/CallTaskSerializer.java b/api/src/main/java/io/serverlessworkflow/api/CallTaskSerializer.java deleted file mode 100644 index 866875ba..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/CallTaskSerializer.java +++ /dev/null @@ -1,30 +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; - -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 java.io.IOException; - -class CallTaskSerializer extends JsonSerializer { - @Override - public void serialize(CallTask value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { - SerializeHelper.serializeOneOf(gen, value); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java b/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java index f45c7632..7bc8414e 100644 --- a/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java +++ b/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java @@ -17,13 +17,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature; -import io.serverlessworkflow.api.types.CallTask; -import io.serverlessworkflow.api.types.SwitchItem; -import io.serverlessworkflow.api.types.Task; -import io.serverlessworkflow.api.types.TaskItem; class ObjectMapperFactory { @@ -41,21 +36,10 @@ public static final ObjectMapper yamlMapper() { } private static ObjectMapper configure(ObjectMapper mapper) { - SimpleModule simpleModule = new SimpleModule(); - simpleModule.addDeserializer(Task.class, new TaskDeserializer()); - simpleModule.addSerializer(Task.class, new TaskSerializer()); - simpleModule.addDeserializer(CallTask.class, new CallTaskDeserializer()); - simpleModule.addSerializer(CallTask.class, new CallTaskSerializer()); - simpleModule.addDeserializer(TaskItem.class, new TaskItemDeserializer()); - simpleModule.addSerializer(TaskItem.class, new TaskItemSerializer()); - simpleModule.addSerializer(SwitchItem.class, new SwitchItemSerializer()); - simpleModule.addDeserializer(SwitchItem.class, new SwitchItemDeserializer()); - return mapper .configure(SerializationFeature.INDENT_OUTPUT, true) .configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false) - .configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false) - .registerModule(simpleModule); + .configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); } private ObjectMapperFactory() {} diff --git a/api/src/main/java/io/serverlessworkflow/api/SwitchItemDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/SwitchItemDeserializer.java deleted file mode 100644 index 02d93585..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/SwitchItemDeserializer.java +++ /dev/null @@ -1,31 +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; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import io.serverlessworkflow.api.types.SwitchCase; -import io.serverlessworkflow.api.types.SwitchItem; -import java.io.IOException; - -class SwitchItemDeserializer extends JsonDeserializer { - - @Override - public SwitchItem deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return DeserializeHelper.deserializeItem(p, SwitchItem.class, SwitchCase.class); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/SwitchItemSerializer.java b/api/src/main/java/io/serverlessworkflow/api/SwitchItemSerializer.java deleted file mode 100644 index f76582cb..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/SwitchItemSerializer.java +++ /dev/null @@ -1,33 +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; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import io.serverlessworkflow.api.types.SwitchItem; -import java.io.IOException; - -class SwitchItemSerializer extends JsonSerializer { - - @Override - public void serialize(SwitchItem value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { - gen.writeStartObject(); - gen.writeObjectField(value.getName(), value.getSwitchCase()); - gen.writeEndObject(); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/TaskDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/TaskDeserializer.java deleted file mode 100644 index 45892cb7..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/TaskDeserializer.java +++ /dev/null @@ -1,58 +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; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import io.serverlessworkflow.api.types.CallTask; -import io.serverlessworkflow.api.types.DoTask; -import io.serverlessworkflow.api.types.EmitTask; -import io.serverlessworkflow.api.types.ForTask; -import io.serverlessworkflow.api.types.ForkTask; -import io.serverlessworkflow.api.types.ListenTask; -import io.serverlessworkflow.api.types.RaiseTask; -import io.serverlessworkflow.api.types.RunTask; -import io.serverlessworkflow.api.types.SetTask; -import io.serverlessworkflow.api.types.SwitchTask; -import io.serverlessworkflow.api.types.Task; -import io.serverlessworkflow.api.types.TryTask; -import io.serverlessworkflow.api.types.WaitTask; -import java.io.IOException; -import java.util.List; - -class TaskDeserializer extends JsonDeserializer { - - @Override - public Task deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return DeserializeHelper.deserializeOneOf( - p, - Task.class, - List.of( - CallTask.class, - DoTask.class, - SwitchTask.class, - TryTask.class, - RaiseTask.class, - EmitTask.class, - ForkTask.class, - ForTask.class, - ListenTask.class, - SetTask.class, - RunTask.class, - WaitTask.class)); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/TaskItemDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/TaskItemDeserializer.java deleted file mode 100644 index 00c6d352..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/TaskItemDeserializer.java +++ /dev/null @@ -1,31 +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; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import io.serverlessworkflow.api.types.Task; -import io.serverlessworkflow.api.types.TaskItem; -import java.io.IOException; - -class TaskItemDeserializer extends JsonDeserializer { - - @Override - public TaskItem deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return DeserializeHelper.deserializeItem(p, TaskItem.class, Task.class); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/TaskItemSerializer.java b/api/src/main/java/io/serverlessworkflow/api/TaskItemSerializer.java deleted file mode 100644 index 53e8a265..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/TaskItemSerializer.java +++ /dev/null @@ -1,33 +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; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import io.serverlessworkflow.api.types.TaskItem; -import java.io.IOException; - -class TaskItemSerializer extends JsonSerializer { - - @Override - public void serialize(TaskItem value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { - gen.writeStartObject(); - gen.writeObjectField(value.getName(), value.getTask()); - gen.writeEndObject(); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/TaskSerializer.java b/api/src/main/java/io/serverlessworkflow/api/TaskSerializer.java deleted file mode 100644 index 1f9d65f9..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/TaskSerializer.java +++ /dev/null @@ -1,31 +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; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import io.serverlessworkflow.api.types.Task; -import java.io.IOException; - -class TaskSerializer extends JsonSerializer { - - @Override - public void serialize(Task value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { - SerializeHelper.serializeOneOf(gen, value); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/DeserializeHelper.java b/api/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java similarity index 92% rename from api/src/main/java/io/serverlessworkflow/api/DeserializeHelper.java rename to api/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java index b6dd947b..fc5390f1 100644 --- a/api/src/main/java/io/serverlessworkflow/api/DeserializeHelper.java +++ b/api/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.api; +package io.serverlessworkflow.serialization; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; @@ -27,7 +27,8 @@ public class DeserializeHelper { public static T deserializeOneOf( JsonParser p, Class targetClass, Collection> unionTypes) throws IOException { TreeNode node = p.readValueAsTree(); - JsonProcessingException ex = new JsonMappingException("Problem deserializing " + targetClass); + JsonProcessingException ex = + new JsonMappingException(p, "Problem deserializing " + targetClass); for (Class unionType : unionTypes) { try { Object object = p.getCodec().treeToValue(node, unionType); diff --git a/api/src/main/java/io/serverlessworkflow/api/SerializeHelper.java b/api/src/main/java/io/serverlessworkflow/serialization/SerializeHelper.java similarity index 96% rename from api/src/main/java/io/serverlessworkflow/api/SerializeHelper.java rename to api/src/main/java/io/serverlessworkflow/serialization/SerializeHelper.java index 85857637..5aaf2d6b 100644 --- a/api/src/main/java/io/serverlessworkflow/api/SerializeHelper.java +++ b/api/src/main/java/io/serverlessworkflow/serialization/SerializeHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.api; +package io.serverlessworkflow.serialization; import com.fasterxml.jackson.core.JsonGenerator; import java.io.IOException; diff --git a/custom-generator/src/main/java/io/serverlessworkflow/generator/AllAnyOneOfSchemaRule.java b/custom-generator/src/main/java/io/serverlessworkflow/generator/AllAnyOneOfSchemaRule.java index d4bcac84..167042f7 100644 --- a/custom-generator/src/main/java/io/serverlessworkflow/generator/AllAnyOneOfSchemaRule.java +++ b/custom-generator/src/main/java/io/serverlessworkflow/generator/AllAnyOneOfSchemaRule.java @@ -16,13 +16,17 @@ package io.serverlessworkflow.generator; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.node.ArrayNode; +import com.sun.codemodel.JBlock; import com.sun.codemodel.JClass; import com.sun.codemodel.JClassAlreadyExistsException; import com.sun.codemodel.JClassContainer; import com.sun.codemodel.JDefinedClass; import com.sun.codemodel.JExpr; import com.sun.codemodel.JFieldVar; +import com.sun.codemodel.JInvocation; import com.sun.codemodel.JMethod; import com.sun.codemodel.JMod; import com.sun.codemodel.JPackage; @@ -32,6 +36,7 @@ import java.net.URLDecoder; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Optional; import org.jsonschema2pojo.Jsonschema2Pojo; import org.jsonschema2pojo.Schema; @@ -113,20 +118,66 @@ private JDefinedClass populateClass( return definedClass; } + private JDefinedClass generateSerializer(JDefinedClass relatedClass) { + JDefinedClass definedClass = GeneratorUtils.serializerClass(relatedClass); + GeneratorUtils.fillSerializer( + definedClass, + relatedClass, + (method, valueParam, genParam) -> + method + .body() + .staticInvoke( + definedClass.owner().ref(GeneratorUtils.SERIALIZE_HELPER_NAME), + "serializeOneOf") + .arg(genParam) + .arg(valueParam)); + return definedClass; + } + + private JDefinedClass generateDeserializer( + JDefinedClass relatedClass, Collection unionTypes) { + JDefinedClass definedClass = GeneratorUtils.deserializerClass(relatedClass); + GeneratorUtils.fillDeserializer( + definedClass, + relatedClass, + (method, parserParam) -> { + JBlock body = method.body(); + JInvocation list = definedClass.owner().ref(List.class).staticInvoke("of"); + unionTypes.forEach(c -> list.arg(((JClass) c).dotclass())); + body._return( + definedClass + .owner() + .ref(GeneratorUtils.DESERIALIZE_HELPER_NAME) + .staticInvoke("deserializeOneOf") + .arg(parserParam) + .arg(relatedClass.dotclass()) + .arg(list)); + }); + return definedClass; + } + private JDefinedClass createUnionClass( String nodeName, JPackage container, Optional refType, Collection unionTypes) { - String className = ruleFactory.getNameHelper().getUniqueClassName(nodeName, null, container); + final String className = + ruleFactory.getNameHelper().getUniqueClassName(nodeName, null, container); try { - return populateClass(container._class(className), refType, unionTypes); + JDefinedClass definedClass = container._class(className); + definedClass.annotate(JsonSerialize.class).param("using", generateSerializer(definedClass)); + definedClass + .annotate(JsonDeserialize.class) + .param("using", generateDeserializer(definedClass, unionTypes)); + return populateClass(definedClass, refType, unionTypes); } catch (JClassAlreadyExistsException e) { throw new IllegalArgumentException(e); } } private void wrapIt(JDefinedClass definedClass, JType unionType) { + final String name = unionType.name(); JFieldVar instanceField = - GeneratorUtils.addGetter( - definedClass, unionType, ruleFactory.getNameHelper(), unionType.name()); + definedClass.field( + JMod.PRIVATE, unionType, ruleFactory.getNameHelper().getPropertyName(name, null)); + GeneratorUtils.buildMethod(definedClass, instanceField, ruleFactory.getNameHelper(), name); JMethod constructor = definedClass.constructor(JMod.PUBLIC); constructor .body() diff --git a/custom-generator/src/main/java/io/serverlessworkflow/generator/GeneratorUtils.java b/custom-generator/src/main/java/io/serverlessworkflow/generator/GeneratorUtils.java index d6736159..ffc23466 100644 --- a/custom-generator/src/main/java/io/serverlessworkflow/generator/GeneratorUtils.java +++ b/custom-generator/src/main/java/io/serverlessworkflow/generator/GeneratorUtils.java @@ -15,23 +15,88 @@ */ package io.serverlessworkflow.generator; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.sun.codemodel.JClassAlreadyExistsException; import com.sun.codemodel.JDefinedClass; import com.sun.codemodel.JFieldVar; import com.sun.codemodel.JMethod; import com.sun.codemodel.JMod; -import com.sun.codemodel.JType; +import com.sun.codemodel.JVar; +import java.io.IOException; import org.jsonschema2pojo.util.NameHelper; public class GeneratorUtils { - public static JFieldVar addGetter( - JDefinedClass definedClass, JType type, NameHelper nameHelper, String name) { - JFieldVar instanceField = - definedClass.field(JMod.PRIVATE, type, nameHelper.getPropertyName(name, null)); + public static final String SERIALIZE_HELPER_NAME = + "io.serverlessworkflow.serialization.SerializeHelper"; + public static final String DESERIALIZE_HELPER_NAME = + "io.serverlessworkflow.serialization.DeserializeHelper"; + + @FunctionalInterface + public interface SerializerFiller { + void accept(JMethod method, JVar valueParam, JVar genParam); + } + + @FunctionalInterface + public interface DeserializerFiller { + void accept(JMethod method, JVar parserParam); + } + + public static JDefinedClass serializerClass(JDefinedClass relatedClass) { + return createClass(relatedClass, JsonSerializer.class, "Serializer"); + } + + public static JDefinedClass deserializerClass(JDefinedClass relatedClass) { + return createClass(relatedClass, JsonDeserializer.class, "Deserializer"); + } + + public static JMethod buildMethod( + JDefinedClass definedClass, JFieldVar instanceField, NameHelper nameHelper, String name) { JMethod method = - definedClass.method(JMod.PUBLIC, type, nameHelper.getGetterName(name, type, null)); + definedClass.method( + JMod.PUBLIC, + instanceField.type(), + nameHelper.getGetterName(name, instanceField.type(), null)); method.body()._return(instanceField); - return instanceField; + return method; + } + + public static void fillSerializer( + JDefinedClass definedClass, JDefinedClass relatedClass, SerializerFiller filler) { + JMethod method = definedClass.method(JMod.PUBLIC, void.class, "serialize"); + method.annotate(Override.class); + method._throws(IOException.class); + JVar valueParam = method.param(relatedClass, "value"); + JVar genParam = method.param(JsonGenerator.class, "gen"); + method.param(SerializerProvider.class, "serializers"); + filler.accept(method, valueParam, genParam); + } + + public static void fillDeserializer( + JDefinedClass definedClass, JDefinedClass relatedClass, DeserializerFiller filler) { + JMethod method = definedClass.method(JMod.PUBLIC, relatedClass, "deserialize"); + method.annotate(Override.class); + method._throws(IOException.class); + JVar parserParam = method.param(JsonParser.class, "parser"); + method.param(DeserializationContext.class, "dctx"); + filler.accept(method, parserParam); + } + + private static JDefinedClass createClass( + JDefinedClass relatedClass, Class serializerClass, String suffix) { + try { + JDefinedClass definedClass = + relatedClass._package()._class(JMod.NONE, relatedClass.name() + suffix); + definedClass._extends(definedClass.owner().ref(serializerClass).narrow(relatedClass)); + return definedClass; + } catch (JClassAlreadyExistsException ex) { + throw new IllegalArgumentException(ex); + } } private GeneratorUtils() {} diff --git a/custom-generator/src/main/java/io/serverlessworkflow/generator/UnevaluatedPropertiesRule.java b/custom-generator/src/main/java/io/serverlessworkflow/generator/UnevaluatedPropertiesRule.java index e94d6a25..25eacd11 100644 --- a/custom-generator/src/main/java/io/serverlessworkflow/generator/UnevaluatedPropertiesRule.java +++ b/custom-generator/src/main/java/io/serverlessworkflow/generator/UnevaluatedPropertiesRule.java @@ -16,6 +16,10 @@ package io.serverlessworkflow.generator; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.sun.codemodel.JBlock; +import com.sun.codemodel.JClass; import com.sun.codemodel.JDefinedClass; import com.sun.codemodel.JExpr; import com.sun.codemodel.JFieldVar; @@ -58,7 +62,9 @@ private JDefinedClass addKeyValueFields( JDefinedClass jclass, JsonNode node, JsonNode parent, String nodeName, Schema schema) { NameHelper nameHelper = ruleFactory.getNameHelper(); JType stringClass = jclass.owner()._ref(String.class); - JFieldVar nameField = GeneratorUtils.addGetter(jclass, stringClass, nameHelper, "name"); + JFieldVar nameField = + jclass.field(JMod.PRIVATE, stringClass, nameHelper.getPropertyName("name", null)); + JMethod nameMethod = GeneratorUtils.buildMethod(jclass, nameField, nameHelper, "name"); JType propertyType; if (node != null && node.size() != 0) { String pathToAdditionalProperties; @@ -83,7 +89,16 @@ private JDefinedClass addKeyValueFields( propertyType = jclass.owner().ref(Object.class); } JFieldVar valueField = - GeneratorUtils.addGetter(jclass, propertyType, nameHelper, propertyType.name()); + jclass.field( + JMod.PRIVATE, propertyType, nameHelper.getPropertyName(propertyType.name(), null)); + JMethod valueMethod = + GeneratorUtils.buildMethod(jclass, valueField, nameHelper, propertyType.name()); + jclass + .annotate(JsonSerialize.class) + .param("using", generateSerializer(jclass, nameMethod, valueMethod)); + jclass + .annotate(JsonDeserialize.class) + .param("using", generateDeserializer(jclass, propertyType)); JMethod constructor = jclass.constructor(JMod.PUBLIC); constructor .body() @@ -92,6 +107,42 @@ private JDefinedClass addKeyValueFields( return jclass; } + private JDefinedClass generateDeserializer(JDefinedClass relatedClass, JType propertyType) { + JDefinedClass definedClass = GeneratorUtils.deserializerClass(relatedClass); + GeneratorUtils.fillDeserializer( + definedClass, + relatedClass, + (method, parserParam) -> + method + .body() + ._return( + definedClass + .owner() + .ref(GeneratorUtils.DESERIALIZE_HELPER_NAME) + .staticInvoke("deserializeItem") + .arg(parserParam) + .arg(relatedClass.dotclass()) + .arg(((JClass) propertyType).dotclass()))); + return definedClass; + } + + private JDefinedClass generateSerializer( + JDefinedClass relatedClass, JMethod nameMethod, JMethod valueMethod) { + JDefinedClass definedClass = GeneratorUtils.serializerClass(relatedClass); + GeneratorUtils.fillSerializer( + definedClass, + relatedClass, + (method, valueParam, genParam) -> { + JBlock body = method.body(); + body.invoke(genParam, "writeStartObject"); + body.invoke(genParam, "writeObjectField") + .arg(valueParam.invoke(nameMethod)) + .arg(valueParam.invoke(valueMethod)); + body.invoke(genParam, "writeEndObject"); + }); + return definedClass; + } + private boolean checkIntValue(JsonNode node, String propName, int value) { return node.has(propName) && node.get(propName).asInt() == value; } From 9ebcdbe0de878c3342b2d2b6284f1d6be5aab871 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Thu, 18 Jul 2024 17:56:37 +0200 Subject: [PATCH 2/2] [Fix #379] Update to last version of the schema Signed-off-by: Francisco Javier Tirado Sarti --- api/src/main/resources/schema/workflow.yaml | 183 ++++++++++++-------- 1 file changed, 107 insertions(+), 76 deletions(-) diff --git a/api/src/main/resources/schema/workflow.yaml b/api/src/main/resources/schema/workflow.yaml index 1c6d1bcc..c1a0d749 100644 --- a/api/src/main/resources/schema/workflow.yaml +++ b/api/src/main/resources/schema/workflow.yaml @@ -103,6 +103,7 @@ properties: description: Schedules the workflow $defs: taskList: + title: TaskList type: array items: type: object @@ -181,10 +182,8 @@ $defs: type: object description: The payload to call the AsyncAPI operation with, if any. authentication: + $ref: '#/$defs/referenceableAuthenticationPolicy' description: The authentication policy, if any, to use when calling the AsyncAPI operation. - oneOf: - - $ref: '#/$defs/authenticationPolicy' - - type: string required: [ document, operationRef ] additionalProperties: false description: Defines the AsyncAPI call to perform. @@ -216,14 +215,12 @@ $defs: description: The hostname of the GRPC service to call. port: type: integer - min: 0 - max: 65535 + minimum: 0 + maximum: 65535 description: The port number of the GRPC service to call. authentication: + $ref: '#/$defs/referenceableAuthenticationPolicy' description: The endpoint's authentication policy, if any. - oneOf: - - $ref: '#/$defs/authenticationPolicy' - - type: string required: [ name, host ] method: type: string @@ -293,10 +290,8 @@ $defs: additionalProperties: true description: A name/value mapping of the parameters of the OpenAPI operation to call. authentication: + $ref: '#/$defs/referenceableAuthenticationPolicy' description: The authentication policy, if any, to use when calling the OpenAPI operation. - oneOf: - - $ref: '#/$defs/authenticationPolicy' - - type: string output: type: string enum: [ raw, content, response ] @@ -320,6 +315,7 @@ $defs: additionalProperties: true description: A name/value mapping of the parameters, if any, to call the function with. forkTask: + title: ForkTask description: Allows workflows to execute multiple tasks concurrently and optionally race them against each other, with a single possible winner, which sets the task's output. $ref: '#/$defs/taskBase' type: object @@ -337,6 +333,7 @@ $defs: type: boolean default: false doTask: + title: DoTask description: Allows to execute a list of tasks in sequence $ref: '#/$defs/taskBase' type: object @@ -346,6 +343,7 @@ $defs: do: $ref: '#/$defs/taskList' emitTask: + title: EmitTask description: Allows workflows to publish events to event brokers or messaging systems, facilitating communication and coordination between different components and services. $ref: '#/$defs/taskBase' type: object @@ -383,6 +381,7 @@ $defs: additionalProperties: true required: [ event ] forTask: + title: ForTask description: Allows workflows to iterate over a collection of items, executing a defined set of subtasks for each item in the collection. This task type is instrumental in handling scenarios such as batch processing, data transformation, and repetitive operations across datasets. $ref: '#/$defs/taskBase' type: object @@ -410,6 +409,7 @@ $defs: do: $ref: '#/$defs/taskList' listenTask: + title: ListenTask description: Provides a mechanism for workflows to await and react to external events, enabling event-driven behavior within workflow systems. $ref: '#/$defs/taskBase' type: object @@ -424,6 +424,7 @@ $defs: description: Defines the event(s) to listen to. required: [ to ] raiseTask: + title: RaiseTask description: Intentionally triggers and propagates errors. $ref: '#/$defs/taskBase' type: object @@ -438,6 +439,7 @@ $defs: description: Defines the error to raise. required: [ error ] runTask: + title: RunTask description: Provides the capability to execute external containers, shell commands, scripts, or workflows. $ref: '#/$defs/taskBase' type: object @@ -546,6 +548,7 @@ $defs: required: [ workflow ] description: Enables the invocation and execution of nested workflows within a parent workflow, facilitating modularization, reusability, and abstraction of complex logic or business processes by encapsulating them into standalone workflow units. setTask: + title: SetTask description: A task used to set data $ref: '#/$defs/taskBase' type: object @@ -558,6 +561,7 @@ $defs: additionalProperties: true description: The data to set switchTask: + title: SwitchTask description: Enables conditional branching within workflows, allowing them to dynamically select different paths based on specified conditions or criteria $ref: '#/$defs/taskBase' type: object @@ -575,10 +579,9 @@ $defs: additionalProperties: type: object title: SwitchCase + required: + - then properties: - name: - type: string - description: The case's name. when: type: string description: A runtime expression used to determine whether or not the case matches. @@ -586,6 +589,7 @@ $defs: $ref: '#/$defs/flowDirective' description: The flow directive to execute when the case matches. tryTask: + title: TryTask description: Serves as a mechanism within workflows to handle errors gracefully, potentially retrying failed tasks before proceeding with alternate ones. $ref: '#/$defs/taskBase' type: object @@ -617,6 +621,7 @@ $defs: description: The definition of the task(s) to run when catching an error. $ref: '#/$defs/taskList' waitTask: + title: WaitTask description: Allows workflows to pause or delay their execution for a specified period of time. $ref: '#/$defs/taskBase' type: object @@ -627,12 +632,30 @@ $defs: description: The amount of time to wait. $ref: '#/$defs/duration' flowDirective: - additionalProperties: false anyOf: - type: string enum: [ continue, exit, end ] default: continue - type: string + referenceableAuthenticationPolicy: + type: object + oneOf: + - title: AuthenticationPolicyReference + properties: + use: + type: string + minLength: 1 + description: The name of the authentication policy to use + required: [use] + - $ref: '#/$defs/authenticationPolicy' + secretBasedAuthenticationPolicy: + type: object + properties: + use: + type: string + minLength: 1 + description: The name of the authentication policy to use + required: [use] authenticationPolicy: type: object oneOf: @@ -640,72 +663,78 @@ $defs: properties: basic: type: object - properties: - username: - type: string - description: The username to use. - password: - type: string - description: The password to use. - required: [ username, password ] + oneOf: + - properties: + username: + type: string + description: The username to use. + password: + type: string + description: The password to use. + required: [ username, password ] + - $ref: '#/$defs/secretBasedAuthenticationPolicy' required: [ basic ] description: Use basic authentication. - title: BearerAuthenticationPolicy properties: bearer: type: object - properties: - token: - type: string - description: The bearer token to use. - required: [ token ] + oneOf: + - properties: + token: + type: string + description: The bearer token to use. + required: [ token ] + - $ref: '#/$defs/secretBasedAuthenticationPolicy' required: [ bearer ] description: Use bearer authentication. - title: OAuth2AuthenticationPolicy properties: oauth2: type: object - properties: - authority: - type: string - format: uri - description: The URI that references the OAuth2 authority to use. - grant: - type: string - description: The grant type to use. - client: - type: object - properties: - id: + oneOf: + - properties: + authority: type: string - description: The client id to use. - secret: + format: uri + description: The URI that references the OAuth2 authority to use. + grant: type: string - description: The client secret to use, if any. - required: [ id ] - scopes: - type: array - items: - type: string - description: The scopes, if any, to request the token for. - audiences: - type: array - items: - type: string - description: The audiences, if any, to request the token for. - username: - type: string - description: The username to use. Used only if the grant type is Password. - password: - type: string - description: The password to use. Used only if the grant type is Password. - subject: - $ref: '#/$defs/oauth2Token' - description: The security token that represents the identity of the party on behalf of whom the request is being made. - actor: - $ref: '#/$defs/oauth2Token' - description: The security token that represents the identity of the acting party. - required: [ authority, grant, client ] + description: The grant type to use. + client: + type: object + properties: + id: + type: string + description: The client id to use. + secret: + type: string + description: The client secret to use, if any. + required: [ id ] + scopes: + type: array + items: + type: string + description: The scopes, if any, to request the token for. + audiences: + type: array + items: + type: string + description: The audiences, if any, to request the token for. + username: + type: string + description: The username to use. Used only if the grant type is Password. + password: + type: string + description: The password to use. Used only if the grant type is Password. + subject: + $ref: '#/$defs/oauth2Token' + description: The security token that represents the identity of the party on behalf of whom the request is being made. + actor: + $ref: '#/$defs/oauth2Token' + description: The security token that represents the identity of the acting party. + required: [ authority, grant, client ] + - $ref: '#/$defs/secretBasedAuthenticationPolicy' required: [ oauth2 ] description: Use OAUTH2 authentication. description: Defines an authentication policy. @@ -768,10 +797,8 @@ $defs: format: uri-template description: The endpoint's URI. authentication: + $ref: '#/$defs/referenceableAuthenticationPolicy' description: The authentication policy to use. - oneOf: - - $ref: '#/$defs/authenticationPolicy' - - type: string required: [ uri ] eventConsumptionStrategy: type: object @@ -871,10 +898,8 @@ $defs: format: uri description: The endpoint's URI. authentication: + $ref: '#/$defs/referenceableAuthenticationPolicy' description: The authentication policy to use. - oneOf: - - $ref: '#/$defs/authenticationPolicy' - - type: string name: type: string description: The external resource's name, if any. @@ -886,7 +911,9 @@ $defs: $ref: '#/$defs/schema' description: The schema used to describe and validate the input of the workflow or task. from: - type: string + oneOf: + - type: string + - type: object description: A runtime expression, if any, used to mutate and/or filter the input of the workflow or task. description: Configures the input of a workflow or task. output: @@ -896,7 +923,9 @@ $defs: $ref: '#/$defs/schema' description: The schema used to describe and validate the output of the workflow or task. as: - type: string + oneOf: + - type: string + - type: object description: A runtime expression, if any, used to mutate and/or filter the output of the workflow or task. description: Configures the output of a workflow or task. export: @@ -906,7 +935,9 @@ $defs: $ref: '#/$defs/schema' description: The schema used to describe and validate the workflow context. as: - type: string + oneOf: + - type: string + - type: object description: A runtime expression, if any, used to export the output data to the context. description: Set the content of the context. retryPolicy: