From: Michael Arrastia Date: Mon, 11 Jun 2018 21:02:33 +0000 (+0100) Subject: Fix serialization of update notification payload X-Git-Tag: 1.3.0~24 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=aai%2Fchamp.git;a=commitdiff_plain;h=0edaac90429d84075ee01404166f29c666b3d107 Fix serialization of update notification payload Fix Gson serialization of ChampEventEnvelope: - serialize key as primitive. Currently output as object ({}) with a "value" property. This is due to Optional object declaration and misalignment between Gson serializer and Jackson annotations in ChampObject - serialize transactionId as transaction-id - serialize dbTransactionId as db-transaction-id Change-Id: I3a9824ad376ca8189ba31cc9442cb42bb06a9d70 Issue-ID: AAI-1218 Signed-off-by: Michael Arrastia --- diff --git a/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/ChampEvent.java b/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/ChampEvent.java index e7feeda..a8a8117 100644 --- a/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/ChampEvent.java +++ b/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/ChampEvent.java @@ -33,6 +33,7 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.annotations.SerializedName; public class ChampEvent { @@ -45,12 +46,14 @@ public class ChampEvent { private ChampOperation operation; private long timestamp; + @SerializedName("transaction-id") private String transactionId = null; private ChampObject vertex = null; private ChampRelationship relationship = null; private ChampPartition partition = null; private ChampObjectIndex objectIndex = null; private ChampRelationshipIndex relationshipIndex = null; + @SerializedName("database-transaction-id") private String dbTransactionId = null; diff --git a/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelope.java b/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelope.java index 2547ae5..f4a20a6 100644 --- a/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelope.java +++ b/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelope.java @@ -22,9 +22,9 @@ package org.onap.aai.champcore.event.envelope; import org.onap.aai.champcore.event.ChampEvent; +import org.onap.aai.champcore.event.envelope.util.GsonUtil; import org.onap.aai.champcore.exceptions.ChampUnmarshallingException; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; public class ChampEventEnvelope { @@ -32,9 +32,9 @@ public class ChampEventEnvelope { private ChampEvent body; /** - * Marshaller/unmarshaller for converting to/from JSON. + * Serializer/deserializer for converting to/from JSON. */ - private static final Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + private static final Gson gson = GsonUtil.createGson(); public ChampEventEnvelope(ChampEvent event) { this.header = new ChampEventHeader.Builder(ChampEventHeader.EventType.UPDATE_NOTIFICATION) diff --git a/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/ChampEventHeader.java b/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/ChampEventHeader.java index a9dbdaf..9ff6deb 100644 --- a/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/ChampEventHeader.java +++ b/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/ChampEventHeader.java @@ -27,8 +27,8 @@ import java.time.format.DateTimeFormatter; import java.util.Objects; import java.util.UUID; import org.apache.commons.lang3.builder.EqualsBuilder; +import org.onap.aai.champcore.event.envelope.util.GsonUtil; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; public class ChampEventHeader { @@ -70,7 +70,7 @@ public class ChampEventHeader { @SerializedName("entity-link") private String entityLink; - private static final Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + private static final Gson gson = GsonUtil.createGson(); public static class Builder { diff --git a/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/util/GsonUtil.java b/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/util/GsonUtil.java new file mode 100644 index 0000000..df64040 --- /dev/null +++ b/champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/util/GsonUtil.java @@ -0,0 +1,124 @@ +/** + * ============LICENSE_START========================================== + * org.onap.aai + * =================================================================== + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * =================================================================== + * 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. + * ============LICENSE_END============================================ + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.champcore.event.envelope.util; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Optional; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +public class GsonUtil { + + /** + * All methods are static. + */ + private GsonUtil() { + // Do not instantiate + } + + /** + * Tell Gson how to handle Optional fields. This factory builds a Type Adapter for a class wrapped + * with Optional + * + */ + public static class OptionalTypeAdapterFactory implements TypeAdapterFactory { + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + Class rawType = (Class) type.getRawType(); + if (rawType != Optional.class) { + return null; + } + final ParameterizedType parameterizedType = (ParameterizedType) type.getType(); + final Type actualType = parameterizedType.getActualTypeArguments()[0]; + return new OptionalTypeAdapter(gson.getAdapter(TypeToken.get(actualType))); + } + } + + /** + * Implementation of the Optional Type Adapter + * + * @param + */ + public static class OptionalTypeAdapter extends TypeAdapter> { + + private final TypeAdapter adapter; + + public static final TypeAdapterFactory FACTORY = new OptionalTypeAdapterFactory(); + + /** + * @param adapter + */ + public OptionalTypeAdapter(TypeAdapter adapter) { + this.adapter = adapter; + } + + @Override + public void write(JsonWriter out, Optional value) throws IOException { + if (value != null && value.isPresent()) { // NOSONAR + adapter.write(out, value.get()); + } else { + boolean nullsAllowed = out.getSerializeNulls(); + if (nullsAllowed) { + out.setSerializeNulls(false); + } + out.nullValue(); + if (nullsAllowed) { + out.setSerializeNulls(true); + } + } + } + + @Override + public Optional read(JsonReader in) throws IOException { + final JsonToken peek = in.peek(); + if (peek != JsonToken.NULL) { + return Optional.ofNullable(adapter.read(in)); + } + return Optional.empty(); + } + + } + + /** + * @return a new GsonBuilder with standard settings + */ + public static GsonBuilder createGsonBuilder() { + return new GsonBuilder().disableHtmlEscaping().setPrettyPrinting() + .registerTypeAdapterFactory(OptionalTypeAdapter.FACTORY); + } + + /** + * @return a new Gson instance + */ + public static Gson createGson() { + return createGsonBuilder().create(); + } +} diff --git a/champ-lib/champ-core/src/test/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelopeTest.java b/champ-lib/champ-core/src/test/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelopeTest.java index 6b7bd02..2067ab4 100644 --- a/champ-lib/champ-core/src/test/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelopeTest.java +++ b/champ-lib/champ-core/src/test/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelopeTest.java @@ -35,8 +35,8 @@ import org.skyscreamer.jsonassert.comparator.CustomComparator; public class ChampEventEnvelopeTest { @Test - public void testEventEnvelopeFormat() throws Exception { - String expectedEnvelope = TestUtil.getFileAsString("event/event-envelope.json"); + public void testEventEnvelopeBodyNoKey() throws Exception { + String expectedEnvelope = TestUtil.getFileAsString("event/event-envelope-no-key.json"); ChampEvent body = ChampEvent.builder().entity(new ChampObject.Builder("pserver").build()).build(); @@ -46,9 +46,25 @@ public class ChampEventEnvelopeTest { new CustomComparator(JSONCompareMode.STRICT, new Customization("header.request-id", (o1, o2) -> true), new Customization("header.timestamp", (o1, o2) -> true), new Customization("body.timestamp", (o1, o2) -> true), - new Customization("body.transactionId", (o1, o2) -> true))); + new Customization("body.transaction-id", (o1, o2) -> true))); } + @Test + public void testEventEnvelopeBodyWithKey() throws Exception { + String expectedEnvelope = TestUtil.getFileAsString("event/event-envelope-with-key.json"); + + ChampEvent body = ChampEvent.builder().entity(new ChampObject.Builder("pserver").key("1234").build()).build(); + + String envelope = new ChampEventEnvelope(body).toJson(); + + JSONAssert.assertEquals(expectedEnvelope, envelope, + new CustomComparator(JSONCompareMode.STRICT, new Customization("header.request-id", (o1, o2) -> true), + new Customization("header.timestamp", (o1, o2) -> true), + new Customization("body.timestamp", (o1, o2) -> true), + new Customization("body.transaction-id", (o1, o2) -> true))); + } + + @Test public void testRequestIdIsTransactionId() throws Exception { ChampEvent body = ChampEvent.builder().entity(new ChampObject.Builder("pserver").build()).build(); diff --git a/champ-lib/champ-core/src/test/resources/event/event-envelope.json b/champ-lib/champ-core/src/test/resources/event/event-envelope-no-key.json similarity index 79% rename from champ-lib/champ-core/src/test/resources/event/event-envelope.json rename to champ-lib/champ-core/src/test/resources/event/event-envelope-no-key.json index 0389c4a..f6f072b 100644 --- a/champ-lib/champ-core/src/test/resources/event/event-envelope.json +++ b/champ-lib/champ-core/src/test/resources/event/event-envelope-no-key.json @@ -7,10 +7,9 @@ }, "body": { "timestamp": 1521564357772, - "transactionId": "bffbbf77-e6fc-4c5a-af02-2dbe1bef003f", + "transaction-id": "bffbbf77-e6fc-4c5a-af02-2dbe1bef003f", "vertex": { "type": "pserver", - "key": {}, "properties": {} } } diff --git a/champ-lib/champ-core/src/test/resources/event/event-envelope-with-key.json b/champ-lib/champ-core/src/test/resources/event/event-envelope-with-key.json new file mode 100644 index 0000000..4e1c50a --- /dev/null +++ b/champ-lib/champ-core/src/test/resources/event/event-envelope-with-key.json @@ -0,0 +1,17 @@ +{ + "header": { + "request-id": "bffbbf77-e6fc-4c5a-af02-2dbe1bef003f", + "timestamp": "20180320T164558Z", + "source-name": "CHAMP", + "event-type": "update-notification-raw" + }, + "body": { + "timestamp": 1521564357772, + "transaction-id": "bffbbf77-e6fc-4c5a-af02-2dbe1bef003f", + "vertex": { + "type": "pserver", + "key": "1234", + "properties": {} + } + } +} \ No newline at end of file