Fix serialization of update notification payload 69/51169/1
authorMichael Arrastia <MArrasti@amdocs.com>
Mon, 11 Jun 2018 21:02:33 +0000 (22:02 +0100)
committerMichael Arrastia <MArrasti@amdocs.com>
Mon, 11 Jun 2018 21:02:33 +0000 (22:02 +0100)
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 <MArrasti@amdocs.com>
champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/ChampEvent.java
champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelope.java
champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/ChampEventHeader.java
champ-lib/champ-core/src/main/java/org/onap/aai/champcore/event/envelope/util/GsonUtil.java [new file with mode: 0644]
champ-lib/champ-core/src/test/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelopeTest.java
champ-lib/champ-core/src/test/resources/event/event-envelope-no-key.json [moved from champ-lib/champ-core/src/test/resources/event/event-envelope.json with 79% similarity]
champ-lib/champ-core/src/test/resources/event/event-envelope-with-key.json [new file with mode: 0644]

index e7feeda..a8a8117 100644 (file)
@@ -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;
 
 
index 2547ae5..f4a20a6 100644 (file)
@@ -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)
index a9dbdaf..9ff6deb 100644 (file)
@@ -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 (file)
index 0000000..df64040
--- /dev/null
@@ -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 <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+            Class<T> rawType = (Class<T>) 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 <E>
+     */
+    public static class OptionalTypeAdapter<E> extends TypeAdapter<Optional<E>> {
+
+        private final TypeAdapter<E> adapter;
+
+        public static final TypeAdapterFactory FACTORY = new OptionalTypeAdapterFactory();
+
+        /**
+         * @param adapter
+         */
+        public OptionalTypeAdapter(TypeAdapter<E> adapter) {
+            this.adapter = adapter;
+        }
+
+        @Override
+        public void write(JsonWriter out, Optional<E> 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<E> 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();
+    }
+}
index 6b7bd02..2067ab4 100644 (file)
@@ -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();
@@ -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 (file)
index 0000000..4e1c50a
--- /dev/null
@@ -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