You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@atlas.apache.org by ma...@apache.org on 2020/02/07 06:15:55 UTC

[atlas] branch master updated: ATLAS-3534: enhancements to support add/update/delete namespace-attributes on entities

This is an automated email from the ASF dual-hosted git repository.

madhan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/atlas.git


The following commit(s) were added to refs/heads/master by this push:
     new d3a08b6  ATLAS-3534: enhancements to support add/update/delete namespace-attributes on entities
d3a08b6 is described below

commit d3a08b6eabcc50536c468db2d1328df54ee27fce
Author: Mandar Ambawane <ma...@freestoneinfotech.com>
AuthorDate: Thu Feb 6 14:45:49 2020 +0530

    ATLAS-3534: enhancements to support add/update/delete namespace-attributes on entities
    
    Signed-off-by: Madhan Neethiraj <ma...@apache.org>
---
 .../org/apache/atlas/repository/Constants.java     |   3 +-
 .../main/java/org/apache/atlas/AtlasErrorCode.java |  41 +++---
 .../apache/atlas/model/instance/AtlasEntity.java   |  56 +++++++-
 .../org/apache/atlas/type/AtlasNamespaceType.java  |  17 +--
 .../org/apache/atlas/type/AtlasTypeRegistry.java   |   1 +
 .../repository/store/graph/AtlasEntityStore.java   |  17 +++
 .../store/graph/v2/AtlasEntityStoreV2.java         | 122 ++++++++++++++++-
 .../store/graph/v2/EntityGraphMapper.java          | 146 ++++++++++++++++++++-
 .../store/graph/v2/EntityGraphRetriever.java       |  23 ++++
 .../java/org/apache/atlas/web/rest/EntityREST.java |  73 +++++++++++
 10 files changed, 455 insertions(+), 44 deletions(-)

diff --git a/common/src/main/java/org/apache/atlas/repository/Constants.java b/common/src/main/java/org/apache/atlas/repository/Constants.java
index 0b28243..7c0fd56 100644
--- a/common/src/main/java/org/apache/atlas/repository/Constants.java
+++ b/common/src/main/java/org/apache/atlas/repository/Constants.java
@@ -88,14 +88,13 @@ public final class Constants {
     public static final String VERSION_PROPERTY_KEY                 = encodePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "version");
     public static final String STATE_PROPERTY_KEY                   = encodePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "state");
     public static final String CREATED_BY_KEY                       = encodePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "createdBy");
+    public static final String MODIFIED_BY_KEY                      = encodePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "modifiedBy");
     public static final String CLASSIFICATION_TEXT_KEY              = encodePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "classificationsText");
     public static final String CLASSIFICATION_NAMES_KEY             = encodePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "classificationNames");
     public static final String PROPAGATED_CLASSIFICATION_NAMES_KEY  = encodePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "propagatedClassificationNames");
     public static final String CUSTOM_ATTRIBUTES_PROPERTY_KEY       = encodePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "customAttributes");
     public static final String LABELS_PROPERTY_KEY                  = encodePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "labels");
 
-    public static final String MODIFIED_BY_KEY                      = encodePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "modifiedBy");
-
     /**
      * Patch vertices property keys.
      */
diff --git a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java
index 2054513..1670bda 100644
--- a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java
+++ b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java
@@ -147,25 +147,28 @@ public enum AtlasErrorCode {
     ATTRIBUTE_TYPE_INVALID(400, "ATLAS-400-00-081", "{0}.{1}: invalid attribute type. Attribute cannot be of type classification"),
     MISSING_CATEGORY_DISPLAY_NAME(400, "ATLAS-400-00-082", "Category name is empty/null"),
     INVALID_DISPLAY_NAME(400, "ATLAS-400-00-083", "name cannot contain following special chars ('@', '.')"),
-    TERM_HAS_ENTITY_ASSOCIATION(400, "ATLAS-400-00-086", "Term (guid={0}) can't be deleted as it has been assigned to {1} entities."),
-    INVALID_TIMEBOUNDRY_TIMEZONE(400, "ATLAS-400-00-87A", "Invalid timezone {0}"),
-    INVALID_TIMEBOUNDRY_START_TIME(400, "ATLAS-400-00-87B", "Invalid startTime {0}"),
-    INVALID_TIMEBOUNDRY_END_TIME(400, "ATLAS-400-00-87C", "Invalid endTime {0}"),
-    INVALID_TIMEBOUNDRY_DATERANGE(400, "ATLAS-400-00-87D", "Invalid dateRange: startTime {0} must be before endTime {1}"),
-    PROPAGATED_CLASSIFICATION_REMOVAL_NOT_SUPPORTED(400, "ATLAS-400-00-87E", "Removal of classification {0}, which is propagated from entity {1}, is not supported"),
-    IMPORT_ATTEMPTING_EMPTY_ZIP(400, "ATLAS-400-00-87F", "Attempting to import empty ZIP file."),
-    PATCH_MISSING_RELATIONSHIP_LABEL(400, "ATLAS-400-00-88", "{0} - must include relationship label for type {1}"),
-    INVALID_CUSTOM_ATTRIBUTE_KEY_LENGTH(400, "ATLAS-400-00-89", "Invalid key: {0} in custom attribute, key size should not be greater than 50"),
-    INVALID_CUSTOM_ATTRIBUTE_KEY_CHARACTERS(400, "ATLAS-400-00-90", "Invalid key: {0} in custom attribute, key should only contain alphanumeric characters, '_' or '-'"),
-    INVALID_CUSTOM_ATTRIBUTE_VALUE(400, "ATLAS-400-00-9A", "Invalid value: {0} in custom attribute, value length is greater than {1}"),
-    INVALID_LABEL_LENGTH(400, "ATLAS-400-00-9B", "Invalid label: {0}, label size should not be greater than {1}"),
-    INVALID_LABEL_CHARACTERS(400, "ATLAS-400-00-9C", "Invalid label: {0}, label should contain alphanumeric characters, '_' or '-'"),
-    INVALID_PROPAGATION_TYPE(400, "ATLAS-400-00-9D", "Invalid propagation {0} for relationship-type={1}. Default value is {2}"),
-    DUPLICATE_NAMESPACE_ATTRIBUTE(400, "ATLAS-400-00-094", "Duplicate Namespace Attributes: {0} not allowed within the same namespace: {1}"),
-    APPLICABLE_ENTITY_TYPES_DELETION_NOT_SUPPORTED(400, "ATLAS-400-00-095", "Cannot remove applicableEntityTypes in Attribute Def: {1}, defined in namespace: {2}"),
-    NAMESPACE_DEF_MANDATORY_ATTRIBUTE_NOT_ALLOWED(400, "ATLAS-400-00-096", "{0}.{1} : namespaces can not have mandatory attribute"),
-    NAMESPACE_DEF_UNIQUE_ATTRIBUTE_NOT_ALLOWED(400, "ATLAS-400-00-097", "{0}.{1} : namespaces can not have unique attribute"),
-    NAMESPACE_DEF_ATTRIBUTE_TYPE_INVALID(400, "ATLAS-400-00-098", "{0}.{1}: invalid attribute type. Namespace attribute cannot be of type entity/classification/struct/namespace"),
+    TERM_HAS_ENTITY_ASSOCIATION(400, "ATLAS-400-00-084", "Term (guid={0}) can't be deleted as it has been assigned to {1} entities."),
+    INVALID_TIMEBOUNDRY_TIMEZONE(400, "ATLAS-400-00-085", "Invalid timezone {0}"),
+    INVALID_TIMEBOUNDRY_START_TIME(400, "ATLAS-400-00-086", "Invalid startTime {0}"),
+    INVALID_TIMEBOUNDRY_END_TIME(400, "ATLAS-400-00-087", "Invalid endTime {0}"),
+    INVALID_TIMEBOUNDRY_DATERANGE(400, "ATLAS-400-00-088", "Invalid dateRange: startTime {0} must be before endTime {1}"),
+    PROPAGATED_CLASSIFICATION_REMOVAL_NOT_SUPPORTED(400, "ATLAS-400-00-089", "Removal of classification {0}, which is propagated from entity {1}, is not supported"),
+    IMPORT_ATTEMPTING_EMPTY_ZIP(400, "ATLAS-400-00-08A", "Attempting to import empty ZIP file."),
+    PATCH_MISSING_RELATIONSHIP_LABEL(400, "ATLAS-400-00-08B", "{0} - must include relationship label for type {1}"),
+    INVALID_CUSTOM_ATTRIBUTE_KEY_LENGTH(400, "ATLAS-400-00-08C", "Invalid key: {0} in custom attribute, key size should not be greater than 50"),
+    INVALID_CUSTOM_ATTRIBUTE_KEY_CHARACTERS(400, "ATLAS-400-00-08D", "Invalid key: {0} in custom attribute, key should only contain alphanumeric characters, '_' or '-'"),
+    INVALID_CUSTOM_ATTRIBUTE_VALUE(400, "ATLAS-400-00-08E", "Invalid value: {0} in custom attribute, value length is greater than {1}"),
+    INVALID_LABEL_LENGTH(400, "ATLAS-400-00-08F", "Invalid label: {0}, label size should not be greater than {1}"),
+    INVALID_LABEL_CHARACTERS(400, "ATLAS-400-00-090", "Invalid label: {0}, label should contain alphanumeric characters, '_' or '-'"),
+    INVALID_PROPAGATION_TYPE(400, "ATLAS-400-00-091", "Invalid propagation {0} for relationship-type={1}. Default value is {2}"),
+    DUPLICATE_NAMESPACE_ATTRIBUTE(400, "ATLAS-400-00-092", "Duplicate Namespace Attributes: {0} not allowed within the same namespace: {1}"),
+    APPLICABLE_ENTITY_TYPES_DELETION_NOT_SUPPORTED(400, "ATLAS-400-00-093", "Cannot remove applicableEntityTypes in Attribute Def: {0}, defined in namespace: {1}"),
+    NAMESPACE_DEF_MANDATORY_ATTRIBUTE_NOT_ALLOWED(400, "ATLAS-400-00-094", "{0}.{1} : namespaces can not have mandatory attribute"),
+    NAMESPACE_DEF_UNIQUE_ATTRIBUTE_NOT_ALLOWED(400, "ATLAS-400-00-095", "{0}.{1} : namespaces can not have unique attribute"),
+    NAMESPACE_DEF_ATTRIBUTE_TYPE_INVALID(400, "ATLAS-400-00-096", "{0}.{1}: invalid attribute type. Namespace attribute cannot be of type entity/classification/struct/namespace"),
+    INVALID_NAMESPACE_NAME_FOR_ENTITY_TYPE(400, "ATLAS-400-00-097", "Invalid Namespace: {0} specified for entity, applicable namespaces: {1}"),
+    NAMESPACE_ATTRIBUTE_DOES_NOT_EXIST(400, "ATLAS-400-00-098", "Namespace attribute does not exist  in entity: {0}"),
+    NAMESPACE_ATTRIBUTE_ALREADY_EXISTS(400, "ATLAS-400-00-099", "Namespace attribute already exists in entity: {0}"),
 
     UNAUTHORIZED_ACCESS(403, "ATLAS-403-00-001", "{0} is not authorized to perform {1}"),
 
diff --git a/intg/src/main/java/org/apache/atlas/model/instance/AtlasEntity.java b/intg/src/main/java/org/apache/atlas/model/instance/AtlasEntity.java
index 1b033b9..2e2e4ee 100644
--- a/intg/src/main/java/org/apache/atlas/model/instance/AtlasEntity.java
+++ b/intg/src/main/java/org/apache/atlas/model/instance/AtlasEntity.java
@@ -89,11 +89,12 @@ public class AtlasEntity extends AtlasStruct implements Serializable {
     private Date    updateTime     = null;
     private Long    version        = 0L;
 
-    private Map<String, Object>             relationshipAttributes;
-    private List<AtlasClassification>       classifications;
-    private List<AtlasTermAssignmentHeader> meanings;
-    private Map<String, String>             customAttributes;
-    private Set<String>                     labels;
+    private Map<String, Object>              relationshipAttributes;
+    private List<AtlasClassification>        classifications;
+    private List<AtlasTermAssignmentHeader>  meanings;
+    private Map<String, String>              customAttributes;
+    private Map<String, Map<String, Object>> namespaceAttributes;
+    private Set<String>                      labels;
 
     @JsonIgnore
     private static AtomicLong s_nextId = new AtomicLong(System.nanoTime());
@@ -217,6 +218,7 @@ public class AtlasEntity extends AtlasStruct implements Serializable {
             setRelationshipAttributes(other.getRelationshipAttributes());
             setMeanings(other.getMeanings());
             setCustomAttributes(other.getCustomAttributes());
+            setNamespaceAttributes(other.getNamespaceAttributes());
             setLabels(other.getLabels());
         }
     }
@@ -348,6 +350,41 @@ public class AtlasEntity extends AtlasStruct implements Serializable {
         this.customAttributes = customAttributes;
     }
 
+    public Map<String, Map<String, Object>> getNamespaceAttributes() {
+        return namespaceAttributes;
+    }
+
+    public void setNamespaceAttributes(Map<String, Map<String, Object>> namespaceAttributes) {
+        this.namespaceAttributes = namespaceAttributes;
+    }
+
+    public void setNamespaceAttribute(String nsName, String nsAttrName, Object nsValue) {
+        Map<String, Map<String, Object>> namespaceAttributes = this.namespaceAttributes;
+
+        if (namespaceAttributes == null) {
+            namespaceAttributes = new HashMap<>();
+
+            this.namespaceAttributes = namespaceAttributes;
+        }
+
+        Map<String, Object> namespaceAttributeMap = namespaceAttributes.get(nsName);
+
+        if (namespaceAttributeMap == null) {
+            namespaceAttributeMap = new HashMap<>();
+
+            namespaceAttributes.put(nsName, namespaceAttributeMap);
+        }
+
+        namespaceAttributeMap.put(nsAttrName, nsValue);
+    }
+
+    public Object getNamespaceAttribute(String nsName, String nsAttrName) {
+        Map<String, Map<String, Object>> namespaceAttributes   = this.namespaceAttributes;
+        Map<String, Object>              namespaceAttributeMap = namespaceAttributes == null ? null : namespaceAttributes.get(nsName);
+
+        return namespaceAttributeMap == null ? null : namespaceAttributeMap.get(nsAttrName);
+    }
+
     public Set<String> getLabels() {
         return labels;
     }
@@ -404,6 +441,7 @@ public class AtlasEntity extends AtlasStruct implements Serializable {
         setClassifications(null);
         setMeanings(null);
         setCustomAttributes(null);
+        setNamespaceAttributes(null);
         setLabels(null);
     }
 
@@ -442,6 +480,9 @@ public class AtlasEntity extends AtlasStruct implements Serializable {
         sb.append(", customAttributes=[");
         dumpObjects(customAttributes, sb);
         sb.append("]");
+        sb.append(", namespaceAttributes=[");
+        dumpObjects(namespaceAttributes, sb);
+        sb.append("]");
         sb.append(", labels=[");
         dumpObjects(labels, sb);
         sb.append("]");
@@ -470,14 +511,15 @@ public class AtlasEntity extends AtlasStruct implements Serializable {
                 Objects.equals(version, that.version) &&
                 Objects.equals(relationshipAttributes, that.relationshipAttributes) &&
                 Objects.equals(customAttributes, that.customAttributes) &&
+                Objects.equals(namespaceAttributes, that.namespaceAttributes) &&
                 Objects.equals(labels, that.labels) &&
                 Objects.equals(classifications, that.classifications);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), guid, homeId, isProxy, isIncomplete, provenanceType, status, createdBy,
-                updatedBy, createTime, updateTime, version, relationshipAttributes, classifications, customAttributes, labels);
+        return Objects.hash(super.hashCode(), guid, homeId, isProxy, isIncomplete, provenanceType, status, createdBy, updatedBy,
+                createTime, updateTime, version, relationshipAttributes, classifications, customAttributes, namespaceAttributes, labels);
     }
 
     @Override
diff --git a/intg/src/main/java/org/apache/atlas/type/AtlasNamespaceType.java b/intg/src/main/java/org/apache/atlas/type/AtlasNamespaceType.java
index a141d4a..cfbf2b1 100644
--- a/intg/src/main/java/org/apache/atlas/type/AtlasNamespaceType.java
+++ b/intg/src/main/java/org/apache/atlas/type/AtlasNamespaceType.java
@@ -146,21 +146,22 @@ public class AtlasNamespaceType extends AtlasStructType {
 
     public static class AtlasNamespaceAttribute extends AtlasAttribute {
         private final Set<AtlasEntityType> applicableEntityTypes;
-        private final int maxStringLength;
-        private final String validPattern;
+        private final int                  maxStringLength;
+        private final String               validPattern;
 
         public AtlasNamespaceAttribute(AtlasAttribute attribute, Set<AtlasEntityType> applicableEntityTypes) {
             super(attribute);
-            this.maxStringLength = 0;
-            this.validPattern = null;
+
+            this.maxStringLength       = 0;
+            this.validPattern          = null;
             this.applicableEntityTypes = applicableEntityTypes;
         }
 
-        public AtlasNamespaceAttribute(AtlasAttribute attribute, Set<AtlasEntityType> applicableEntityTypes,
-                                       int maxStringLength, String validPattern) {
+        public AtlasNamespaceAttribute(AtlasAttribute attribute, Set<AtlasEntityType> applicableEntityTypes, int maxStringLength, String validPattern) {
             super(attribute);
-            this.maxStringLength = maxStringLength;
-            this.validPattern = validPattern;
+
+            this.maxStringLength       = maxStringLength;
+            this.validPattern          = validPattern;
             this.applicableEntityTypes = applicableEntityTypes;
         }
 
diff --git a/intg/src/main/java/org/apache/atlas/type/AtlasTypeRegistry.java b/intg/src/main/java/org/apache/atlas/type/AtlasTypeRegistry.java
index 97e27d0..5b7cbee 100644
--- a/intg/src/main/java/org/apache/atlas/type/AtlasTypeRegistry.java
+++ b/intg/src/main/java/org/apache/atlas/type/AtlasTypeRegistry.java
@@ -201,6 +201,7 @@ public class AtlasTypeRegistry {
         return registryData.namespaceDefs.getAll();
     }
 
+    public AtlasNamespaceType getNamespaceTypeByName(String name) { return registryData.namespaceDefs.getTypeByName(name); }
 
     public Collection<AtlasRelationshipDef> getAllRelationshipDefs() { return registryData.relationshipDefs.getAll(); }
 
diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasEntityStore.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasEntityStore.java
index 928c70d..39ea3f8 100644
--- a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasEntityStore.java
+++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasEntityStore.java
@@ -248,6 +248,23 @@ public interface AtlasEntityStore {
     void setLabels(String guid, Set<String> labels) throws AtlasBaseException;
 
     /**
+     *
+     * @param guid
+     * @param namespaceAttributes
+     * @param isOverwrite
+     * @throws AtlasBaseException
+     */
+    void addOrUpdateNamespaceAttributes(String guid, Map<String, Map<String, Object>> namespaceAttributes, boolean isOverwrite) throws AtlasBaseException;
+
+    /**
+     *
+     * @param guid
+     * @param namespaceAttributes
+     * @throws AtlasBaseException
+     */
+    void removeNamespaceAttributes(String guid, Map<String, Map<String, Object>> namespaceAttributes) throws AtlasBaseException;
+
+    /**
      * Remove given labels, if labels is null/empty, no labels will be removed. If any labels in
      * labels set are non-existing labels, they will be ignored, only existing labels will be removed.
      */
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 25284e9..f511e2f 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
@@ -47,6 +47,7 @@ import org.apache.atlas.repository.store.graph.v1.DeleteHandlerDelegate;
 import org.apache.atlas.store.DeleteType;
 import org.apache.atlas.type.AtlasClassificationType;
 import org.apache.atlas.type.AtlasEntityType;
+import org.apache.atlas.type.AtlasNamespaceType.AtlasNamespaceAttribute;
 import org.apache.atlas.type.AtlasStructType.AtlasAttribute;
 import org.apache.atlas.type.AtlasType;
 import org.apache.atlas.type.AtlasTypeRegistry;
@@ -74,6 +75,7 @@ import static java.lang.Boolean.FALSE;
 import static org.apache.atlas.model.instance.EntityMutations.EntityOperation.*;
 import static org.apache.atlas.repository.Constants.IS_INCOMPLETE_PROPERTY_KEY;
 import static org.apache.atlas.repository.graph.GraphHelper.getCustomAttributes;
+import static org.apache.atlas.repository.graph.GraphHelper.getTypeName;
 import static org.apache.atlas.repository.graph.GraphHelper.isEntityIncomplete;
 import static org.apache.atlas.repository.store.graph.v2.EntityGraphMapper.validateLabels;
 
@@ -318,13 +320,13 @@ public class AtlasEntityStoreV2 implements AtlasEntityStore {
     @Override
     @GraphTransaction
     public EntityMutationResponse createOrUpdate(EntityStream entityStream, boolean isPartialUpdate) throws AtlasBaseException {
-        return createOrUpdate(entityStream, isPartialUpdate, false);
+        return createOrUpdate(entityStream, isPartialUpdate, false, false);
     }
 
     @Override
     @GraphTransaction(logRollback = false)
     public EntityMutationResponse createOrUpdateForImport(EntityStream entityStream) throws AtlasBaseException {
-        return createOrUpdate(entityStream, false, true);
+        return createOrUpdate(entityStream, false, true, true);
     }
 
     @Override
@@ -356,7 +358,7 @@ public class AtlasEntityStoreV2 implements AtlasEntityStore {
 
         entity.setGuid(guid);
 
-        return createOrUpdate(new AtlasEntityStream(updatedEntityInfo), isPartialUpdate, false);
+        return createOrUpdate(new AtlasEntityStream(updatedEntityInfo), isPartialUpdate, false, false);
     }
 
     @Override
@@ -378,7 +380,7 @@ public class AtlasEntityStoreV2 implements AtlasEntityStore {
 
         AtlasAuthorizationUtils.verifyAccess(new AtlasEntityAccessRequest(typeRegistry, AtlasPrivilege.ENTITY_UPDATE, new AtlasEntityHeader(entity)), "update entity ByUniqueAttributes");
 
-        return createOrUpdate(new AtlasEntityStream(updatedEntityInfo), true, false);
+        return createOrUpdate(new AtlasEntityStream(updatedEntityInfo), true, false, false);
     }
 
     @Override
@@ -429,7 +431,7 @@ public class AtlasEntityStoreV2 implements AtlasEntityStore {
                 throw new AtlasBaseException(AtlasErrorCode.ATTRIBUTE_UPDATE_NOT_SUPPORTED, attrName, attrType.getTypeName());
         }
 
-        return createOrUpdate(new AtlasEntityStream(updateEntity), true, false);
+        return createOrUpdate(new AtlasEntityStream(updateEntity), true, false, false);
     }
 
     @Override
@@ -823,6 +825,66 @@ public class AtlasEntityStoreV2 implements AtlasEntityStore {
 
     @Override
     @GraphTransaction
+    public void addOrUpdateNamespaceAttributes(String guid, Map<String, Map<String, Object>> entityNamespaces, boolean isOverwrite) throws AtlasBaseException {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> addOrUpdateNamespaceAttributes(guid={}, entityNamespaces={}, isOverwrite={})", guid, entityNamespaces, isOverwrite);
+        }
+
+        if (StringUtils.isEmpty(guid)) {
+            throw new AtlasBaseException(AtlasErrorCode.INVALID_PARAMETERS, "guid is null/empty");
+        }
+
+        AtlasVertex entityVertex = AtlasGraphUtilsV2.findByGuid(guid);
+
+        if (entityVertex == null) {
+            throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, guid);
+        }
+
+        String          typeName   = getTypeName(entityVertex);
+        AtlasEntityType entityType = typeRegistry.getEntityTypeByName(typeName);
+
+        validateNamespaceAttributes(entityVertex, entityType, entityNamespaces, isOverwrite);
+
+        if (isOverwrite) {
+            entityGraphMapper.setNamespaceAttributes(entityVertex, entityType, entityNamespaces);
+        } else {
+            entityGraphMapper.addOrUpdateNamespaceAttributes(entityVertex, entityType, entityNamespaces);
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== addOrUpdateNamespaceAttributes(guid={}, entityNamespaces={}, isOverwrite={})", guid, entityNamespaces, isOverwrite);
+        }
+    }
+
+    @Override
+    @GraphTransaction
+    public void removeNamespaceAttributes(String guid, Map<String, Map<String, Object>> entityNamespaces) throws AtlasBaseException {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> removeNamespaceAttributes(guid={}, entityNamespaces={})", guid, entityNamespaces);
+        }
+
+        if (StringUtils.isEmpty(guid)) {
+            throw new AtlasBaseException(AtlasErrorCode.INVALID_PARAMETERS, "guid is null/empty");
+        }
+
+        AtlasVertex entityVertex = AtlasGraphUtilsV2.findByGuid(guid);
+
+        if (entityVertex == null) {
+            throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, guid);
+        }
+
+        String          typeName   = getTypeName(entityVertex);
+        AtlasEntityType entityType = typeRegistry.getEntityTypeByName(typeName);
+
+        entityGraphMapper.removeNamespaceAttributes(entityVertex, entityType, entityNamespaces);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== removeNamespaceAttributes(guid={}, entityNamespaces={})", guid, entityNamespaces);
+        }
+    }
+
+    @Override
+    @GraphTransaction
     public void setLabels(String guid, Set<String> labels) throws AtlasBaseException {
         if (LOG.isDebugEnabled()) {
             LOG.debug("==> setLabels()");
@@ -899,7 +961,7 @@ public class AtlasEntityStoreV2 implements AtlasEntityStore {
         }
     }
 
-    private EntityMutationResponse createOrUpdate(EntityStream entityStream, boolean isPartialUpdate, boolean replaceClassifications) throws AtlasBaseException {
+    private EntityMutationResponse createOrUpdate(EntityStream entityStream, boolean isPartialUpdate, boolean replaceClassifications, boolean replaceNamespaceAttributes) throws AtlasBaseException {
         if (LOG.isDebugEnabled()) {
             LOG.debug("==> createOrUpdate()");
         }
@@ -1040,7 +1102,7 @@ public class AtlasEntityStoreV2 implements AtlasEntityStore {
                 RequestContext.get().endMetricRecord(checkForUnchangedEntities);
             }
 
-            EntityMutationResponse ret = entityGraphMapper.mapAttributesAndClassifications(context, isPartialUpdate, replaceClassifications);
+            EntityMutationResponse ret = entityGraphMapper.mapAttributesAndClassifications(context, isPartialUpdate, replaceClassifications, replaceNamespaceAttributes);
 
             ret.setGuidAssignments(context.getGuidAssignments());
 
@@ -1311,4 +1373,50 @@ public class AtlasEntityStoreV2 implements AtlasEntityStore {
             }
         }
     }
+
+    private void validateNamespaceAttributes(AtlasVertex entityVertex, AtlasEntityType entityType, Map<String, Map<String, Object>> entityNamespaces, boolean isOverwrite) throws AtlasBaseException {
+        List<String> messages = new ArrayList<>();
+
+        Map<String, List<AtlasNamespaceAttribute>> entityTypeNamespaces = entityType.getNamespaceAttributes();
+
+        for (String nsName : entityNamespaces.keySet()) {
+            if (!entityNamespaces.containsKey(nsName)) {
+                messages.add(nsName + ": invalid namespace for entity type " + entityType.getTypeName());
+
+                continue;
+            }
+
+            List<AtlasNamespaceAttribute> entityTypeNsAttributes = entityTypeNamespaces.get(nsName);
+            Map<String, Object>           entityNsAttributes     = entityNamespaces.get(nsName);
+
+            for (AtlasNamespaceAttribute nsAttribute : entityTypeNsAttributes) {
+                AtlasType attrType  = nsAttribute.getAttributeType();
+                String    attrName  = nsAttribute.getName();
+                Object    attrValue = entityNsAttributes.get(attrName);
+                String    fieldName = entityType.getTypeName() + "." + nsName + "." + attrName;
+
+                if (attrValue != null) {
+                    attrType.validateValue(attrValue, fieldName, messages);
+                } else if (!nsAttribute.getAttributeDef().getIsOptional()) {
+                    final boolean isAttrValuePresent;
+
+                    if (isOverwrite) {
+                        isAttrValuePresent = false;
+                    } else {
+                        Object existingValue = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, nsAttribute.getVertexPropertyName(), Object.class);
+
+                        isAttrValuePresent = existingValue != null;
+                    }
+
+                    if (!isAttrValuePresent) {
+                        messages.add(fieldName + ": mandatory namespace attribute value missing in type " + entityType.getTypeName());
+                    }
+                }
+            }
+        }
+
+        if (!messages.isEmpty()) {
+            throw new AtlasBaseException(AtlasErrorCode.INSTANCE_CRUD_INVALID_PARAMS, messages);
+        }
+    }
 }
diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphMapper.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphMapper.java
index 3d42d1f..113325d 100644
--- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphMapper.java
+++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphMapper.java
@@ -53,6 +53,7 @@ import org.apache.atlas.type.AtlasBuiltInTypes;
 import org.apache.atlas.type.AtlasClassificationType;
 import org.apache.atlas.type.AtlasEntityType;
 import org.apache.atlas.type.AtlasMapType;
+import org.apache.atlas.type.AtlasNamespaceType.AtlasNamespaceAttribute;
 import org.apache.atlas.type.AtlasStructType;
 import org.apache.atlas.type.AtlasStructType.AtlasAttribute;
 import org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection;
@@ -253,7 +254,7 @@ public class EntityGraphMapper {
         }
     }
 
-    public EntityMutationResponse mapAttributesAndClassifications(EntityMutationContext context, final boolean isPartialUpdate, final boolean replaceClassifications) throws AtlasBaseException {
+    public EntityMutationResponse mapAttributesAndClassifications(EntityMutationContext context, final boolean isPartialUpdate, final boolean replaceClassifications, boolean replaceNamespaceAttributes) throws AtlasBaseException {
         MetricRecorder metric = RequestContext.get().startMetricRecord("mapAttributesAndClassifications");
 
         EntityMutationResponse resp       = new EntityMutationResponse();
@@ -275,6 +276,8 @@ public class EntityGraphMapper {
                 resp.addEntity(CREATE, constructHeader(createdEntity, entityType, vertex));
                 addClassifications(context, guid, createdEntity.getClassifications());
 
+                addOrUpdateNamespaceAttributes(vertex, entityType, createdEntity.getNamespaceAttributes());
+
                 reqContext.cache(createdEntity);
             }
         }
@@ -300,6 +303,10 @@ public class EntityGraphMapper {
                     addClassifications(context, guid, updatedEntity.getClassifications());
                 }
 
+                if (replaceNamespaceAttributes) {
+                    setNamespaceAttributes(vertex, entityType, updatedEntity.getNamespaceAttributes());
+                }
+
                 reqContext.cache(updatedEntity);
             }
         }
@@ -412,6 +419,143 @@ public class EntityGraphMapper {
         return ret;
     }
 
+    /*
+     * reset/overwrite namespace attributes of the entity with given values
+     */
+    public void setNamespaceAttributes(AtlasVertex entityVertex, AtlasEntityType entityType, Map<String, Map<String, Object>> entityNamespaces) throws AtlasBaseException {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> setNamespaceAttributes(entityVertex={}, entityType={}, entityNamespaces={}", entityVertex, entityType.getTypeName(), entityNamespaces);
+        }
+
+        Map<String, List<AtlasNamespaceAttribute>> entityTypeNamespaces = entityType.getNamespaceAttributes();
+
+        for (Map.Entry<String, List<AtlasNamespaceAttribute>> entry : entityTypeNamespaces.entrySet()) {
+            String                        nsName                 = entry.getKey();
+            List<AtlasNamespaceAttribute> entityTypeNsAttributes = entry.getValue();
+            Map<String, Object>           entityNsAttributes     = MapUtils.isEmpty(entityNamespaces) ? null : entityNamespaces.get(nsName);
+
+            for (AtlasNamespaceAttribute nsAttribute : entityTypeNsAttributes) {
+                String nsAttrName          = nsAttribute.getName();
+                Object nsAttrExistingValue = entityVertex.getProperty(nsAttribute.getVertexPropertyName(), Object.class);
+                Object nsAttrNewValue      = MapUtils.isEmpty(entityNsAttributes) ? null : entityNsAttributes.get(nsAttrName);
+
+                if (nsAttrExistingValue == null) {
+                    if (nsAttrNewValue != null) {
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("setNamespaceAttributes(): adding {}.{}={}", nsName, nsAttribute.getName(), nsAttrNewValue);
+                        }
+
+                        mapAttribute(nsAttribute, nsAttrNewValue, entityVertex, CREATE, new EntityMutationContext());
+                    }
+                } else {
+                    if (nsAttrNewValue != null) {
+                        if (!Objects.equals(nsAttrExistingValue, nsAttrNewValue)) {
+                            if (LOG.isDebugEnabled()) {
+                                LOG.debug("setNamespaceAttributes(): updating {}.{}={}", nsName, nsAttribute.getName(), nsAttrNewValue);
+                            }
+
+                            mapAttribute(nsAttribute, nsAttrNewValue, entityVertex, UPDATE, new EntityMutationContext());
+                        }
+                    } else {
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("setNamespaceAttributes(): removing {}.{}", nsName, nsAttribute.getName());
+                        }
+
+                        entityVertex.removeProperty(nsAttribute.getVertexPropertyName());
+                    }
+                }
+            }
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== setNamespaceAttributes(entityVertex={}, entityType={}, entityNamespaces={}", entityVertex, entityType.getTypeName(), entityNamespaces);
+        }
+    }
+
+    /*
+     * add or update the given namespace attributes on the entity
+     */
+    public void addOrUpdateNamespaceAttributes(AtlasVertex entityVertex, AtlasEntityType entityType, Map<String, Map<String, Object>> entityNamespaces) throws AtlasBaseException {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> addOrUpdateNamespaceAttributes(entityVertex={}, entityType={}, entityNamespaces={}", entityVertex, entityType.getTypeName(), entityNamespaces);
+        }
+
+        Map<String, List<AtlasNamespaceAttribute>> entityTypeNamespaces = entityType.getNamespaceAttributes();
+
+        if (MapUtils.isNotEmpty(entityTypeNamespaces) && MapUtils.isNotEmpty(entityNamespaces)) {
+            for (Map.Entry<String, List<AtlasNamespaceAttribute>> entry : entityTypeNamespaces.entrySet()) {
+                String                        nsName                 = entry.getKey();
+                List<AtlasNamespaceAttribute> entityTypeNsAttributes = entry.getValue();
+                Map<String, Object>           entityNsAttributes     = entityNamespaces.get(nsName);
+
+                if (MapUtils.isEmpty(entityNsAttributes)) {
+                    continue;
+                }
+
+                for (AtlasNamespaceAttribute nsAttribute : entityTypeNsAttributes) {
+                    String nsAttrName = nsAttribute.getName();
+
+                    if (!entityNsAttributes.containsKey(nsAttrName)) {
+                        continue;
+                    }
+
+                    Object nsAttrValue   = entityNsAttributes.get(nsAttrName);
+                    Object existingValue = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, nsAttribute.getVertexPropertyName(), Object.class);
+
+                    if (existingValue == null) {
+                        if (nsAttrValue != null) {
+                            mapAttribute(nsAttribute, nsAttrValue, entityVertex, CREATE, new EntityMutationContext());
+                        }
+                    } else {
+                        if (!Objects.equals(existingValue, nsAttrValue)) {
+                            mapAttribute(nsAttribute, nsAttrValue, entityVertex, UPDATE, new EntityMutationContext());
+                        }
+                    }
+                }
+            }
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== addOrUpdateNamespaceAttributes(entityVertex={}, entityType={}, entityNamespaces={}", entityVertex, entityType.getTypeName(), entityNamespaces);
+        }
+    }
+
+    /*
+     * remove the given namespace attributes from the entity
+     */
+    public void removeNamespaceAttributes(AtlasVertex entityVertex, AtlasEntityType entityType, Map<String, Map<String, Object>> entityNamespaces) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> removeNamespaceAttributes(entityVertex={}, entityType={}, entityNamespaces={}", entityVertex, entityType.getTypeName(), entityNamespaces);
+        }
+
+        Map<String, List<AtlasNamespaceAttribute>> entityTypeNamespaces = entityType.getNamespaceAttributes();
+
+        if (MapUtils.isNotEmpty(entityTypeNamespaces) && MapUtils.isNotEmpty(entityNamespaces)) {
+            for (Map.Entry<String, List<AtlasNamespaceAttribute>> entry : entityTypeNamespaces.entrySet()) {
+                String                        nsName                 = entry.getKey();
+                List<AtlasNamespaceAttribute> entityTypeNsAttributes = entry.getValue();
+
+                if (!entityNamespaces.containsKey(nsName)) { // nothing to remove for this namespace
+                    continue;
+                }
+
+                Map<String, Object> entityNsAttributes = entityNamespaces.get(nsName);
+
+                for (AtlasNamespaceAttribute nsAttribute : entityTypeNsAttributes) {
+                    // if (entityNsAttributes is empty) remove all attributes in this namespace
+                    // else remove the attribute only if its given in entityNsAttributes
+                    if (MapUtils.isEmpty(entityNsAttributes) || entityNsAttributes.containsKey(nsAttribute.getName())) {
+                        entityVertex.removeProperty(nsAttribute.getVertexPropertyName());
+                    }
+                }
+            }
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== removeNamespaceAttributes(entityVertex={}, entityType={}, entityNamespaces={}", entityVertex, entityType.getTypeName(), entityNamespaces);
+        }
+    }
+
     private AtlasVertex createStructVertex(AtlasStruct struct) {
         return createStructVertex(struct.getTypeName());
     }
diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphRetriever.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphRetriever.java
index dc4c399..55c1cac 100644
--- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphRetriever.java
+++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphRetriever.java
@@ -48,6 +48,7 @@ import org.apache.atlas.type.AtlasArrayType;
 import org.apache.atlas.type.AtlasBuiltInTypes.AtlasObjectIdType;
 import org.apache.atlas.type.AtlasEntityType;
 import org.apache.atlas.type.AtlasMapType;
+import org.apache.atlas.type.AtlasNamespaceType.AtlasNamespaceAttribute;
 import org.apache.atlas.type.AtlasRelationshipType;
 import org.apache.atlas.type.AtlasStructType;
 import org.apache.atlas.type.AtlasStructType.AtlasAttribute;
@@ -586,6 +587,8 @@ public class EntityGraphRetriever {
 
             mapSystemAttributes(entityVertex, entity);
 
+            mapNamespaceAttributes(entityVertex, entity);
+
             mapAttributes(entityVertex, entity, entityExtInfo, isMinExtInfo, includeReferences);
 
             if (!ignoreRelationshipAttr) { // only map when really needed
@@ -761,6 +764,26 @@ public class EntityGraphRetriever {
         }
     }
 
+    private void mapNamespaceAttributes(AtlasVertex entityVertex, AtlasEntity entity) throws AtlasBaseException {
+        AtlasEntityType                            entityType           = typeRegistry.getEntityTypeByName(entity.getTypeName());
+        Map<String, List<AtlasNamespaceAttribute>> entityTypeNamespaces = entityType != null ? entityType.getNamespaceAttributes() : null;
+
+        if (MapUtils.isNotEmpty(entityTypeNamespaces)) {
+            for (Map.Entry<String, List<AtlasNamespaceAttribute>> entry : entityTypeNamespaces.entrySet()) {
+                String                        nsName       = entry.getKey();
+                List<AtlasNamespaceAttribute> nsAttributes = entry.getValue();
+
+                for (AtlasNamespaceAttribute nsAttribute : nsAttributes) {
+                    Object nsAttrValue = mapVertexToAttribute(entityVertex, nsAttribute, null, false, false);
+
+                    if (nsAttrValue != null) {
+                        entity.setNamespaceAttribute(nsName, nsAttribute.getName(), nsAttrValue);
+                    }
+                }
+            }
+        }
+    }
+
     public List<AtlasClassification> getAllClassifications(AtlasVertex entityVertex) throws AtlasBaseException {
         List<AtlasClassification> ret   = new ArrayList<>();
         Iterable                  edges = entityVertex.query().direction(AtlasEdgeDirection.OUT).label(CLASSIFICATION_LABEL).edges();
diff --git a/webapp/src/main/java/org/apache/atlas/web/rest/EntityREST.java b/webapp/src/main/java/org/apache/atlas/web/rest/EntityREST.java
index 6845121..fcf7189 100644
--- a/webapp/src/main/java/org/apache/atlas/web/rest/EntityREST.java
+++ b/webapp/src/main/java/org/apache/atlas/web/rest/EntityREST.java
@@ -64,6 +64,7 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -855,6 +856,78 @@ public class EntityREST {
         }
     }
 
+    @POST
+    @Path("/guid/{guid}/namespaces")
+    @Produces(Servlets.JSON_MEDIA_TYPE)
+    @Consumes(Servlets.JSON_MEDIA_TYPE)
+    public void addOrUpdateNamespaceAttributes(@PathParam("guid") final String guid, @QueryParam("isOverwrite") @DefaultValue("false") boolean isOverwrite, Map<String, Map<String, Object>> entityNamespaces) throws AtlasBaseException {
+        AtlasPerfTracer perf = null;
+
+        try {
+            if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
+                perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.addOrUpdateNamespaceAttributes(" + guid + ", isOverwrite=" + isOverwrite + ")");
+            }
+
+            entitiesStore.addOrUpdateNamespaceAttributes(guid, entityNamespaces, isOverwrite);
+        } finally {
+            AtlasPerfTracer.log(perf);
+        }
+    }
+
+    @DELETE
+    @Path("/guid/{guid}/namespaces")
+    @Produces(Servlets.JSON_MEDIA_TYPE)
+    @Consumes(Servlets.JSON_MEDIA_TYPE)
+    public void removeNamespaceAttributes(@PathParam("guid") final String guid, Map<String, Map<String, Object>> entityNamespaces) throws AtlasBaseException {
+        AtlasPerfTracer perf = null;
+
+        try {
+            if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
+                perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.removeNamespaceAttributes(" + guid + ")");
+            }
+
+            entitiesStore.removeNamespaceAttributes(guid, entityNamespaces);
+        } finally {
+            AtlasPerfTracer.log(perf);
+        }
+    }
+
+    @POST
+    @Path("/guid/{guid}/namespace/{namespace}")
+    @Produces(Servlets.JSON_MEDIA_TYPE)
+    @Consumes(Servlets.JSON_MEDIA_TYPE)
+    public void addOrUpdateNamespaceAttributes(@PathParam("guid") final String guid, @PathParam("namespace") final String namespace, Map<String, Object> entityNsAttributes) throws AtlasBaseException {
+        AtlasPerfTracer perf = null;
+
+        try {
+            if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
+                perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.addOrUpdateNamespaceAttributes(" + guid + ", " + namespace + ")");
+            }
+
+            entitiesStore.addOrUpdateNamespaceAttributes(guid, Collections.singletonMap(namespace, entityNsAttributes), false);
+        } finally {
+            AtlasPerfTracer.log(perf);
+        }
+    }
+
+    @DELETE
+    @Path("/guid/{guid}/namespace/{namespace}")
+    @Produces(Servlets.JSON_MEDIA_TYPE)
+    @Consumes(Servlets.JSON_MEDIA_TYPE)
+    public void removeNamespaceAttributes(@PathParam("guid") final String guid, @PathParam("namespace") final String namespace, Map<String, Object> entityNsAttributes) throws AtlasBaseException {
+        AtlasPerfTracer perf = null;
+
+        try {
+            if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
+                perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.removeNamespaceAttributes(" + guid + ", " + namespace + ")");
+            }
+
+            entitiesStore.removeNamespaceAttributes(guid, Collections.singletonMap(namespace, entityNsAttributes));
+        } finally {
+            AtlasPerfTracer.log(perf);
+        }
+    }
+
     /**
      * Set labels to a given entity
      * @param guid - Unique entity identifier