From 16683c855c3a12e80a747f7dcbc28aa870f7b964 Mon Sep 17 00:00:00 2001 From: Andrii Serkes Date: Fri, 5 Aug 2022 12:09:26 +0200 Subject: [PATCH] Serialization (JSON-B vs Jackson) for Helidon SE Server generator (#27) Serialization (JSON-B vs Jackson) for Helidon SE Server generator Signed-off-by: aserkes --- .../languages/JavaHelidonServerCodegen.java | 29 +++++++++ .../server/libraries/se/api.mustache | 4 ++ .../server/libraries/se/build.gradle.mustache | 6 +- .../server/libraries/se/enumClass.mustache | 56 ++++++++++++++-- .../libraries/se/enumOuterClass.mustache | 56 ++++++++++++++-- .../server/libraries/se/jsonProvider.mustache | 20 ++++++ .../server/libraries/se/main.mustache | 13 +++- .../server/libraries/se/model.mustache | 5 +- .../server/libraries/se/pojo.mustache | 65 +++++++++++++++++-- .../server/libraries/se/pom.mustache | 17 ++--- 10 files changed, 237 insertions(+), 34 deletions(-) create mode 100644 modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/jsonProvider.mustache diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonServerCodegen.java index 4dc1d0c76bdd..9c0132c352f5 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonServerCodegen.java @@ -29,6 +29,7 @@ import org.openapitools.codegen.CodegenConstants; import org.openapitools.codegen.CodegenModel; import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenProperty; import org.openapitools.codegen.CodegenType; import org.openapitools.codegen.SupportingFile; import org.openapitools.codegen.meta.features.DocumentationFeature; @@ -138,6 +139,9 @@ public void processOpts() { importMapping.put("UncheckedIOException", "java.io.UncheckedIOException"); importMapping.put("IOException", "java.io.IOException"); importMapping.put("ByteArrayInputStream", "java.io.ByteArrayInputStream"); + importMapping.put("ObjectMapper", "com.fasterxml.jackson.databind.ObjectMapper"); + importMapping.put("Jsonb", "javax.json.bind.Jsonb"); + importMapping.put("JsonbBuilder", "javax.json.bind.JsonbBuilder"); if (!additionalProperties.containsKey(MICROPROFILE_ROOT_PACKAGE_PROPERTY)) { additionalProperties.put(MICROPROFILE_ROOT_PACKAGE_PROPERTY, MICROPROFILE_REST_CLIENT_DEFAULT_ROOT_PACKAGE); @@ -195,6 +199,11 @@ public void processOpts() { additionalProperties.put(SERIALIZATION_LIBRARY_JACKSON, "true"); additionalProperties.remove(SERIALIZATION_LIBRARY_JSONB); supportingFiles.add(new SupportingFile("RFC3339DateFormat.mustache", invokerFolder, "RFC3339DateFormat.java")); + if (isLibrary(HELIDON_SE)) { + supportingFiles.add(new SupportingFile("jsonProvider.mustache", + (sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), + "JsonProvider.java")); + } break; case SERIALIZATION_LIBRARY_JSONB: additionalProperties.put(SERIALIZATION_LIBRARY_JSONB, "true"); @@ -212,6 +221,13 @@ public void processOpts() { public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List servers) { CodegenOperation codegenOperation = super.fromOperation(path, httpMethod, operation, servers); if (HELIDON_SE.equals(getLibrary())) { + if (additionalProperties.containsKey(JACKSON)){ + codegenOperation.imports.add("ObjectMapper"); + } + if (additionalProperties.containsKey(SERIALIZATION_LIBRARY_JSONB)){ + codegenOperation.imports.add("Jsonb"); + codegenOperation.imports.add("JsonbBuilder"); + } if (codegenOperation.bodyParam != null) { codegenOperation.imports.add("Handler"); } @@ -279,6 +295,19 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, ListadditionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}} { {{#allowableValues}}{{#enumVars}}{{{name}}}({{{value}}}){{^-last}}, {{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} - private String value; + private {{{dataType}}} value; - {{{datatypeWithEnum}}}(String value) { + {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}({{{dataType}}} value) { this.value = value; } + {{#jackson}} + @JsonValue + {{/jackson}} + public {{{dataType}}} getValue() { + return value; + } + @Override public String toString() { - return value; + return String.valueOf(value); + } + + {{#jsonb}} + public static final class Deserializer implements JsonbDeserializer<{{datatypeWithEnum}}> { + @Override + public {{datatypeWithEnum}} deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { + for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (String.valueOf(b.value).equals(parser.getString())) { + return b; + } + } + {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + parser.getString() + "'");{{/useNullForUnknownEnumValue}} + } + } + + public static final class Serializer implements JsonbSerializer<{{datatypeWithEnum}}> { + @Override + public void serialize({{datatypeWithEnum}} obj, JsonGenerator generator, SerializationContext ctx) { + generator.write(obj.value); + } + } + {{/jsonb}} + + {{#jackson}} + @JsonCreator + {{/jackson}} + public static {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} fromValue(String text) { + for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + text + "'");{{/useNullForUnknownEnumValue}} } } diff --git a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/enumOuterClass.mustache b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/enumOuterClass.mustache index fddd076841e1..a695da1902a0 100644 --- a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/enumOuterClass.mustache +++ b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/enumOuterClass.mustache @@ -1,6 +1,26 @@ +{{#jsonb}}import java.lang.reflect.Type; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeDeserializer; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeSerializer; +import {{rootJavaEEPackage}}.json.bind.serializer.DeserializationContext; +import {{rootJavaEEPackage}}.json.bind.serializer.JsonbDeserializer; +import {{rootJavaEEPackage}}.json.bind.serializer.JsonbSerializer; +import {{rootJavaEEPackage}}.json.bind.serializer.SerializationContext; +import {{rootJavaEEPackage}}.json.stream.JsonGenerator; +import {{rootJavaEEPackage}}.json.stream.JsonParser; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbProperty; +{{#vendorExtensions.x-has-readonly-properties}} +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbCreator; +{{/vendorExtensions.x-has-readonly-properties}}{{/jsonb}} +{{#jackson}} +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue;{{/jackson}} + /** * {{^description}}Gets or Sets {{{name}}}{{/description}}{{{description}}} */ +{{#jsonb}} +@JsonbTypeSerializer({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Serializer.class) +@JsonbTypeDeserializer({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Deserializer.class){{/jsonb}} public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} { {{#allowableValues}} @@ -15,22 +35,46 @@ public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatyp this.value = value; } + {{#jackson}} + @JsonValue + {{/jackson}} + public {{{dataType}}} getValue() { + return value; + } + @Override public String toString() { return String.valueOf(value); } +{{#jsonb}} + public static final class Deserializer implements JsonbDeserializer<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}> { + @Override + public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { + for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (String.valueOf(b.value).equals(parser.getString())) { + return b; + } + } + {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + parser.getString() + "'");{{/useNullForUnknownEnumValue}} + } + } + + public static final class Serializer implements JsonbSerializer<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}> { + @Override + public void serialize({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} obj, JsonGenerator generator, SerializationContext ctx) { + generator.write(obj.value); + } + } +{{/jsonb}} +{{#jackson}} + @JsonCreator{{/jackson}} public static {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} fromValue(String text) { for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { if (String.valueOf(b.value).equals(text)) { return b; } } - {{#isNullable}} - return null; - {{/isNullable}} - {{^isNullable}} - throw new IllegalArgumentException("Unexpected value '" + text + "'"); - {{/isNullable}} + {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + text + "'");{{/useNullForUnknownEnumValue}} } } diff --git a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/jsonProvider.mustache b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/jsonProvider.mustache new file mode 100644 index 000000000000..9a3b6f4c1076 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/jsonProvider.mustache @@ -0,0 +1,20 @@ +package {{apiPackage}}; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +public class JsonProvider { + + public static ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + mapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false); + return mapper; + } +} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/main.mustache b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/main.mustache index cd5acf19f6c4..5c12cd5b6db9 100644 --- a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/main.mustache +++ b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/main.mustache @@ -5,8 +5,14 @@ import io.helidon.common.reactive.Single; import io.helidon.config.Config; import io.helidon.health.HealthSupport; import io.helidon.health.checks.HealthChecks; -import io.helidon.media.jsonb.JsonbSupport; import io.helidon.media.jsonp.JsonpSupport; +{{#jsonb}} +import io.helidon.media.jsonb.JsonbSupport; +{{/jsonb}} +{{#jackson}} +import io.helidon.media.jackson.JacksonSupport; +import {{apiPackage}}.JsonProvider; +{{/jackson}} import io.helidon.metrics.MetricsSupport; import io.helidon.openapi.OpenAPISupport; import io.helidon.webserver.Routing; @@ -46,7 +52,12 @@ public final class Main { WebServer server = WebServer.builder(createRouting(config)) .config(config.get("server")) .addMediaSupport(JsonpSupport.create()) +{{#jsonb}} .addMediaSupport(JsonbSupport.create()) +{{/jsonb}} +{{#jackson}} + .addMediaSupport(JacksonSupport.create(JsonProvider.objectMapper())) +{{/jackson}} .build(); Single webserver = server.start(); diff --git a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/model.mustache b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/model.mustache index 0635d1342c42..dbe0359ada42 100644 --- a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/model.mustache +++ b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/model.mustache @@ -3,10 +3,7 @@ package {{package}}; {{#imports}}import {{import}}; {{/imports}} {{#models}} -{{#model}}{{#description}} -/** - * {{{.}}} - */{{/description}}{{#isEnum}} +{{#model}}{{#isEnum}} {{>enumOuterClass}}{{/isEnum}}{{^isEnum}} {{>pojo}}{{/isEnum}} {{/model}} diff --git a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/pojo.mustache b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/pojo.mustache index a133af360dae..944ae482956d 100644 --- a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/pojo.mustache +++ b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/pojo.mustache @@ -1,15 +1,37 @@ +{{#jsonb}} +import java.lang.reflect.Type; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeDeserializer; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeSerializer; +import {{rootJavaEEPackage}}.json.bind.serializer.DeserializationContext; +import {{rootJavaEEPackage}}.json.bind.serializer.JsonbDeserializer; +import {{rootJavaEEPackage}}.json.bind.serializer.JsonbSerializer; +import {{rootJavaEEPackage}}.json.bind.serializer.SerializationContext; +import {{rootJavaEEPackage}}.json.stream.JsonGenerator; +import {{rootJavaEEPackage}}.json.stream.JsonParser; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbProperty; +{{#vendorExtensions.x-has-readonly-properties}} +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbCreator; +{{/vendorExtensions.x-has-readonly-properties}} +{{/jsonb}} + +{{#description}} +/** + * {{{.}}} + */{{/description}} public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}} {{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { {{#vars}} {{#isEnum}} + {{^isContainer}} - {{>enumClass}} +{{>enumClass}} {{/isContainer}} {{#isContainer}} {{#mostInnerItems}} - {{>enumClass}} +{{>enumClass}} {{/mostInnerItems}} {{/isContainer}} + {{/isEnum}} private {{{datatypeWithEnum}}} {{{name}}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; {{/vars}} @@ -18,7 +40,7 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}} {{#vendorExtens * Default constructor. */ public {{classname}}() { - // JSON-B + // JSON-B / Jackson } /** @@ -37,7 +59,18 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}} {{#vendorExtens this.{{name}} = {{name}}; {{/vars}} } -{{#vars}} + +{{#vars}}{{#vendorExtensions.x-has-readonly-properties}}{{#jsonb}} + @JsonbCreator + public {{classname}}( + {{#readOnlyVars}} + @JsonbProperty("{{baseName}}") {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} + {{/readOnlyVars}} + ) { + {{#readOnlyVars}} + this.{{name}} = {{name}}; + {{/readOnlyVars}} + }{{/jsonb}}{{/vendorExtensions.x-has-readonly-properties}} /** {{#description}} @@ -60,7 +93,29 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}} {{#vendorExtens public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { this.{{name}} = {{name}}; + }{{/vars}} + + /** + * Create a string representation of this pojo. + **/ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class {{classname}} {\n"); + {{#parent}}sb.append(" ").append(toIndentedString(super.toString())).append("\n");{{/parent}} + {{#vars}}sb.append(" {{name}}: ").append(toIndentedString({{name}})).append("\n"); + {{/vars}}sb.append("}"); + return sb.toString(); } -{{/vars}} + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private static String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } } diff --git a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/pom.mustache b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/pom.mustache index c28139100c9c..432f44950549 100644 --- a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/pom.mustache +++ b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/pom.mustache @@ -36,10 +36,6 @@ io.helidon.media helidon-media-jsonp - - io.helidon.media - helidon-media-jsonb - io.helidon.media helidon-media-multipart @@ -66,18 +62,15 @@ {{#jackson}} - org.glassfish.jersey.media - jersey-media-json-jackson + io.helidon.media + helidon-media-jackson {{/jackson}} {{#jsonb}} - org.glassfish.jersey.media - jersey-media-json-binding - - - jakarta.json.bind - jakarta.json.bind-api + io.helidon.media + helidon-media-jsonb + ${helidon.version} {{/jsonb}}