diff --git a/build.gradle.kts b/build.gradle.kts index e3a415031b..6aa5daf839 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -517,12 +517,15 @@ tasks { "sourceFolder" to "", "dateLibrary" to "java8", "disallowAdditionalPropertiesIfNotPresent" to "false", - "openApiNullable" to "false", "useJakartaEe" to "true" ) ) - // Specify custom Mustache template dir as temporary workaround for the issue where OpenAPI Generator - // fails to generate import statements for @JsonbCreator annotations. + // Specify custom Mustache template dir as temporary workaround for issues we have with the OpenAPI Generator. + // Both issues have to do with the support for JSON-B polymorphism type annotations introduced by + // https://github.com/OpenAPITools/openapi-generator/pull/20164 in OpenAPI Generator version 7.11. + // Instead of overriding these Mustache templates the obvious workaround seems to set the additional property + // 'jsonbPolymorphism' to false in this Gradle build file. However, that does not seem to work. + // Probably because this property is set by the OpenAPI Generator library itself regardless of our configuration. templateDir.set("$rootDir/src/main/resources/openapi-generator-templates") } @@ -568,7 +571,6 @@ tasks { "sourceFolder" to "", "dateLibrary" to "java8-localdatetime", "disallowAdditionalPropertiesIfNotPresent" to "false", - "openApiNullable" to "false", "useJakartaEe" to "true" ) ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d7ad5cea4b..b6b43aa707 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ nodejs = "20.17.0" jsonschema2pojo = "1.2.2" kotlin-csv = "1.10.0" openapi = "4.0.7" -openapi-generator = "7.10.0" +openapi-generator = "7.11.0" opentelemetry = "1.46.0" opentelemetry-instrumentation = "2.12.0" node-gradle = "7.1.0" diff --git a/src/itest/kotlin/nl/info/zac/itest/KlantRestServiceTest.kt b/src/itest/kotlin/nl/info/zac/itest/KlantRestServiceTest.kt index 40ed5ff000..37950e2284 100644 --- a/src/itest/kotlin/nl/info/zac/itest/KlantRestServiceTest.kt +++ b/src/itest/kotlin/nl/info/zac/itest/KlantRestServiceTest.kt @@ -86,9 +86,9 @@ class KlantRestServiceTest : BehaviorSpec({ the response should be a 200 HTTP response with personal data from both the BRP and Klanten databases """ ) { - response.code shouldBe HTTP_STATUS_OK val responseBody = response.body!!.string() logger.info { "Response: $responseBody" } + response.code shouldBe HTTP_STATUS_OK responseBody shouldEqualJson """ { "bsn": "$TEST_PERSON_HENDRIKA_JANSE_BSN", diff --git a/src/main/java/net/atos/zac/app/bag/converter/RESTBAGConverter.java b/src/main/java/net/atos/zac/app/bag/converter/RESTBAGConverter.java index 27a6b42588..688afcfd62 100644 --- a/src/main/java/net/atos/zac/app/bag/converter/RESTBAGConverter.java +++ b/src/main/java/net/atos/zac/app/bag/converter/RESTBAGConverter.java @@ -16,8 +16,8 @@ import org.apache.commons.lang3.StringUtils; import net.atos.client.bag.model.generated.PointGeoJSON; -import net.atos.client.bag.model.generated.PolygonGeoJSON; import net.atos.client.bag.model.generated.PuntOfVlak; +import net.atos.client.bag.model.generated.Surface; import net.atos.client.zgw.zrc.model.Zaak; import net.atos.client.zgw.zrc.model.zaakobjecten.Zaakobject; import net.atos.client.zgw.zrc.model.zaakobjecten.ZaakobjectAdres; @@ -95,11 +95,11 @@ public static String getHuisnummerWeergave(final Integer huisnummer, final Strin return volledigHuisnummer.toString().trim(); } - public static RestGeometry convertVlak(final PolygonGeoJSON polygonGeoJSON) { + public static RestGeometry convertVlak(final Surface surface) { return new RestGeometry( - polygonGeoJSON.getType().value(), + surface.getType().value(), null, - polygonGeoJSON.getCoordinates() + surface.getCoordinates() .stream() .map(coords -> coords.stream() .map(RESTBAGConverter::convertCoordinates) diff --git a/src/main/resources/openapi-generator-templates/model.mustache b/src/main/resources/openapi-generator-templates/model.mustache deleted file mode 100644 index 00bab37fc6..0000000000 --- a/src/main/resources/openapi-generator-templates/model.mustache +++ /dev/null @@ -1,64 +0,0 @@ -{{! Temporary workaround for the issue where the OpenAPI Generator Gradle plugin }} -{{! fails to generate JsonbCreator import statements for @JsonbCreator annotations. }} -{{! This template is based on a copy of the Java Microprofile Mustache model template of the main branch of https://github.com/OpenAPITools/openapi-generator }} -{{! with the JsonbCreator import statements added (see 'Lifely/INFO workaround' below). }} -{{! This workaround can be removed once we have migrated to a version of the OpenAPI Generator Gradle plugin that has solved this issue. }} -{{>licenseInfo}} -package {{package}}; - -{{#useReflectionEqualsHashCode}} -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -{{/useReflectionEqualsHashCode}} -import java.util.Objects; -import java.util.Arrays; -{{#imports}}import {{import}}; -{{/imports}} -{{#serializableModel}} -import java.io.Serializable; -{{/serializableModel}} -{{#jackson}} -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.JsonTypeName; -{{#withXml}} -import com.fasterxml.jackson.dataformat.xml.annotation.*; -{{/withXml}} -{{#vendorExtensions.x-has-readonly-properties}} -import com.fasterxml.jackson.annotation.JsonCreator; -{{/vendorExtensions.x-has-readonly-properties}} -{{/jackson}} -{{#withXml}} -import {{rootJavaEEPackage}}.xml.bind.annotation.*; -import {{rootJavaEEPackage}}.xml.bind.annotation.adapters.*; -{{/withXml}} -{{#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; -{{! Lifely/INFO workaround for issue where JsonbCreator import statements are not generated (but @JsonbCreator annotations are..). }} -{{!#vendorExtensions.x-has-readonly-properties}} -import {{rootJavaEEPackage}}.json.bind.annotation.JsonbCreator; -{{!/vendorExtensions.x-has-readonly-properties}} -{{/jsonb}} -{{#useBeanValidation}} -import {{rootJavaEEPackage}}.validation.constraints.*; -import {{rootJavaEEPackage}}.validation.Valid; -{{/useBeanValidation}} - -{{#models}} -{{#model}} -{{#isEnum}} -{{>enumOuterClass}} -{{/isEnum}} -{{^isEnum}} -{{>pojo}} -{{/isEnum}} -{{/model}} -{{/models}} diff --git a/src/main/resources/openapi-generator-templates/pojo.mustache b/src/main/resources/openapi-generator-templates/pojo.mustache new file mode 100644 index 0000000000..6784043115 --- /dev/null +++ b/src/main/resources/openapi-generator-templates/pojo.mustache @@ -0,0 +1,166 @@ +{{! Lifely/INFO temporary workaround for the issue where the OpenAPI Generator Gradle plugin }} +{{! incorrectly generates '@JsonbTransient' annotations for certain 'type' fields in polymorphic model classes }} +{{! which has the result that these fields are not serialized and deserialized leading to errors in the various APIs we use. }} +{{! For example in the generated BRP 'PersonenQuery.java' class the 'type' String field was given the '@JsonbTransient' annotation }} +{{! which breaks the ZAC BRP API integration because this is mandatory field in the BRP API. }} +{{! This was most likely introduced by: https://github.com/OpenAPITools/openapi-generator/pull/20164 }} +{{! It may also be that the BRP OpenAPI spec does not use the discriminator keyword correctly since these 'type' fields are simple strings. }} +{{! and not polymorphic structures. Also see: https://apidog.com/blog/openapi-discriminator-guide/ }} + +{{! This template is based on a copy of the Java Mustache model template of the main branch of https://github.com/OpenAPITools/openapi-generator }} +{{! This workaround can be removed once we have migrated to a version of the OpenAPI Generator Gradle plugin that has solved this issue. }} + +{{#withXml}} +{{#hasVars}}@XmlType(name = "{{classname}}", propOrder = + { {{#vars}}"{{name}}"{{^-last}}, {{/-last}}{{/vars}} } +){{/hasVars}} +{{^hasVars}}@XmlType(name = "{{classname}}"){{/hasVars}} +{{> xmlAnnotation }} +{{/withXml}} +{{#jackson}} +@JsonPropertyOrder({ +{{#vars}} + {{classname}}.JSON_PROPERTY_{{nameInSnakeCase}}{{^-last}},{{/-last}} +{{/vars}} +}) +{{#isClassnameSanitized}} +{{^hasDiscriminatorWithNonEmptyMapping}} +@JsonTypeName("{{name}}") +{{/hasDiscriminatorWithNonEmptyMapping}} +{{/isClassnameSanitized}} +{{/jackson}} +{{#description}} +/** + * {{{.}}} + */ +{{/description}} +{{>additionalModelTypeAnnotations}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}} +{{#vendorExtensions.x-class-extra-annotation}} +{{{vendorExtensions.x-class-extra-annotation}}} +{{/vendorExtensions.x-class-extra-annotation}} +public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { + {{#vars}}{{#isEnum}}{{^isContainer}} +{{>enumClass}}{{/isContainer}}{{#isContainer}}{{#mostInnerItems}} +{{>enumClass}}{{/mostInnerItems}}{{/isContainer}}{{/isEnum}} +{{#jackson}} + public static final String JSON_PROPERTY_{{nameInSnakeCase}} = "{{baseName}}"; +{{/jackson}} +{{#withXml}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} +{{/withXml}} +{{#description}} + /** + * {{{.}}} + */ +{{/description}} + {{^withXml}} + {{! Lifely/INFO. Changed this line to prevent the generation of the @JsonbTransient annotation for polymorphism. See reasons above. }} + {{#jsonb}}@JsonbProperty("{{baseName}}"){{/jsonb}} + {{! Lifely/INFO. End of change. }} + {{/withXml}} +{{#vendorExtensions.x-field-extra-annotation}} +{{{vendorExtensions.x-field-extra-annotation}}} +{{/vendorExtensions.x-field-extra-annotation}} +{{#isContainer}} + private {{{datatypeWithEnum}}} {{name}}{{#required}} = {{{defaultValue}}}{{/required}}{{^required}} = null{{/required}}; +{{/isContainer}} +{{^isContainer}} + private {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; +{{/isContainer}} + {{/vars}} +{{#vendorExtensions.x-has-readonly-properties}}{{^withXml}} + public {{classname}}() { + } + + {{#jsonb}}@JsonbCreator{{/jsonb}}{{#jackson}}@JsonCreator{{/jackson}} + public {{classname}}( + {{#readOnlyVars}} + {{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nillable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(value = JSON_PROPERTY_{{nameInSnakeCase}}{{#required}}, required = true{{/required}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} + {{/readOnlyVars}} + ) { + {{#readOnlyVars}} + this.{{name}} = {{name}}; + {{/readOnlyVars}} + } + {{/withXml}}{{/vendorExtensions.x-has-readonly-properties}} + {{#vars}} + /** + {{#description}} + * {{.}} + {{/description}} + {{^description}} + * Get {{name}} + {{/description}} + {{#minimum}} + * minimum: {{.}} + {{/minimum}} + {{#maximum}} + * maximum: {{.}} + {{/maximum}} + * @return {{name}} + {{#deprecated}} + * @deprecated + {{/deprecated}} + **/ +{{#deprecated}} + @Deprecated +{{/deprecated}} +{{#vendorExtensions.x-extra-annotation}} + {{{vendorExtensions.x-extra-annotation}}} +{{/vendorExtensions.x-extra-annotation}} +{{#useBeanValidation}}{{>beanValidation}}{{/useBeanValidation}}{{#jackson}}{{> jackson_annotations}}{{/jackson}} {{#withXml}}{{#isEnum}}{{^isArray}}{{^isMap}} public {{dataType}} {{getter}}() { + if ({{name}} == null) { + return null; + } + return {{name}}.value(); + }{{/isMap}}{{/isArray}}{{/isEnum}}{{/withXml}}{{^withXml}}{{#isEnum}}{{^isArray}}{{^isMap}}public {{datatypeWithEnum}} {{getter}}() { + return {{name}}; + }{{/isMap}}{{/isArray}}{{/isEnum}}{{/withXml}}{{#isEnum}}{{#isArray}}public {{{datatypeWithEnum}}} {{getter}}() { + return {{name}}; + }{{/isArray}}{{/isEnum}}{{#isEnum}}{{#isMap}}public {{{datatypeWithEnum}}} {{getter}}() { + return {{name}}; + }{{/isMap}}{{/isEnum}}{{^isEnum}}public {{{datatypeWithEnum}}} {{getter}}() { + return {{name}}; + }{{/isEnum}} + + {{^isReadOnly}} + /** + * Set {{name}} + */ +{{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}} +{{/vendorExtensions.x-setter-extra-annotation}}{{#jackson}}{{> jackson_annotations}}{{/jackson}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { + this.{{name}} = {{name}}; + } + + public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { + this.{{name}} = {{name}}; + return this; + } + {{#isArray}} + + public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; + } + this.{{name}}.add({{name}}Item); + return this; + } + {{/isArray}} + {{#isMap}} + + public {{classname}} put{{nameInPascalCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; + } + this.{{name}}.put(key, {{name}}Item); + return this; + } + {{/isMap}} + {{/isReadOnly}} + + {{/vars}} +{{>pojoOverrides}} +} \ No newline at end of file diff --git a/src/main/resources/openapi-generator-templates/typeInfoAnnotation.mustache b/src/main/resources/openapi-generator-templates/typeInfoAnnotation.mustache new file mode 100644 index 0000000000..b1cf717f4e --- /dev/null +++ b/src/main/resources/openapi-generator-templates/typeInfoAnnotation.mustache @@ -0,0 +1,31 @@ +{{! Lifely/INFO temporary workaround for the issue where the OpenAPI Generator Gradle plugin }} +{{! generates '@JsonbTypeInfo(key = "type")' annotations in the generated model classes both for }} +{{! parent and child polymorphic classes resulting in errors such as: }} +{{! "Caused by: jakarta.json.bind.JsonbException: One polymorphic chain cannot have two conflicting property names. }} +{{! Polymorphic type defined on the type net.atos.client.bag.model.generated.Standplaats }} +{{! and net.atos.client.bag.model.generated.AdresseerbaarObject have conflicting property name" }} +{{! This was most likely introduced by: https://github.com/OpenAPITools/openapi-generator/pull/20164 }} + +{{! This template is based on a copy of the Java Mustache model template of the main branch of https://github.com/OpenAPITools/openapi-generator }} +{{! This workaround can be removed once we have migrated to a version of the OpenAPI Generator Gradle plugin that has solved this issue. }} + +{{#jackson}} + +@JsonIgnoreProperties( + value = "{{{discriminator.propertyBaseName}}}", // ignore manually set {{{discriminator.propertyBaseName}}}, it will be automatically generated by Jackson during serialization + allowSetters = true // allows the {{{discriminator.propertyBaseName}}} to be set during deserialization +) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true) +{{#discriminator.mappedModels}} +{{#-first}} +@JsonSubTypes({ +{{/-first}} + @JsonSubTypes.Type(value = {{modelName}}.class, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"), +{{#-last}} +}) +{{/-last}} +{{/discriminator.mappedModels}} +{{/jackson}} +{{#jsonbPolymorphism}} +{{! Workaround Lifely/INFO: the original code has been removed for reasons see above }} +{{/jsonbPolymorphism}} \ No newline at end of file diff --git a/src/test/kotlin/net/atos/zac/app/bag/BagFixtures.kt b/src/test/kotlin/net/atos/zac/app/bag/BagFixtures.kt index 715a3e375b..f9a45f676c 100644 --- a/src/test/kotlin/net/atos/zac/app/bag/BagFixtures.kt +++ b/src/test/kotlin/net/atos/zac/app/bag/BagFixtures.kt @@ -9,12 +9,12 @@ import net.atos.client.bag.model.generated.AdresseerbaarObjectIOHal import net.atos.client.bag.model.generated.Ligplaats import net.atos.client.bag.model.generated.LigplaatsIOHal import net.atos.client.bag.model.generated.PointGeoJSON -import net.atos.client.bag.model.generated.PolygonGeoJSON import net.atos.client.bag.model.generated.PuntOfVlak import net.atos.client.bag.model.generated.Standplaats import net.atos.client.bag.model.generated.StandplaatsIOHal import net.atos.client.bag.model.generated.StatusPlaats import net.atos.client.bag.model.generated.StatusVerblijfsobject +import net.atos.client.bag.model.generated.Surface import net.atos.client.bag.model.generated.Verblijfsobject import net.atos.client.bag.model.generated.VerblijfsobjectIOHal import java.math.BigDecimal @@ -24,7 +24,7 @@ fun createLigplaatsAdresseerbaarObject(status: StatusPlaats) = ligplaats = LigplaatsIOHal().apply { ligplaats = Ligplaats().apply { this.status = status - this.geometrie = createPolygonGeoJSON() + this.geometrie = createSurface() } } } @@ -34,7 +34,7 @@ fun createStandplaatsAdresseerbaarObject(status: StatusPlaats) = standplaats = StandplaatsIOHal().apply { standplaats = Standplaats().apply { this.status = status - this.geometrie = createPolygonGeoJSON() + this.geometrie = createSurface() } } } @@ -56,8 +56,8 @@ fun createPuntOfVlak() = PuntOfVlak().apply { } } -fun createPolygonGeoJSON() = PolygonGeoJSON().apply { - type = PolygonGeoJSON.TypeEnum.POLYGON +fun createSurface() = Surface().apply { + type = Surface.TypeEnum.POLYGON coordinates = listOf(listOf(createCoordinates())) as List?>?>? } diff --git a/src/test/kotlin/net/atos/zac/app/bag/converter/RESTAdresseerbaarObjectConverterTest.kt b/src/test/kotlin/net/atos/zac/app/bag/converter/RESTAdresseerbaarObjectConverterTest.kt index b69981fe84..d6d0de690c 100644 --- a/src/test/kotlin/net/atos/zac/app/bag/converter/RESTAdresseerbaarObjectConverterTest.kt +++ b/src/test/kotlin/net/atos/zac/app/bag/converter/RESTAdresseerbaarObjectConverterTest.kt @@ -23,7 +23,7 @@ class RESTAdresseerbaarObjectConverterTest : BehaviorSpec({ } Given("Ligplaats addressbaar object") { - val adresseerbaarObjectIOHal = createLigplaatsAdresseerbaarObject(StatusPlaats.AANGEWEZEN) + val adresseerbaarObjectIOHal = createLigplaatsAdresseerbaarObject(StatusPlaats.PLAATS_AANGEWEZEN) When("converted to rest representation") { val result = restAdresseerbaarObjectConverter.convertToREST(adresseerbaarObjectIOHal) @@ -49,8 +49,8 @@ class RESTAdresseerbaarObjectConverterTest : BehaviorSpec({ } } - Given("Standplaats addressbaar object") { - val adresseerbaarObjectIOHal = createStandplaatsAdresseerbaarObject(StatusPlaats.AANGEWEZEN) + Given("Standplaats adresseerbaar object") { + val adresseerbaarObjectIOHal = createStandplaatsAdresseerbaarObject(StatusPlaats.PLAATS_AANGEWEZEN) When("converted to rest representation") { val result = restAdresseerbaarObjectConverter.convertToREST(adresseerbaarObjectIOHal)