diff --git a/api/src/main/java/io/serverlessworkflow/api/CallTaskDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/CallTaskDeserializer.java index fcfec3973..097c77b50 100644 --- a/api/src/main/java/io/serverlessworkflow/api/CallTaskDeserializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/CallTaskDeserializer.java @@ -30,7 +30,7 @@ class CallTaskDeserializer extends JsonDeserializer { @Override public CallTask deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return DeserializeHelper.deserialize( + 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 index 18abac33e..866875baa 100644 --- a/api/src/main/java/io/serverlessworkflow/api/CallTaskSerializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/CallTaskSerializer.java @@ -25,6 +25,6 @@ class CallTaskSerializer extends JsonSerializer { @Override public void serialize(CallTask value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - SerializeHelper.serialize(gen, value); + SerializeHelper.serializeOneOf(gen, value); } } diff --git a/api/src/main/java/io/serverlessworkflow/api/DeserializeHelper.java b/api/src/main/java/io/serverlessworkflow/api/DeserializeHelper.java index ba35b90d9..b6dd947b6 100644 --- a/api/src/main/java/io/serverlessworkflow/api/DeserializeHelper.java +++ b/api/src/main/java/io/serverlessworkflow/api/DeserializeHelper.java @@ -24,7 +24,7 @@ public class DeserializeHelper { - public static T deserialize( + public static T deserializeOneOf( JsonParser p, Class targetClass, Collection> unionTypes) throws IOException { TreeNode node = p.readValueAsTree(); JsonProcessingException ex = new JsonMappingException("Problem deserializing " + targetClass); @@ -38,4 +38,17 @@ public static T deserialize( } throw ex; } + + public static T deserializeItem(JsonParser p, Class targetClass, Class valueClass) + throws IOException { + TreeNode node = p.readValueAsTree(); + String fieldName = node.fieldNames().next(); + try { + return targetClass + .getConstructor(String.class, valueClass) + .newInstance(fieldName, p.getCodec().treeToValue(node.get(fieldName), valueClass)); + } catch (ReflectiveOperationException e) { + throw new IOException(e); + } + } } diff --git a/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java b/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java index d3a9645ca..f45c76327 100644 --- a/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java +++ b/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java @@ -21,7 +21,9 @@ 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 { @@ -44,6 +46,11 @@ private static ObjectMapper configure(ObjectMapper mapper) { 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) diff --git a/api/src/main/java/io/serverlessworkflow/api/SerializeHelper.java b/api/src/main/java/io/serverlessworkflow/api/SerializeHelper.java index e08de1593..858576373 100644 --- a/api/src/main/java/io/serverlessworkflow/api/SerializeHelper.java +++ b/api/src/main/java/io/serverlessworkflow/api/SerializeHelper.java @@ -20,7 +20,7 @@ import java.lang.reflect.Method; public class SerializeHelper { - public static void serialize(JsonGenerator jgen, Object item) throws IOException { + public static void serializeOneOf(JsonGenerator jgen, Object item) throws IOException { try { for (Method m : item.getClass().getDeclaredMethods()) { Object value = m.invoke(item); diff --git a/api/src/main/java/io/serverlessworkflow/api/SwitchItemDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/SwitchItemDeserializer.java new file mode 100644 index 000000000..02d935851 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/SwitchItemDeserializer.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.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 new file mode 100644 index 000000000..f76582cb5 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/SwitchItemSerializer.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.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 index d81091cae..45892cb77 100644 --- a/api/src/main/java/io/serverlessworkflow/api/TaskDeserializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/TaskDeserializer.java @@ -38,7 +38,7 @@ class TaskDeserializer extends JsonDeserializer { @Override public Task deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return DeserializeHelper.deserialize( + return DeserializeHelper.deserializeOneOf( p, Task.class, List.of( diff --git a/api/src/main/java/io/serverlessworkflow/api/TaskItemDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/TaskItemDeserializer.java new file mode 100644 index 000000000..00c6d3520 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/TaskItemDeserializer.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.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 new file mode 100644 index 000000000..53e8a2656 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/TaskItemSerializer.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.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 index 3900f16e1..1f9d65f99 100644 --- a/api/src/main/java/io/serverlessworkflow/api/TaskSerializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/TaskSerializer.java @@ -26,6 +26,6 @@ class TaskSerializer extends JsonSerializer { @Override public void serialize(Task value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - SerializeHelper.serialize(gen, value); + SerializeHelper.serializeOneOf(gen, value); } } diff --git a/api/src/main/resources/schema/workflow.yaml b/api/src/main/resources/schema/workflow.yaml index d3a20f2f2..1c6d1bcc3 100644 --- a/api/src/main/resources/schema/workflow.yaml +++ b/api/src/main/resources/schema/workflow.yaml @@ -571,8 +571,10 @@ $defs: type: object minProperties: 1 maxProperties: 1 + title: SwitchItem additionalProperties: type: object + title: SwitchCase properties: name: type: string diff --git a/api/src/test/java/io/serverlessworkflow/api/ApiTest.java b/api/src/test/java/io/serverlessworkflow/api/ApiTest.java index 879244975..8e4c27b7c 100644 --- a/api/src/test/java/io/serverlessworkflow/api/ApiTest.java +++ b/api/src/test/java/io/serverlessworkflow/api/ApiTest.java @@ -31,9 +31,9 @@ public class ApiTest { void testCallHTTPAPI() throws IOException { Workflow workflow = readWorkflowFromClasspath("features/callHttp.yaml"); assertThat(workflow.getDo()).isNotEmpty(); - assertThat(workflow.getDo().get(0).getAdditionalProperties()).isNotEmpty(); - assertThat(workflow.getDo().get(0).getAdditionalProperties().values()).isNotEmpty(); - Task task = workflow.getDo().get(0).getAdditionalProperties().values().iterator().next(); + assertThat(workflow.getDo().get(0).getName()).isNotNull(); + assertThat(workflow.getDo().get(0).getTask()).isNotNull(); + Task task = workflow.getDo().get(0).getTask(); CallTask callTask = task.getCallTask(); assertThat(callTask).isNotNull(); assertThat(task.getDoTask()).isNull(); 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 e16e07bcb..d4bcac849 100644 --- a/custom-generator/src/main/java/io/serverlessworkflow/generator/AllAnyOneOfSchemaRule.java +++ b/custom-generator/src/main/java/io/serverlessworkflow/generator/AllAnyOneOfSchemaRule.java @@ -15,8 +15,6 @@ */ package io.serverlessworkflow.generator; -import static org.apache.commons.lang3.StringUtils.*; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.sun.codemodel.JClass; @@ -127,16 +125,8 @@ private JDefinedClass createUnionClass( private void wrapIt(JDefinedClass definedClass, JType unionType) { JFieldVar instanceField = - definedClass.field( - JMod.PRIVATE, - unionType, - ruleFactory.getNameHelper().getPropertyName(unionType.name(), null)); - JMethod method = - definedClass.method( - JMod.PUBLIC, - unionType, - ruleFactory.getNameHelper().getGetterName(unionType.name(), unionType, null)); - method.body()._return(instanceField); + GeneratorUtils.addGetter( + definedClass, unionType, ruleFactory.getNameHelper(), unionType.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 new file mode 100644 index 000000000..d6736159c --- /dev/null +++ b/custom-generator/src/main/java/io/serverlessworkflow/generator/GeneratorUtils.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.generator; + +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 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)); + JMethod method = + definedClass.method(JMod.PUBLIC, type, nameHelper.getGetterName(name, type, null)); + method.body()._return(instanceField); + return instanceField; + } + + 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 b523967b8..e94d6a251 100644 --- a/custom-generator/src/main/java/io/serverlessworkflow/generator/UnevaluatedPropertiesRule.java +++ b/custom-generator/src/main/java/io/serverlessworkflow/generator/UnevaluatedPropertiesRule.java @@ -17,15 +17,25 @@ import com.fasterxml.jackson.databind.JsonNode; import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JFieldVar; +import com.sun.codemodel.JMethod; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JType; import org.jsonschema2pojo.Schema; import org.jsonschema2pojo.rules.AdditionalPropertiesRule; import org.jsonschema2pojo.rules.Rule; +import org.jsonschema2pojo.rules.RuleFactory; +import org.jsonschema2pojo.util.NameHelper; public class UnevaluatedPropertiesRule extends AdditionalPropertiesRule implements Rule { - public UnevaluatedPropertiesRule(UnreferencedFactory unreferencedFactory) { - super(unreferencedFactory); + private RuleFactory ruleFactory; + + public UnevaluatedPropertiesRule(RuleFactory ruleFactory) { + super(ruleFactory); + this.ruleFactory = ruleFactory; } public JDefinedClass apply( @@ -35,8 +45,54 @@ public JDefinedClass apply( || (node == null && parent.has("properties"))) { // no additional properties allowed return jclass; + } else if (node != null + && checkIntValue(parent, "maxProperties", 1) + && checkIntValue(parent, "minProperties", 1)) { + return addKeyValueFields(jclass, node, parent, nodeName, schema); } else { return super.apply(nodeName, node, parent, jclass, schema); } } + + 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"); + JType propertyType; + if (node != null && node.size() != 0) { + String pathToAdditionalProperties; + if (schema.getId().getFragment() == null) { + pathToAdditionalProperties = "#/additionalProperties"; + } else { + pathToAdditionalProperties = "#" + schema.getId().getFragment() + "/additionalProperties"; + } + Schema additionalPropertiesSchema = + ruleFactory + .getSchemaStore() + .create( + schema, + pathToAdditionalProperties, + ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters()); + propertyType = + ruleFactory + .getSchemaRule() + .apply(nodeName + "Property", node, parent, jclass, additionalPropertiesSchema); + additionalPropertiesSchema.setJavaTypeIfEmpty(propertyType); + } else { + propertyType = jclass.owner().ref(Object.class); + } + JFieldVar valueField = + GeneratorUtils.addGetter(jclass, propertyType, nameHelper, propertyType.name()); + JMethod constructor = jclass.constructor(JMod.PUBLIC); + constructor + .body() + .assign(JExpr._this().ref(nameField), constructor.param(stringClass, nameField.name())) + .assign(JExpr._this().ref(valueField), constructor.param(propertyType, valueField.name())); + return jclass; + } + + private boolean checkIntValue(JsonNode node, String propName, int value) { + return node.has(propName) && node.get(propName).asInt() == value; + } }