ATLAS-4067: Updates in Custom Attributes and Business Attributes must be captured separately in entity audits.

Signed-off-by: Ashutosh Mestry <amestry@cloudera.com>
diff --git a/dashboardv2/public/js/utils/Enums.js b/dashboardv2/public/js/utils/Enums.js
index ebfceba..d2e476d 100644
--- a/dashboardv2/public/js/utils/Enums.js
+++ b/dashboardv2/public/js/utils/Enums.js
@@ -42,6 +42,7 @@
         BUSINESS_ATTRIBUTE_ADD: "Business Attribute(s) Added",
         BUSINESS_ATTRIBUTE_UPDATE: "Business Attribute(s) Updated",
         BUSINESS_ATTRIBUTE_DELETE: "Business Attribute(s) Deleted",
+        CUSTOM_ATTRIBUTE_UPDATE: "User-defined Attribute(s) Updated",
         TYPE_DEF_UPDATE: "Type Updated",
         TYPE_DEF_CREATE: "Type Created",
         TYPE_DEF_DELETE: "Type Deleted",
diff --git a/dashboardv3/public/js/utils/Enums.js b/dashboardv3/public/js/utils/Enums.js
index ebfceba..d2e476d 100644
--- a/dashboardv3/public/js/utils/Enums.js
+++ b/dashboardv3/public/js/utils/Enums.js
@@ -42,6 +42,7 @@
         BUSINESS_ATTRIBUTE_ADD: "Business Attribute(s) Added",
         BUSINESS_ATTRIBUTE_UPDATE: "Business Attribute(s) Updated",
         BUSINESS_ATTRIBUTE_DELETE: "Business Attribute(s) Deleted",
+        CUSTOM_ATTRIBUTE_UPDATE: "User-defined Attribute(s) Updated",
         TYPE_DEF_UPDATE: "Type Updated",
         TYPE_DEF_CREATE: "Type Created",
         TYPE_DEF_DELETE: "Type Deleted",
diff --git a/intg/src/main/java/org/apache/atlas/exception/AtlasBaseException.java b/intg/src/main/java/org/apache/atlas/exception/AtlasBaseException.java
index 5538702..88a26f4 100644
--- a/intg/src/main/java/org/apache/atlas/exception/AtlasBaseException.java
+++ b/intg/src/main/java/org/apache/atlas/exception/AtlasBaseException.java
@@ -19,7 +19,6 @@
 
 import org.apache.atlas.AtlasErrorCode;
 
-import javax.ws.rs.core.Response;
 import java.util.List;
 
 /**
diff --git a/intg/src/main/java/org/apache/atlas/model/audit/EntityAuditEventV2.java b/intg/src/main/java/org/apache/atlas/model/audit/EntityAuditEventV2.java
index c37e282..083acac 100644
--- a/intg/src/main/java/org/apache/atlas/model/audit/EntityAuditEventV2.java
+++ b/intg/src/main/java/org/apache/atlas/model/audit/EntityAuditEventV2.java
@@ -54,7 +54,7 @@
         CLASSIFICATION_ADD, CLASSIFICATION_DELETE, CLASSIFICATION_UPDATE,
         PROPAGATED_CLASSIFICATION_ADD, PROPAGATED_CLASSIFICATION_DELETE, PROPAGATED_CLASSIFICATION_UPDATE,
         TERM_ADD, TERM_DELETE, LABEL_ADD, LABEL_DELETE, ENTITY_PURGE,
-        BUSINESS_ATTRIBUTE_UPDATE;
+        BUSINESS_ATTRIBUTE_UPDATE, CUSTOM_ATTRIBUTE_UPDATE;
 
         public static EntityAuditActionV2 fromString(String strValue) {
             switch (strValue) {
@@ -97,6 +97,8 @@
                     return LABEL_DELETE;
                 case "BUSINESS_ATTRIBUTE_UPDATE":
                     return BUSINESS_ATTRIBUTE_UPDATE;
+                case "CUSTOM_ATTRIBUTE_UPDATE":
+                    return CUSTOM_ATTRIBUTE_UPDATE;
             }
 
             throw new IllegalArgumentException("No enum constant " + EntityAuditActionV2.class.getCanonicalName() + "." + strValue);
diff --git a/repository/src/main/java/org/apache/atlas/repository/audit/EntityAuditListenerV2.java b/repository/src/main/java/org/apache/atlas/repository/audit/EntityAuditListenerV2.java
index ca6f373..0043f1c 100644
--- a/repository/src/main/java/org/apache/atlas/repository/audit/EntityAuditListenerV2.java
+++ b/repository/src/main/java/org/apache/atlas/repository/audit/EntityAuditListenerV2.java
@@ -63,6 +63,7 @@
 import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.ENTITY_IMPORT_DELETE;
 import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.ENTITY_IMPORT_UPDATE;
 import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.ENTITY_UPDATE;
+import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.CUSTOM_ATTRIBUTE_UPDATE;
 import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.LABEL_ADD;
 import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.LABEL_DELETE;
 import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.PROPAGATED_CLASSIFICATION_ADD;
@@ -109,7 +110,11 @@
 
         FixedBufferList<EntityAuditEventV2> updatedEvents = getAuditEventsList();
         for (AtlasEntity entity : entities) {
-            createEvent(updatedEvents.next(), entity, isImport ? ENTITY_IMPORT_UPDATE : ENTITY_UPDATE);
+            EntityAuditActionV2 action = isImport ? ENTITY_IMPORT_UPDATE :
+                    RequestContext.get().checkIfEntityIsForCustomAttributeUpdate(entity.getGuid()) ? CUSTOM_ATTRIBUTE_UPDATE :
+                    RequestContext.get().checkIfEntityIsForBusinessAttributeUpdate(entity.getGuid()) ? BUSINESS_ATTRIBUTE_UPDATE :
+                            ENTITY_UPDATE;
+            createEvent(updatedEvents.next(), entity, action);
         }
 
         auditRepository.putEventsV2(updatedEvents.toList());
@@ -579,6 +584,12 @@
             case ENTITY_UPDATE:
                 ret = "Updated: ";
                 break;
+            case CUSTOM_ATTRIBUTE_UPDATE:
+                ret = "Updated custom attribute: ";
+                break;
+            case BUSINESS_ATTRIBUTE_UPDATE:
+                ret = "Updated business attribute: ";
+                break;
             case ENTITY_DELETE:
                 ret = "Deleted: ";
                 break;
diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasEntityStoreV2.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasEntityStoreV2.java
index b80e42e..2440722 100644
--- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasEntityStoreV2.java
+++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasEntityStoreV2.java
@@ -1149,7 +1149,7 @@
                     String          guid       = entity.getGuid();
                     AtlasVertex     vertex     = context.getVertex(guid);
                     AtlasEntityType entityType = typeRegistry.getEntityTypeByName(entity.getTypeName());
-                    boolean         hasUpdates = false;
+                    boolean         hasUpdates = false, hasUpdatesInCustAttr = false, hasUpdatesInBusAttr = false;
 
                     if (!hasUpdates) {
                         hasUpdates = entity.getStatus() == AtlasEntity.Status.DELETED; // entity status could be updated during import
@@ -1199,21 +1199,21 @@
                         }
                     }
 
-                    if (!hasUpdates && entity.getCustomAttributes() != null) {
+                    if (entity.getCustomAttributes() != null) {
                         Map<String, String> currCustomAttributes = getCustomAttributes(vertex);
                         Map<String, String> newCustomAttributes  = entity.getCustomAttributes();
 
                         if (!Objects.equals(currCustomAttributes, newCustomAttributes)) {
-                            hasUpdates = true;
+                            hasUpdatesInCustAttr = true;
                         }
                     }
 
-                    if (!hasUpdates && replaceBusinessAttributes) {
+                    if (replaceBusinessAttributes) {
                         Map<String, Map<String, Object>> currBusinessMetadata = entityRetriever.getBusinessMetadata(vertex);
                         Map<String, Map<String, Object>> newBusinessMetadata  = entity.getBusinessAttributes();
 
                         if (!Objects.equals(currBusinessMetadata, newBusinessMetadata)) {
-                            hasUpdates = true;
+                            hasUpdatesInBusAttr = true;
                         }
                     }
 
@@ -1231,6 +1231,14 @@
                         }
                     }
 
+                    if(!hasUpdates && (hasUpdatesInCustAttr || hasUpdatesInBusAttr)) {
+                        hasUpdates = true;
+                        if (hasUpdatesInCustAttr ^ hasUpdatesInBusAttr) {
+                            if(hasUpdatesInCustAttr) RequestContext.get().recordEntityWithCustomAttributeUpdate(entity.getGuid());
+                            if(hasUpdatesInBusAttr) RequestContext.get().recordEntityWithBusinessAttributeUpdate(entity.getGuid());
+                        }
+                    }
+
                     if (!hasUpdates) {
                         if (entitiesToSkipUpdate == null) {
                             entitiesToSkipUpdate = new ArrayList<>();
diff --git a/server-api/src/main/java/org/apache/atlas/RequestContext.java b/server-api/src/main/java/org/apache/atlas/RequestContext.java
index 7f0cfe5..216ba08 100644
--- a/server-api/src/main/java/org/apache/atlas/RequestContext.java
+++ b/server-api/src/main/java/org/apache/atlas/RequestContext.java
@@ -58,6 +58,8 @@
     private final AtlasPerfMetrics                       metrics              = isMetricsEnabled ? new AtlasPerfMetrics() : null;
     private       List<EntityGuidPair>                   entityGuidInRequest  = null;
     private final Set<String>                            entitiesToSkipUpdate = new HashSet<>();
+    private final Set<String>                            onlyCAUpdateEntities = new HashSet<>();
+    private final Set<String>                            onlyBAUpdateEntities = new HashSet<>();
 
     private String       user;
     private Set<String>  userGroups;
@@ -115,6 +117,8 @@
         this.addedPropagations.clear();
         this.removedPropagations.clear();
         this.entitiesToSkipUpdate.clear();
+        this.onlyCAUpdateEntities.clear();
+        this.onlyBAUpdateEntities.clear();
 
         if (metrics != null && !metrics.isEmpty()) {
             METRICS.debug(metrics.toString());
@@ -243,6 +247,26 @@
         }
     }
 
+    public void recordEntityWithCustomAttributeUpdate(String guid) {
+        if(! StringUtils.isEmpty(guid)) {
+            onlyCAUpdateEntities.add(guid);
+        }
+    }
+
+    public void recordEntityWithBusinessAttributeUpdate(String guid) {
+        if(! StringUtils.isEmpty(guid)) {
+            onlyBAUpdateEntities.add(guid);
+        }
+    }
+
+    public boolean checkIfEntityIsForCustomAttributeUpdate(String guid) {
+        return StringUtils.isNotEmpty(guid) && onlyCAUpdateEntities.contains(guid);
+    }
+
+    public boolean checkIfEntityIsForBusinessAttributeUpdate(String guid) {
+        return StringUtils.isNotEmpty(guid) && onlyBAUpdateEntities.contains(guid);
+    }
+
     public void recordEntityDelete(AtlasEntityHeader entity) {
         if (entity != null && entity.getGuid() != null) {
             deletedEntities.put(entity.getGuid(), entity);