Fix serialization of the "key" property 31/55331/1
authorMichael Arrastia <MArrasti@amdocs.com>
Mon, 25 Jun 2018 14:55:54 +0000 (15:55 +0100)
committerMichael Arrastia <MArrasti@amdocs.com>
Mon, 25 Jun 2018 14:55:54 +0000 (15:55 +0100)
The introduction of a payload envelope to the update notification event
issued by champ-core caused the incorrect serialization of the "key"
property as a JSON object instead of a String.
This caused Spike to fail when parsing the notification event from
Champ as it expects "key" to be a String.

A previous fix attempt incorrectly addressed this problem. As the issue
originated from the inconsistent use of both Jackson and Gson JSON
libraries with the envelope using Gson but the contained objects
using Jackson annotations. This fix primarily involves updates to
champ-core to ensure that the envelope generation uses Jackson
instead of Gson and respects the original Jackson serialization of
the "key" property as a String.

Change-Id: I2a12732c9ff3970c3db9de5f0039304e68cb3556
Issue-ID: AAI-1243
Signed-off-by: Michael Arrastia <MArrasti@amdocs.com>
13 files changed:
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 [deleted file]
champ-lib/champ-core/src/test/java/org/onap/aai/champcore/event/envelope/ChampEventEnvelopeTest.java
champ-lib/champ-core/src/test/resources/event/edge-event-envelope.json [new file with mode: 0644]
champ-lib/champ-core/src/test/resources/event/event-envelope-no-key.json [deleted file]
champ-lib/champ-core/src/test/resources/event/event-envelope-with-key.json [deleted file]
champ-lib/champ-core/src/test/resources/event/vertex-event-envelope-no-key.json [new file with mode: 0644]
champ-lib/champ-core/src/test/resources/event/vertex-event-envelope-with-key.json [new file with mode: 0644]
champ-service/src/main/java/org/onap/champ/async/ChampAsyncRequestProcessor.java
champ-service/src/main/java/org/onap/champ/event/envelope/GraphEventEnvelope.java
champ-service/src/main/java/org/onap/champ/event/envelope/GraphEventHeader.java [new file with mode: 0644]

index a8a8117..847f255 100644 (file)
@@ -33,7 +33,6 @@ 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 {
@@ -46,14 +45,12 @@ 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;
 
 
@@ -131,15 +128,11 @@ public class ChampEvent {
         return dbTransactionId;
     }
 
-
     public void setDbTransactionId(String id) {
         this.dbTransactionId = id;
     }
 
-
-
     public String toJson() {
-
         ObjectMapper mapper = new ObjectMapper();
         mapper.setSerializationInclusion(Include.NON_NULL);
 
@@ -151,14 +144,12 @@ public class ChampEvent {
     }
 
     public static ChampEvent fromJson(String json) throws JsonParseException, JsonMappingException, IOException {
-
         mapper.setSerializationInclusion(Include.NON_NULL);
         return mapper.readValue(json, ChampEvent.class);
     }
 
     @Override
     public String toString() {
-
         return toJson();
     }
 
@@ -166,7 +157,6 @@ public class ChampEvent {
 
         ChampEvent event = null;
 
-
         public Builder() {
             event = new ChampEvent();
         }
@@ -201,9 +191,7 @@ public class ChampEvent {
             return this;
         }
 
-
         public ChampEvent build() {
-
             event.setTimestamp(System.currentTimeMillis());
 
             // Set a unique transaction id on this event that can be used by downstream entities
index f4a20a6..0dbe1d9 100644 (file)
 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.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 
 public class ChampEventEnvelope {
 
     private ChampEventHeader header;
     private ChampEvent body;
 
-    /**
-     * Serializer/deserializer for converting to/from JSON.
-     */
-    private static final Gson gson = GsonUtil.createGson();
-
     public ChampEventEnvelope(ChampEvent event) {
         this.header = new ChampEventHeader.Builder(ChampEventHeader.EventType.UPDATE_NOTIFICATION)
                 .requestId(event.getTransactionId()).build();
@@ -69,24 +64,13 @@ public class ChampEventEnvelope {
      * @return - A JSON format string representation of this Vertex.
      */
     public String toJson() {
-        return gson.toJson(this);
-    }
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setSerializationInclusion(Include.NON_NULL);
 
-    /**
-     * Deserializes the provided JSON string into a Event Envelope object.
-     *
-     * @param json the JSON string to produce the Event Envelope from.
-     * @return an Event Envelope object.
-     * @throws ChampUnmarshallingException
-     */
-    public static ChampEventEnvelope fromJson(String json) throws ChampUnmarshallingException {
         try {
-            if (json == null || json.isEmpty()) {
-                throw new ChampUnmarshallingException("Empty or null JSON string.");
-            }
-            return gson.fromJson(json, ChampEventEnvelope.class);
-        } catch (Exception ex) {
-            throw new ChampUnmarshallingException("Unable to parse JSON string: ");
+            return mapper.writeValueAsString(this);
+        } catch (JsonProcessingException e) {
+            return "Unmarshallable: " + e.getMessage();
         }
     }
 
index 9ff6deb..3e19aa5 100644 (file)
@@ -27,9 +27,10 @@ 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.annotations.SerializedName;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 
 public class ChampEventHeader {
 
@@ -50,28 +51,26 @@ public class ChampEventHeader {
         }
     }
 
-    @SerializedName("request-id")
+    @JsonProperty("request-id")
     private String requestId;
 
     private String timestamp;
 
-    @SerializedName("source-name")
+    @JsonProperty("source-name")
     private String sourceName;
 
-    @SerializedName("event-type")
+    @JsonProperty("event-type")
     private String eventType;
 
-    @SerializedName("validation-entity-type")
+    @JsonProperty("validation-entity-type")
     private String validationEntityType;
 
-    @SerializedName("validation-top-entity-type")
+    @JsonProperty("validation-top-entity-type")
     private String validationTopEntityType;
 
-    @SerializedName("entity-link")
+    @JsonProperty("entity-link")
     private String entityLink;
 
-    private static final Gson gson = GsonUtil.createGson();
-
     public static class Builder {
 
         private final EventType eventType;
@@ -127,7 +126,14 @@ public class ChampEventHeader {
      * @return a JSON format string representation of this object.
      */
     public String toJson() {
-        return gson.toJson(this);
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setSerializationInclusion(Include.NON_NULL);
+
+        try {
+            return mapper.writeValueAsString(this);
+        } catch (JsonProcessingException e) {
+            return "Unmarshallable: " + e.getMessage();
+        }
     }
 
     ///////////////////////////////////////////////////////////////////////////
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
deleted file mode 100644 (file)
index df64040..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-/**
- * ============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 2067ab4..56cf8ea 100644 (file)
@@ -26,17 +26,20 @@ import static org.junit.Assert.assertThat;
 import org.junit.Test;
 import org.onap.aai.champcore.event.ChampEvent;
 import org.onap.aai.champcore.model.ChampObject;
+import org.onap.aai.champcore.model.ChampRelationship;
 import org.onap.aai.champcore.util.TestUtil;
 import org.skyscreamer.jsonassert.Customization;
 import org.skyscreamer.jsonassert.JSONAssert;
 import org.skyscreamer.jsonassert.JSONCompareMode;
 import org.skyscreamer.jsonassert.comparator.CustomComparator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 
 public class ChampEventEnvelopeTest {
 
     @Test
-    public void testEventEnvelopeBodyNoKey() throws Exception {
-        String expectedEnvelope = TestUtil.getFileAsString("event/event-envelope-no-key.json");
+    public void testVertexEventEnvelopeBodyNoKey() throws Exception {
+        String expectedEnvelope = TestUtil.getFileAsString("event/vertex-event-envelope-no-key.json");
 
         ChampEvent body = ChampEvent.builder().entity(new ChampObject.Builder("pserver").build()).build();
 
@@ -50,8 +53,8 @@ public class ChampEventEnvelopeTest {
     }
 
     @Test
-    public void testEventEnvelopeBodyWithKey() throws Exception {
-        String expectedEnvelope = TestUtil.getFileAsString("event/event-envelope-with-key.json");
+    public void testVertexEventEnvelopeBodyWithKey() throws Exception {
+        String expectedEnvelope = TestUtil.getFileAsString("event/vertex-event-envelope-with-key.json");
 
         ChampEvent body = ChampEvent.builder().entity(new ChampObject.Builder("pserver").key("1234").build()).build();
 
@@ -73,4 +76,29 @@ public class ChampEventEnvelopeTest {
 
         assertThat(envelope.getHeader().getRequestId(), is(envelope.getBody().getTransactionId()));
     }
+
+    @Test
+    public void testEdgeEventEnvelope() throws Exception {
+        String expectedEnvelope = TestUtil.getFileAsString("event/edge-event-envelope.json");
+
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode objectNode = mapper.createObjectNode();
+        objectNode.put("inVertexId", 5678);
+        objectNode.put("typeId", 1000);
+        objectNode.put("relationId", 2000);
+        objectNode.put("outVertexId", 1234);
+
+        ChampRelationship relationship =
+                new ChampRelationship.Builder(new ChampObject.Builder("vserver").key("1234").build(),
+                        new ChampObject.Builder("pserver").key("5678").build(), "test").key(objectNode).build();
+        ChampEvent body = ChampEvent.builder().entity(relationship).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)));
+    }
 }
diff --git a/champ-lib/champ-core/src/test/resources/event/edge-event-envelope.json b/champ-lib/champ-core/src/test/resources/event/edge-event-envelope.json
new file mode 100644 (file)
index 0000000..617bb9b
--- /dev/null
@@ -0,0 +1,33 @@
+{
+    "header": {
+        "timestamp": "20180625T131609Z",
+        "request-id": "ad65331f-932e-4cce-acc5-97ad88ce0483",
+        "source-name": "CHAMP",
+        "event-type": "update-notification-raw"
+    },
+    "body": {
+        "timestamp": 1529932568889,
+        "relationship": {
+            "type": "test",
+            "properties": {},
+            "source": {
+                "type": "vserver",
+                "properties": {},
+                "object": true,
+                "relationship": false,
+                "key": "1234"
+            },
+            "target": {
+                "type": "pserver",
+                "properties": {},
+                "object": true,
+                "relationship": false,
+                "key": "5678"
+            },
+            "object": false,
+            "relationship": true,
+            "key": "{\"inVertexId\":5678,\"typeId\":1000,\"relationId\":2000,\"outVertexId\":1234}"
+        },
+        "transaction-id": "ad65331f-932e-4cce-acc5-97ad88ce0483"
+    }
+}
\ No newline at end of file
diff --git a/champ-lib/champ-core/src/test/resources/event/event-envelope-no-key.json b/champ-lib/champ-core/src/test/resources/event/event-envelope-no-key.json
deleted file mode 100644 (file)
index f6f072b..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "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",
-      "properties": {}
-    }
-  }
-}
\ No newline at end of file
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
deleted file mode 100644 (file)
index 4e1c50a..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-  "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
diff --git a/champ-lib/champ-core/src/test/resources/event/vertex-event-envelope-no-key.json b/champ-lib/champ-core/src/test/resources/event/vertex-event-envelope-no-key.json
new file mode 100644 (file)
index 0000000..1935b07
--- /dev/null
@@ -0,0 +1,19 @@
+{
+    "header": {
+        "request-id": "d9b95d97-ac49-4425-8700-a704fa9fc88b",
+        "timestamp": "20180625T100903Z",
+        "source-name": "CHAMP",
+        "event-type": "update-notification-raw"
+    },
+    "body": {
+        "timestamp": 1529921297261,
+        "vertex": {
+            "type": "pserver",
+            "properties": {},
+            "object": true,
+            "relationship": false,
+            "key": ""
+        },
+        "transaction-id": "d9b95d97-ac49-4425-8700-a704fa9fc88b"
+    }
+}
\ No newline at end of file
diff --git a/champ-lib/champ-core/src/test/resources/event/vertex-event-envelope-with-key.json b/champ-lib/champ-core/src/test/resources/event/vertex-event-envelope-with-key.json
new file mode 100644 (file)
index 0000000..87e4ae1
--- /dev/null
@@ -0,0 +1,19 @@
+{
+    "header": {
+        "timestamp": "20180625T105153Z",
+        "request-id": "013c787f-86e1-42d2-a4f7-e736ed7e6c60",
+        "source-name": "CHAMP",
+        "event-type": "update-notification-raw"
+    },
+    "body": {
+        "timestamp": 1529923913318,
+        "vertex": {
+            "type": "pserver",
+            "properties": {},
+            "object": true,
+            "relationship": false,
+            "key": "1234"
+        },
+        "transaction-id": "013c787f-86e1-42d2-a4f7-e736ed7e6c60"
+    }
+}
\ No newline at end of file
index 3b13a42..4966287 100644 (file)
@@ -29,7 +29,6 @@ import java.util.concurrent.ThreadPoolExecutor;
 import javax.naming.OperationNotSupportedException;
 import javax.ws.rs.core.Response.Status;
 import org.onap.aai.champcore.ChampTransaction;
-import org.onap.aai.champcore.event.envelope.ChampEventHeader;
 import org.onap.aai.cl.api.Logger;
 import org.onap.aai.cl.eelf.LoggerFactory;
 import org.onap.aai.event.api.EventConsumer;
@@ -39,6 +38,7 @@ import org.onap.champ.event.GraphEvent.GraphEventResult;
 import org.onap.champ.event.GraphEventEdge;
 import org.onap.champ.event.GraphEventVertex;
 import org.onap.champ.event.envelope.GraphEventEnvelope;
+import org.onap.champ.event.envelope.GraphEventHeader;
 import org.onap.champ.exception.ChampServiceException;
 import org.onap.champ.service.ChampDataService;
 import org.onap.champ.service.ChampThreadFactory;
@@ -159,7 +159,7 @@ public class ChampAsyncRequestProcessor extends TimerTask {
                 }
 
                 // Apply Champ Event header
-                eventEnvelope.setHeader(new ChampEventHeader.Builder(ChampEventHeader.EventType.UPDATE_RESULT).requestId(event.getTransactionId()).build());
+                eventEnvelope.setHeader(new GraphEventHeader.Builder().requestId(event.getTransactionId()).build());
 
                 // Parse the event and call champ Dao to process , Create the
                 // response event and put it on response queue
index 13e0f7a..7958a3a 100644 (file)
@@ -22,7 +22,6 @@
 package org.onap.champ.event.envelope;
 
 import javax.ws.rs.core.Response.Status;
-import org.onap.aai.champcore.event.envelope.ChampEventHeader;
 import org.onap.champ.event.GraphEvent;
 import org.onap.champ.exception.ChampServiceException;
 import com.google.gson.Gson;
@@ -30,7 +29,7 @@ import com.google.gson.GsonBuilder;
 
 public class GraphEventEnvelope {
 
-    private ChampEventHeader header;
+    private GraphEventHeader header;
     private GraphEvent body;
 
     /**
@@ -39,21 +38,20 @@ public class GraphEventEnvelope {
     private static final Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
 
     public GraphEventEnvelope(GraphEvent event) {
-        this.header = new ChampEventHeader.Builder(ChampEventHeader.EventType.UPDATE_RESULT)
-                .requestId(event.getTransactionId()).build();
+        this.header = new GraphEventHeader.Builder().requestId(event.getTransactionId()).build();
         this.body = event;
     }
 
-    public GraphEventEnvelope(ChampEventHeader header, GraphEvent body) {
+    public GraphEventEnvelope(GraphEventHeader header, GraphEvent body) {
         this.header = header;
         this.body = body;
     }
 
-    public ChampEventHeader getHeader() {
+    public GraphEventHeader getHeader() {
         return header;
     }
 
-    public void setHeader(ChampEventHeader header) {
+    public void setHeader(GraphEventHeader header) {
         this.header = header;
     }
 
diff --git a/champ-service/src/main/java/org/onap/champ/event/envelope/GraphEventHeader.java b/champ-service/src/main/java/org/onap/champ/event/envelope/GraphEventHeader.java
new file mode 100644 (file)
index 0000000..ae9fe53
--- /dev/null
@@ -0,0 +1,228 @@
+/**
+ * ============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.champ.event.envelope;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.SerializedName;
+
+public class GraphEventHeader {
+
+    private static final String SOURCE_NAME = "CHAMP";
+
+    private static final String EVENT_TYPE = "update-result";
+
+    @SerializedName("request-id")
+    private String requestId;
+
+    private String timestamp;
+
+    @SerializedName("source-name")
+    private String sourceName;
+
+    @SerializedName("event-type")
+    private String eventType;
+
+    @SerializedName("validation-entity-type")
+    private String validationEntityType;
+
+    @SerializedName("validation-top-entity-type")
+    private String validationTopEntityType;
+
+    @SerializedName("entity-link")
+    private String entityLink;
+
+    private static final Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+
+        private String requestId;
+        private String validationEntityType;
+        private String validationTopEntityType;
+        private String entityLink;
+
+        public Builder requestId(String val) {
+            requestId = val;
+            return this;
+        }
+
+        public Builder validationEntityType(String val) {
+            validationEntityType = val;
+            return this;
+        }
+
+        public Builder validationTopEntityType(String val) {
+            validationTopEntityType = val;
+            return this;
+        }
+
+        public Builder entityLink(String val) {
+            entityLink = val;
+            return this;
+        }
+
+        public GraphEventHeader build() {
+            return new GraphEventHeader(this);
+        }
+    }
+
+    private GraphEventHeader(Builder builder) {
+        requestId = builder.requestId != null ? builder.requestId : UUID.randomUUID().toString();
+        timestamp = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmssX").withZone(ZoneOffset.UTC).format(Instant.now());
+        sourceName = SOURCE_NAME;
+        eventType = EVENT_TYPE;
+
+        validationEntityType = builder.validationEntityType;
+        validationTopEntityType = builder.validationTopEntityType;
+        entityLink = builder.entityLink;
+    }
+
+    /**
+     * Serializes this object into a JSON string representation.
+     *
+     * @return a JSON format string representation of this object.
+     */
+    public String toJson() {
+        return gson.toJson(this);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // GETTERS AND SETTERS
+    ///////////////////////////////////////////////////////////////////////////
+
+    public String getRequestId() {
+        return requestId;
+    }
+
+    public void setRequestId(String requestId) {
+        this.requestId = requestId;
+    }
+
+    public String getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(String timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public String getSourceName() {
+        return sourceName;
+    }
+
+    public void setSourceName(String sourceName) {
+        this.sourceName = sourceName;
+    }
+
+    public String getEventType() {
+        return eventType;
+    }
+
+    public void setEventType(String eventType) {
+        this.eventType = eventType;
+    }
+
+    public String getValidationEntityType() {
+        return validationEntityType;
+    }
+
+    public void setValidationEntityType(String validationEntityType) {
+        this.validationEntityType = validationEntityType;
+    }
+
+    public String getValidationTopEntityType() {
+        return validationTopEntityType;
+    }
+
+    public void setValidationTopEntityType(String validationTopEntityType) {
+        this.validationTopEntityType = validationTopEntityType;
+    }
+
+    public String getEntityLink() {
+        return entityLink;
+    }
+
+    public void setEntityLink(String entityLink) {
+        this.entityLink = entityLink;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // OVERRIDES
+    ///////////////////////////////////////////////////////////////////////////
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.requestId, this.timestamp, this.sourceName, this.eventType, this.validationEntityType,
+                this.validationTopEntityType, this.entityLink);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof GraphEventHeader)) {
+            return false;
+        } else if (obj == this) {
+            return true;
+        }
+        GraphEventHeader rhs = (GraphEventHeader) obj;
+     // @formatter:off
+     return new EqualsBuilder()
+                  .append(requestId, rhs.requestId)
+                  .append(timestamp, rhs.timestamp)
+                  .append(sourceName, rhs.sourceName)
+                  .append(eventType, rhs.eventType)
+                  .append(validationEntityType, rhs.validationEntityType)
+                  .append(validationTopEntityType, rhs.validationTopEntityType)
+                  .append(entityLink, rhs.entityLink)
+                  .isEquals();
+     // @formatter:on
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return this.toJson();
+    }
+}