Skip to content

Commit

Permalink
fix: zaak geometry should not be deleted on zaak update (#2588)
Browse files Browse the repository at this point in the history
Fixed issue where zaak geometry was deleted on every zaak update and
fixed issue where zaak geometry update and deletions sometimes worked
and sometimes did not. The issue was that for deletions a @JsonbNillable
annotation was used on the Zaak object in the ZrcClient but for normal
updates not. However this annotation is static and there is only one
zaak patch method in the ZrcClient and this resulted in inconsistent
behaviour. This was solved by introducing a custom JSONB serializer for
the zaak Geometry class that dynamically either writes a null (for
deletions) or the geometry (Point only for now) object to JSON.

Solves  PZ-5299

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Hristo Iliev <[email protected]>
  • Loading branch information
3 people authored Feb 3, 2025
1 parent c4c179e commit d5f471d
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import io.kotest.matchers.shouldBe
import nl.info.zac.itest.client.ItestHttpClient
import nl.info.zac.itest.config.ItestConfiguration.OBJECT_PRODUCTAANVRAAG_1_BRON_KENMERK
import nl.info.zac.itest.config.ItestConfiguration.OPEN_FORMULIEREN_FORMULIER_BRON_NAAM
import nl.info.zac.itest.config.ItestConfiguration.PRODUCTAANVRAAG_ZAAKGEGEVENS_GEOMETRY_LATITUDE
import nl.info.zac.itest.config.ItestConfiguration.PRODUCTAANVRAAG_ZAAKGEGEVENS_GEOMETRY_LONGITUDE
import nl.info.zac.itest.config.ItestConfiguration.SMART_DOCUMENTS_FILE_TITLE
import nl.info.zac.itest.config.ItestConfiguration.TEST_GROUP_A_DESCRIPTION
import nl.info.zac.itest.config.ItestConfiguration.TEST_PERSON_2_BSN
Expand Down Expand Up @@ -129,13 +127,6 @@ class ZaakRestServiceHistoryTest : BehaviorSpec({
"nieuweWaarde": "$TEST_USER_2_NAME",
"toelichting": "dummyLijstVerdelenReason"
},
{
"actie": "GEWIJZIGD",
"attribuutLabel": "zaakgeometrie",
"door": "$TEST_USER_1_NAME",
"oudeWaarde": "POINT($PRODUCTAANVRAAG_ZAAKGEGEVENS_GEOMETRY_LATITUDE $PRODUCTAANVRAAG_ZAAKGEGEVENS_GEOMETRY_LONGITUDE)",
"toelichting": "Aanvullende informatie opgevraagd"
},
{
"actie": "GEWIJZIGD",
"attribuutLabel": "uiterlijkeEinddatumAfdoening",
Expand Down
69 changes: 64 additions & 5 deletions src/itest/kotlin/nl/info/zac/itest/ZaakRestServiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ class ZaakRestServiceTest : BehaviorSpec({
val itestHttpClient = ItestHttpClient()
val zacClient = ZacClient()
val logger = KotlinLogging.logger {}
val longitude = Random.nextFloat()
val latitude = Random.nextFloat()
val startDateNew = LocalDate.now()
val fatalDateNew = startDateNew.plusDays(1)

lateinit var zaak2UUID: UUID

Expand Down Expand Up @@ -388,8 +392,6 @@ class ZaakRestServiceTest : BehaviorSpec({
and also the communication channel is changed
"""
) {
val startDateNew = LocalDate.now()
val fatalDateNew = startDateNew.plusDays(1)
val response = itestHttpClient.performPatchRequest(
url = "$ZAC_API_URI/zaken/zaak/$zaak2UUID",
requestBodyAsString = """
Expand Down Expand Up @@ -469,8 +471,6 @@ class ZaakRestServiceTest : BehaviorSpec({
}
}
When("the 'update Zaak Locatie' endpoint is called with a valid location") {
val longitude = Random.nextFloat()
val latitude = Random.nextFloat()
val response = itestHttpClient.performPatchRequest(
url = "$ZAC_API_URI/zaken/$zaak2UUID/zaaklocatie",
requestBodyAsString = """
Expand Down Expand Up @@ -504,7 +504,66 @@ class ZaakRestServiceTest : BehaviorSpec({
}
}
}

When("the update zaak endpoint is called with a changed zaak description") {
val response = itestHttpClient.performPatchRequest(
url = "$ZAC_API_URI/zaken/zaak/$zaak2UUID",
requestBodyAsString = """
{
"zaak": {
"omschrijving": "changedDescription"
},
"reden": "dummyReason"
}
""".trimIndent()
)
Then(
"the response should be a 200 HTTP response with only the changed zaak description and no other changes"
) {
val responseBody = response.body!!.string()
logger.info { "Response: $responseBody" }
response.code shouldBe HTTP_STATUS_OK
responseBody shouldEqualJsonIgnoringOrderAndExtraneousFields """
{
"besluiten": [],
"bronorganisatie": "$BRON_ORGANISATIE",
"communicatiekanaal": "$COMMUNICATIEKANAAL_TEST_2",
"gerelateerdeZaken": [],
"groep": {
"id": "$TEST_GROUP_A_ID",
"naam": "$TEST_GROUP_A_DESCRIPTION"
},
"identificatie": "$ZAAK_MANUAL_1_IDENTIFICATION",
"indicaties": [],
"isBesluittypeAanwezig": false,
"isDeelzaak": false,
"isHeropend": false,
"isHoofdzaak": false,
"isInIntakeFase": true,
"isOntvangstbevestigingVerstuurd": false,
"isOpen": true,
"isOpgeschort": false,
"isProcesGestuurd": false,
"isVerlengd": false,
"kenmerken": [],
"omschrijving": "changedDescription",
"registratiedatum": "${LocalDate.now()}",
"startdatum": "$startDateNew",
"toelichting": "$ZAAK_EXPLANATION_1",
"uiterlijkeEinddatumAfdoening": "$fatalDateNew",
"uuid" : "$zaak2UUID",
"verantwoordelijkeOrganisatie" : "$VERANTWOORDELIJKE_ORGANISATIE",
"vertrouwelijkheidaanduiding" : "$DOCUMENT_VERTROUWELIJKHEIDS_AANDUIDING_OPENBAAR",
"zaakgeometrie" : {
"point" : {
"latitude" : $latitude,
"longitude" : $longitude
},
"type" : "Point"
}
}
""".trimIndent()
}
}
When("the 'update Zaak Locatie' endpoint is called with a null value as location") {
val response = itestHttpClient.performPatchRequest(
url = "$ZAC_API_URI/zaken/$zaak2UUID/zaaklocatie",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,23 @@
import net.atos.client.zgw.zrc.util.GeometryJsonbDeserializer;
import net.atos.client.zgw.zrc.util.RolJsonbDeserializer;
import net.atos.client.zgw.zrc.util.ZaakObjectJsonbDeserializer;
import nl.info.client.zgw.zrc.jsonb.GeometryJsonbSerializer;

public class JsonbConfiguration implements ContextResolver<Jsonb> {

private final Jsonb jsonb;

public JsonbConfiguration() {
final JsonbConfig jsonbConfig = new JsonbConfig().withDeserializers(
new RolJsonbDeserializer(),
new ZaakObjectJsonbDeserializer(),
new GeometryJsonbDeserializer(),
new URIJsonbDeserializer()
);
final JsonbConfig jsonbConfig = new JsonbConfig()
.withDeserializers(
new RolJsonbDeserializer(),
new ZaakObjectJsonbDeserializer(),
new GeometryJsonbDeserializer(),
new URIJsonbDeserializer()
)
.withSerializers(
new GeometryJsonbSerializer()
);
jsonb = JsonbBuilder.create(jsonbConfig);
}

Expand Down
21 changes: 21 additions & 0 deletions src/main/java/net/atos/client/zgw/zrc/model/Geometry.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,29 @@

package net.atos.client.zgw.zrc.model;

import jakarta.json.bind.annotation.JsonbTransient;
import jakarta.json.bind.annotation.JsonbTypeDeserializer;
import jakarta.json.bind.annotation.JsonbTypeSerializer;

import net.atos.client.zgw.zrc.util.GeometryJsonbDeserializer;
import nl.info.client.zgw.zrc.jsonb.GeometryJsonbSerializer;

@JsonbTypeDeserializer(GeometryJsonbDeserializer.class)
@JsonbTypeSerializer(GeometryJsonbSerializer.class)
public abstract class Geometry {
public static final String GEOMETRY_TYPE_NAAM = "type";

private final GeometryType type;

/**
* If set to true indicates that the geometry should be deleted.
* Used in {@link GeometryJsonbSerializer}.
* Note that the @JsonbNillable annotation cannot be used here because
* that is a static annotation, and we need to set this value dynamically.
*/
@JsonbTransient
private Boolean markGeometryForDeletion = false;

protected Geometry(final GeometryType type) {
this.type = type;
}
Expand All @@ -27,6 +40,14 @@ public GeometryType getType() {
return type;
}

public boolean getMarkGeometryForDeletion() {
return markGeometryForDeletion;
}

public void setMarkGeometryForDeletion() {
markGeometryForDeletion = true;
}

@Override
public abstract String toString();
}
17 changes: 17 additions & 0 deletions src/main/java/net/atos/client/zgw/zrc/model/GeometryToBeDeleted.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2025 Lifely
* SPDX-License-Identifier: EUPL-1.2+
*/

package net.atos.client.zgw.zrc.model

/**
* Geometry class to indicate that the current geometry should be deleted.
* Used to mark a geometry field for deletion in the ZGW ZRC API using
* the [nl.info.client.zgw.zrc.jsonb.GeometryJsonbSerializer].
*/
class GeometryToBeDeleted : Geometry {
constructor() { setMarkGeometryForDeletion() }

override fun toString() = "Not implemented"
}
26 changes: 0 additions & 26 deletions src/main/java/net/atos/client/zgw/zrc/model/LocatieZaakPatch.java

This file was deleted.

2 changes: 0 additions & 2 deletions src/main/java/net/atos/client/zgw/zrc/model/Zaak.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.util.UUID;

import jakarta.json.bind.annotation.JsonbDateFormat;
import jakarta.json.bind.annotation.JsonbNillable;
import jakarta.json.bind.annotation.JsonbTransient;

import org.apache.commons.collections4.CollectionUtils;
Expand Down Expand Up @@ -312,7 +311,6 @@ public void setToelichting(final @Length(max = TOELICHTING_MAX_LENGTH) String to
this.toelichting = toelichting;
}

@JsonbNillable
public Geometry getZaakgeometrie() {
return zaakgeometrie;
}
Expand Down
10 changes: 5 additions & 5 deletions src/main/kotlin/net/atos/zac/app/zaak/ZaakRestService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import net.atos.client.zgw.util.extractUuid
import net.atos.client.zgw.zrc.ZrcClientService
import net.atos.client.zgw.zrc.model.AardRelatie
import net.atos.client.zgw.zrc.model.BetrokkeneType
import net.atos.client.zgw.zrc.model.GeometryToBeDeleted
import net.atos.client.zgw.zrc.model.HoofdzaakZaakPatch
import net.atos.client.zgw.zrc.model.LocatieZaakPatch
import net.atos.client.zgw.zrc.model.RelevanteZaak
import net.atos.client.zgw.zrc.model.RelevantezaakZaakPatch
import net.atos.client.zgw.zrc.model.Rol
Expand Down Expand Up @@ -336,12 +336,12 @@ class ZaakRestService @Inject constructor(
restZaakLocatieGegevens: RestZaakLocatieGegevens
): RestZaak {
assertPolicy(policyService.readZaakRechten(zrcClientService.readZaak(zaakUUID)).wijzigen)
val locatieZaakPatch = restZaakLocatieGegevens.geometrie?.let {
LocatieZaakPatch(it.toGeometry())
} ?: LocatieZaakPatch(null)
val zaakPatch = Zaak().apply {
zaakgeometrie = restZaakLocatieGegevens.geometrie?.toGeometry() ?: GeometryToBeDeleted()
}
val updatedZaak = zrcClientService.patchZaak(
zaakUUID,
locatieZaakPatch,
zaakPatch,
restZaakLocatieGegevens.reden
)
return restZaakConverter.toRestZaak(updatedZaak)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2025 Lifely
* SPDX-License-Identifier: EUPL-1.2+
*/
package nl.info.client.zgw.zrc.jsonb

import jakarta.json.bind.serializer.JsonbSerializer
import jakarta.json.bind.serializer.SerializationContext
import jakarta.json.stream.JsonGenerator
import net.atos.client.zgw.shared.util.JsonbUtil
import net.atos.client.zgw.zrc.model.Geometry
import net.atos.client.zgw.zrc.model.GeometryType
import net.atos.client.zgw.zrc.model.Point

/**
* Custom JSONB serializer for [Geometry] objects.
* If [Geometry.markGeometryForDeletion] is set to true a null value is written
* to indicate that the geometry field should be deleted, as per the ZGW ZRC API specification.
*/
class GeometryJsonbSerializer : JsonbSerializer<Geometry> {

override fun serialize(
geometry: Geometry,
jsonGenerator: JsonGenerator,
ctx: SerializationContext
) {
if (geometry.markGeometryForDeletion) {
jsonGenerator.writeNull()
} else {
when (geometry.type) {
GeometryType.POINT -> jsonGenerator.write(JsonbUtil.JSONB.toJson(geometry, Point::class.java))
GeometryType.POLYGON -> throw IllegalArgumentException(
"Polygon serialization is not implemented"
)
GeometryType.GEOMETRYCOLLECTION -> throw IllegalArgumentException(
"GeometryCollection serialization is not implemented"
)
}
}
}
}
12 changes: 12 additions & 0 deletions src/test/kotlin/net/atos/client/zgw/zrc/model/ZrcFixtures.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ fun createOrganisatorischeEenheid(
this.naam = naam
}

fun createPoint(
coordinates: Point2D = createPoint2D()
) = Point(coordinates)

fun createPoint2D(
latitude: Double = 1.23,
longitude: Double = 4.56
) = Point2D(
latitude,
longitude
)

fun createResultaat(
url: URI = URI("http://example.com/resultaat/${UUID.randomUUID()}"),
uuid: UUID = UUID.randomUUID(),
Expand Down
Loading

0 comments on commit d5f471d

Please sign in to comment.