This is an automated email from the ASF dual-hosted git repository. madhan pushed a commit to branch branch-2.0 in repository https://gitbox.apache.org/repos/asf/atlas.git
commit e621788c55368a11c4fe8fa28f2b4caa626b1511 Author: Mandar Ambawane <mandar.ambaw...@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 <mad...@apache.org> (cherry picked from commit d3a08b6eabcc50536c468db2d1328df54ee27fce) --- .../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