Repository: incubator-atlas Updated Branches: refs/heads/master f74e43c2b -> 02e4e86b5
http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/02e4e86b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/EntityGraphMapper.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/EntityGraphMapper.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/EntityGraphMapper.java index 157f8cd..1282be5 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/EntityGraphMapper.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/EntityGraphMapper.java @@ -45,6 +45,7 @@ import org.apache.atlas.type.AtlasEntityType; import org.apache.atlas.type.AtlasMapType; import org.apache.atlas.type.AtlasStructType; import org.apache.atlas.type.AtlasStructType.AtlasAttribute; +import org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection; import org.apache.atlas.type.AtlasType; import org.apache.atlas.type.AtlasTypeRegistry; import org.apache.atlas.type.AtlasTypeUtil; @@ -63,7 +64,11 @@ import static org.apache.atlas.model.instance.EntityMutations.EntityOperation.DE import static org.apache.atlas.model.instance.EntityMutations.EntityOperation.PARTIAL_UPDATE; import static org.apache.atlas.model.instance.EntityMutations.EntityOperation.UPDATE; import static org.apache.atlas.repository.Constants.STATE_PROPERTY_KEY; +import static org.apache.atlas.repository.graph.GraphHelper.getTypeName; +import static org.apache.atlas.repository.graph.GraphHelper.isRelationshipEdge; import static org.apache.atlas.repository.graph.GraphHelper.string; +import static org.apache.atlas.repository.store.graph.v1.AtlasGraphUtilsV1.getIdFromVertex; +import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.IN; @Component public class EntityGraphMapper { @@ -144,6 +149,8 @@ public class EntityGraphMapper { mapAttributes(createdEntity, vertex, CREATE, context); + mapRelationshipAttributes(createdEntity, vertex, CREATE, context); + resp.addEntity(CREATE, constructHeader(createdEntity, entityType, vertex)); addClassifications(context, guid, createdEntity.getClassifications()); } @@ -157,6 +164,8 @@ public class EntityGraphMapper { mapAttributes(updatedEntity, vertex, UPDATE, context); + mapRelationshipAttributes(updatedEntity, vertex, UPDATE, context); + if (isPartialUpdate) { resp.addEntity(PARTIAL_UPDATE, constructHeader(updatedEntity, entityType, vertex)); } else { @@ -238,6 +247,7 @@ public class EntityGraphMapper { mapAttribute(attribute, attrValue, vertex, op, context); } + } else if (op.equals(UPDATE)) { for (String attrName : struct.getAttributes().keySet()) { AtlasAttribute attribute = structType.getAttribute(attrName); @@ -260,6 +270,41 @@ public class EntityGraphMapper { } } + private void mapRelationshipAttributes(AtlasEntity entity, AtlasVertex vertex, EntityOperation op, + EntityMutationContext context) throws AtlasBaseException { + if (LOG.isDebugEnabled()) { + LOG.debug("==> mapRelationshipAttributes({}, {})", op, entity.getTypeName()); + } + + if (MapUtils.isNotEmpty(entity.getRelationshipAttributes())) { + AtlasEntityType entityType = getEntityType(entity.getTypeName()); + + if (op.equals(CREATE)) { + for (AtlasAttribute attribute : entityType.getRelationshipAttributes().values()) { + Object attrValue = entity.getRelationshipAttribute(attribute.getName()); + + mapAttribute(attribute, attrValue, vertex, op, context); + } + + } else if (op.equals(UPDATE)) { + // relationship attributes mapping + for (AtlasAttribute attribute : entityType.getRelationshipAttributes().values()) { + if (attribute != null && entity.hasRelationshipAttribute(attribute.getName())) { + Object attrValue = entity.getRelationshipAttribute(attribute.getName()); + + mapAttribute(attribute, attrValue, vertex, op, context); + } + } + } + + updateModificationMetadata(vertex); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("<== mapRelationshipAttributes({}, {})", op, entity.getTypeName()); + } + } + private void mapAttribute(AtlasAttribute attribute, Object attrValue, AtlasVertex vertex, EntityOperation op, EntityMutationContext context) throws AtlasBaseException { if (attrValue == null) { AtlasAttributeDef attributeDef = attribute.getAttributeDef(); @@ -309,14 +354,15 @@ public class EntityGraphMapper { } case OBJECT_ID_TYPE: { - String edgeLabel = ctx.getAttribute().getRelationshipEdgeLabel(); + String edgeLabel = ctx.getAttribute().getRelationshipEdgeLabel(); + AtlasRelationshipEdgeDirection edgeDirection = ctx.getAttribute().getRelationshipEdgeDirection(); // legacy case - if relationship attribute doesn't exist, use legacy edge label. if (StringUtils.isEmpty(edgeLabel)) { edgeLabel = AtlasGraphUtilsV1.getEdgeLabel(ctx.getVertexProperty()); } - AtlasEdge currentEdge = graphHelper.getEdgeForLabel(ctx.getReferringVertex(), edgeLabel); + AtlasEdge currentEdge = graphHelper.getEdgeForLabel(ctx.getReferringVertex(), edgeLabel, edgeDirection); AtlasEdge newEdge = null; if (ctx.getValue() != null) { @@ -328,14 +374,41 @@ public class EntityGraphMapper { newEdge = mapObjectIdValueUsingRelationship(ctx, context); - if (newEdge != null && ctx.getAttribute().getInverseRefAttribute() != null) { + // legacy case update inverse attribute + if (ctx.getAttribute().getInverseRefAttribute() != null) { // Update the inverse reference using relationship on the target entity addInverseReference(ctx.getAttribute().getInverseRefAttribute(), newEdge); } } + // created new relationship, + // record entity update on both vertices of the new relationship + if (currentEdge == null && newEdge != null) { + + // based on relationship edge direction record update only on attribute vertex + if (edgeDirection == IN) { + recordEntityUpdate(newEdge.getOutVertex()); + + } else { + recordEntityUpdate(newEdge.getInVertex()); + } + } + + // update references, if current and new edge don't match + // record entity update on new reference and delete(edge) old reference. if (currentEdge != null && !currentEdge.equals(newEdge)) { - deleteHandler.deleteEdgeReference(currentEdge, ctx.getAttrType().getTypeCategory(), ctx.getAttribute().isOwnedRef(), true); + + //record entity update on new edge + if (isRelationshipEdge(newEdge)) { + AtlasVertex attrVertex = context.getDiscoveryContext().getResolvedEntityVertex(getGuid(ctx.getValue())); + + recordEntityUpdate(attrVertex); + updateModificationMetadata(attrVertex); + } + + //delete old reference + deleteHandler.deleteEdgeReference(currentEdge, ctx.getAttrType().getTypeCategory(), ctx.getAttribute().isOwnedRef(), + true, ctx.getAttribute().getRelationshipEdgeDirection()); } return newEdge; @@ -402,7 +475,7 @@ public class EntityGraphMapper { if (inverseUpdated) { updateModificationMetadata(inverseVertex); - AtlasObjectId inverseEntityId = new AtlasObjectId(AtlasGraphUtilsV1.getIdFromVertex(inverseVertex), inverseType.getTypeName()); + AtlasObjectId inverseEntityId = new AtlasObjectId(getIdFromVertex(inverseVertex), inverseType.getTypeName()); RequestContextV1.get().recordEntityUpdate(inverseEntityId); } } @@ -424,7 +497,7 @@ public class EntityGraphMapper { if (entityType.hasRelationshipAttribute(inverseAttributeName)) { String relationshipName = graphHelper.getRelationshipDefName(inverseVertex, entityType, inverseAttributeName); - ret = getOrCreateRelationship(inverseVertex, vertex, relationshipName); + ret = getOrCreateRelationship(inverseVertex, vertex, relationshipName, inverseAttribute); } else { if (LOG.isDebugEnabled()) { @@ -576,24 +649,47 @@ public class EntityGraphMapper { String attributeName = ctx.getAttribute().getName(); AtlasType type = typeRegistry.getType(AtlasGraphUtilsV1.getTypeName(entityVertex)); + AtlasRelationshipEdgeDirection edgeDirection = ctx.getAttribute().getRelationshipEdgeDirection(); + String edgeLabel = ctx.getAttribute().getRelationshipEdgeLabel(); + if (type instanceof AtlasEntityType) { AtlasEntityType entityType = (AtlasEntityType) type; // use relationship to create/update edges if (entityType.hasRelationshipAttribute(attributeName)) { if (ctx.getCurrentEdge() != null) { - ret = updateRelationship(ctx.getCurrentEdge(), attributeVertex); + ret = updateRelationship(ctx.getCurrentEdge(), attributeVertex, edgeDirection, ctx.getAttribute()); + + recordEntityUpdate(attributeVertex); } else { - String relationshipName = graphHelper.getRelationshipDefName(entityVertex, entityType, attributeName); - ret = getOrCreateRelationship(entityVertex, attributeVertex, relationshipName); - } + String relationshipName = graphHelper.getRelationshipDefName(entityVertex, entityType, attributeName); + AtlasVertex fromVertex; + AtlasVertex toVertex; + + if (edgeDirection == IN) { + fromVertex = attributeVertex; + toVertex = entityVertex; + + } else { + fromVertex = entityVertex; + toVertex = attributeVertex; + } + boolean relationshipExists = isRelationshipExists(fromVertex, toVertex, edgeLabel); + + ret = getOrCreateRelationship(fromVertex, toVertex, relationshipName, ctx.getAttribute()); + // if relationship did not exist before and new relationship was created + // record entity update on both relationship vertices + if (!relationshipExists) { + recordEntityUpdate(attributeVertex); + } + } } else { // use legacy way to create/update edges if (LOG.isDebugEnabled()) { - LOG.debug("No RelationshipDef defined between {} and {} on attribute: {}", AtlasGraphUtilsV1.getTypeName(entityVertex), - AtlasGraphUtilsV1.getTypeName(attributeVertex), attributeName); + LOG.debug("No RelationshipDef defined between {} and {} on attribute: {}", getTypeName(entityVertex), + getTypeName(attributeVertex), attributeName); } ret = mapObjectIdValue(ctx, context); @@ -728,6 +824,21 @@ public class EntityGraphMapper { return newElementsCreated; } + private boolean isRelationshipAttribute(AtlasAttribute attribute) { + boolean ret = false; + + if (attribute != null) { + AtlasStructType structType = attribute.getDefinedInType(); + String attributeName = attribute.getName(); + + if (structType instanceof AtlasEntityType) { + ret = ((AtlasEntityType) structType).hasRelationshipAttribute(attributeName); + } + } + + return ret; + } + private AtlasEdge createVertex(AtlasStruct struct, AtlasVertex referringVertex, String edgeLabel, EntityMutationContext context) throws AtlasBaseException { AtlasVertex vertex = createStructVertex(struct); @@ -767,6 +878,16 @@ public class EntityGraphMapper { return (AtlasStructType)objType; } + private AtlasEntityType getEntityType(String typeName) throws AtlasBaseException { + AtlasType objType = typeRegistry.getType(typeName); + + if (!(objType instanceof AtlasEntityType)) { + throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_INVALID, typeName); + } + + return (AtlasEntityType)objType; + } + private Object mapCollectionElementsToVertex(AttributeMutationContext ctx, EntityMutationContext context) throws AtlasBaseException { switch(ctx.getAttrType().getTypeCategory()) { case PRIMITIVE: @@ -918,8 +1039,8 @@ public class EntityGraphMapper { // Update edge if it exists AtlasVertex currentVertex = currentEdge.getInVertex(); - String currentEntityId = AtlasGraphUtilsV1.getIdFromVertex(currentVertex); - String newEntityId = AtlasGraphUtilsV1.getIdFromVertex(entityVertex); + String currentEntityId = getIdFromVertex(currentVertex); + String newEntityId = getIdFromVertex(entityVertex); AtlasEdge newEdge = currentEdge; if (!currentEntityId.equals(newEntityId)) { @@ -936,16 +1057,25 @@ public class EntityGraphMapper { return newEdge; } - private AtlasEdge updateRelationship(AtlasEdge currentEdge, final AtlasVertex entityVertex) throws AtlasBaseException { + private AtlasEdge updateRelationship(AtlasEdge currentEdge, final AtlasVertex newEntityVertex, + AtlasRelationshipEdgeDirection edgeDirection, AtlasAttribute attribute) + throws AtlasBaseException { if (LOG.isDebugEnabled()) { - LOG.debug("Updating entity reference using relationship {} for reference attribute {}", AtlasGraphUtilsV1.getTypeName(entityVertex)); + LOG.debug("Updating entity reference using relationship {} for reference attribute {}", getTypeName(newEntityVertex)); } - String currentEntityId = AtlasGraphUtilsV1.getIdFromVertex(currentEdge.getInVertex()); - String newEntityId = AtlasGraphUtilsV1.getIdFromVertex(entityVertex); - AtlasEdge ret = currentEdge; + // Max's manager updated from Jane to Julius (Max.manager --> Jane.subordinates) + // manager attribute (OUT direction), current manager vertex (Jane) (IN vertex) - if (!currentEntityId.equals(newEntityId)) { + // Max's mentor updated from John to Jane (John.mentee --> Max.mentor) + // mentor attribute (IN direction), current mentee vertex (John) (OUT vertex) + String currentEntityId = (edgeDirection == IN) ? getIdFromVertex(currentEdge.getOutVertex()) : + getIdFromVertex(currentEdge.getInVertex()); + + String newEntityId = getIdFromVertex(newEntityVertex); + AtlasEdge ret = currentEdge; + + if (!currentEntityId.equals(newEntityId) && newEntityVertex != null) { // create a new relationship edge to the new attribute vertex from the instance String relationshipName = AtlasGraphUtilsV1.getTypeName(currentEdge); @@ -953,7 +1083,8 @@ public class EntityGraphMapper { relationshipName = currentEdge.getLabel(); } - ret = getOrCreateRelationship(currentEdge.getOutVertex(), entityVertex, relationshipName); + ret = (edgeDirection == IN) ? getOrCreateRelationship(newEntityVertex, currentEdge.getInVertex(), relationshipName, attribute) : + getOrCreateRelationship(currentEdge.getOutVertex(), newEntityVertex, relationshipName, attribute); } return ret; @@ -983,8 +1114,7 @@ public class EntityGraphMapper { //Removes unused edges from the old collection, compared to the new collection private List<AtlasEdge> removeUnusedArrayEntries(AtlasAttribute attribute, List<AtlasEdge> currentEntries, List<AtlasEdge> newEntries) throws AtlasBaseException { if (CollectionUtils.isNotEmpty(currentEntries)) { - AtlasStructType entityType = attribute.getDefinedInType(); - AtlasType entryType = ((AtlasArrayType)attribute.getAttributeType()).getElementType(); + AtlasType entryType = ((AtlasArrayType) attribute.getAttributeType()).getElementType(); if (AtlasGraphUtilsV1.isReference(entryType)) { Collection<AtlasEdge> edgesToRemove = CollectionUtils.subtract(currentEntries, newEntries); @@ -993,7 +1123,8 @@ public class EntityGraphMapper { List<AtlasEdge> additionalElements = new ArrayList<>(); for (AtlasEdge edge : edgesToRemove) { - boolean deleted = deleteHandler.deleteEdgeReference(edge, entryType.getTypeCategory(), attribute.isOwnedRef(), true); + boolean deleted = deleteHandler.deleteEdgeReference(edge, entryType.getTypeCategory(), attribute.isOwnedRef(), + true, attribute.getRelationshipEdgeDirection()); if (!deleted) { additionalElements.add(edge); @@ -1021,7 +1152,7 @@ public class EntityGraphMapper { private AtlasEntityHeader constructHeader(AtlasEntity entity, final AtlasEntityType type, AtlasVertex vertex) { AtlasEntityHeader header = new AtlasEntityHeader(entity.getTypeName()); - header.setGuid(AtlasGraphUtilsV1.getIdFromVertex(vertex)); + header.setGuid(getIdFromVertex(vertex)); for (AtlasAttribute attribute : type.getUniqAttributes().values()) { header.setAttribute(attribute.getName(), entity.getAttribute(attribute.getName())); @@ -1148,7 +1279,7 @@ public class EntityGraphMapper { for (String classificationName : classificationNames) { try { - final String entityTypeName = GraphHelper.getTypeName(instanceVertex); + final String entityTypeName = getTypeName(instanceVertex); String relationshipLabel = GraphHelper.getTraitLabel(entityTypeName, classificationName); AtlasEdge edge = graphHelper.getEdgeForLabel(instanceVertex, relationshipLabel); if (edge != null) { @@ -1182,20 +1313,20 @@ public class EntityGraphMapper { } } - private AtlasEdge getOrCreateRelationship(AtlasVertex end1Vertex, AtlasVertex end2Vertex, String relationshipName) throws AtlasBaseException { - AtlasEdge ret = null; - AtlasObjectId end1 = new AtlasObjectId(AtlasGraphUtilsV1.getIdFromVertex(end1Vertex), AtlasGraphUtilsV1.getTypeName(end1Vertex)); - AtlasObjectId end2 = new AtlasObjectId(AtlasGraphUtilsV1.getIdFromVertex(end2Vertex), AtlasGraphUtilsV1.getTypeName(end2Vertex)); - AtlasRelationship relationship = relationshipStore.getOrCreate(new AtlasRelationship(relationshipName, end1, end2)); + private AtlasEdge getOrCreateRelationship(AtlasVertex end1Vertex, AtlasVertex end2Vertex, String relationshipName, AtlasAttribute attribute) throws AtlasBaseException { + AtlasEdge ret = null; + AtlasObjectId end1 = new AtlasObjectId(getIdFromVertex(end1Vertex), AtlasGraphUtilsV1.getTypeName(end1Vertex)); + AtlasObjectId end2 = new AtlasObjectId(getIdFromVertex(end2Vertex), AtlasGraphUtilsV1.getTypeName(end2Vertex)); + AtlasRelationship relationship = relationshipStore.getOrCreate(new AtlasRelationship(relationshipName, end1, end2)); // return newly created AtlasEdge - // if multiple edges are returned, compare using id to pick the right one + // if multiple edges are returned, compare using guid to pick the right one Iterator<AtlasEdge> outEdges = graphHelper.getOutGoingEdgesByLabel(end1Vertex, relationship.getLabel()); while (outEdges.hasNext()) { AtlasEdge edge = outEdges.next(); - if (AtlasGraphUtilsV1.getIdFromVertex(end2Vertex).equals(AtlasGraphUtilsV1.getIdFromVertex(edge.getInVertex()))) { + if (getIdFromVertex(end2Vertex).equals(getIdFromVertex(edge.getInVertex()))) { ret = edge; break; } @@ -1203,4 +1334,47 @@ public class EntityGraphMapper { return ret; } + + private boolean isRelationshipExists(AtlasVertex fromVertex, AtlasVertex toVertex, String edgeLabel) { + boolean ret = false; + Iterator<AtlasEdge> edges = graphHelper.getOutGoingEdgesByLabel(fromVertex, edgeLabel); + + while (edges != null && edges.hasNext()) { + AtlasEdge edge = edges.next(); + AtlasVertex inVertex = edge.getInVertex(); + + if (inVertex != null && StringUtils.equals(getIdFromVertex(inVertex), getIdFromVertex(toVertex))) { + ret = true; + } + } + + return ret; + } + + private void recordEntityUpdate(AtlasVertex vertex) { + AtlasObjectId objectId = new AtlasObjectId(GraphHelper.getGuid(vertex), GraphHelper.getTypeName(vertex)); + RequestContextV1 req = RequestContextV1.get(); + + if (!objectIdsContain(req.getUpdatedEntityIds(), objectId)) { + req.recordEntityUpdate(objectId); + } + } + + private boolean objectIdsContain(Collection<AtlasObjectId> objectIds, AtlasObjectId objectId) { + boolean ret = false; + + if (objectIds != null && objectIds.isEmpty()) { + ret = false; + + } else { + for (AtlasObjectId id : objectIds) { + if (StringUtils.equals(id.getGuid(), objectId.getGuid())) { + ret = true; + break; + } + } + } + + return ret; + } } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/02e4e86b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/EntityGraphRetriever.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/EntityGraphRetriever.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/EntityGraphRetriever.java index f4257be..31fc837 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/EntityGraphRetriever.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/EntityGraphRetriever.java @@ -69,6 +69,7 @@ import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_SHORT; import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_STRING; import static org.apache.atlas.repository.graph.GraphHelper.EDGE_LABEL_PREFIX; import static org.apache.atlas.repository.store.graph.v1.AtlasGraphUtilsV1.getIdFromVertex; +import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection; public final class EntityGraphRetriever { @@ -136,6 +137,10 @@ public final class EntityGraphRetriever { return toAtlasEntityHeader(entityVertex, Collections.<String>emptySet()); } + public AtlasEntityHeader toAtlasEntityHeader(AtlasVertex atlasVertex, Set<String> attributes) throws AtlasBaseException { + return atlasVertex != null ? mapVertexToAtlasEntityHeader(atlasVertex, attributes) : null; + } + private AtlasVertex getEntityVertex(String guid) throws AtlasBaseException { AtlasVertex ret = AtlasGraphUtilsV1.findByGuid(guid); @@ -188,7 +193,7 @@ public final class EntityGraphRetriever { mapAttributes(entityVertex, entity, entityExtInfo); - mapRelationshipAttributes(entityVertex, entity, entityExtInfo); + mapRelationshipAttributes(entityVertex, entity); mapClassifications(entityVertex, entity, entityExtInfo); } @@ -300,23 +305,6 @@ public final class EntityGraphRetriever { } } - private void mapRelationshipAttributes(AtlasVertex entityVertex, AtlasEntity entity, AtlasEntityExtInfo entityExtInfo) throws AtlasBaseException { - AtlasType objType = typeRegistry.getType(entity.getTypeName()); - - if (!(objType instanceof AtlasEntityType)) { - throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_INVALID, entity.getTypeName()); - } - - AtlasEntityType entityType = (AtlasEntityType) objType; - - for (AtlasAttribute attribute : entityType.getRelationshipAttributes().values()) { - - Object attrValue = mapVertexToRelationshipAttribute(entityVertex, entityType, attribute, entityExtInfo); - - entity.addRelationshipAttribute(attribute.getName(), attrValue); - } - } - public List<AtlasClassification> getClassifications(String guid) throws AtlasBaseException { AtlasVertex instanceVertex = AtlasGraphUtilsV1.findByGuid(guid); @@ -401,6 +389,7 @@ public final class EntityGraphRetriever { String vertexPropertyName = attribute.getQualifiedName(); String edgeLabel = EDGE_LABEL_PREFIX + vertexPropertyName; boolean isOwnedAttribute = attribute.isOwnedRef(); + AtlasRelationshipEdgeDirection edgeDirection = attribute.getRelationshipEdgeDirection(); if (LOG.isDebugEnabled()) { LOG.debug("Mapping vertex {} to atlas entity {}.{}", entityVertex, attribute.getDefinedInDef().getName(), attribute.getName()); @@ -417,13 +406,13 @@ public final class EntityGraphRetriever { ret = mapVertexToStruct(entityVertex, edgeLabel, null, entityExtInfo); break; case OBJECT_ID_TYPE: - ret = mapVertexToObjectId(entityVertex, edgeLabel, null, entityExtInfo, isOwnedAttribute); + ret = mapVertexToObjectId(entityVertex, edgeLabel, null, entityExtInfo, isOwnedAttribute, edgeDirection); break; case ARRAY: - ret = mapVertexToArray(entityVertex, (AtlasArrayType) attrType, vertexPropertyName, entityExtInfo, isOwnedAttribute); + ret = mapVertexToArray(entityVertex, (AtlasArrayType) attrType, vertexPropertyName, entityExtInfo, isOwnedAttribute, edgeDirection); break; case MAP: - ret = mapVertexToMap(entityVertex, (AtlasMapType) attrType, vertexPropertyName, entityExtInfo, isOwnedAttribute); + ret = mapVertexToMap(entityVertex, (AtlasMapType) attrType, vertexPropertyName, entityExtInfo, isOwnedAttribute, edgeDirection); break; case CLASSIFICATION: // do nothing @@ -433,51 +422,10 @@ public final class EntityGraphRetriever { return ret; } - private Object mapVertexToRelationshipAttribute(AtlasVertex entityVertex, AtlasEntityType entityType, AtlasAttribute attribute, - AtlasEntityExtInfo entityExtInfo) throws AtlasBaseException { - Object ret = null; - AtlasRelationshipDef relationshipDef = graphHelper.getRelationshipDef(entityVertex, entityType, attribute.getName()); - - if (relationshipDef == null) { - throw new AtlasBaseException(AtlasErrorCode.RELATIONSHIPDEF_INVALID, "relationshipDef is null"); - } - - AtlasRelationshipEndDef endDef1 = relationshipDef.getEndDef1(); - AtlasRelationshipEndDef endDef2 = relationshipDef.getEndDef2(); - AtlasEntityType endDef1Type = typeRegistry.getEntityTypeByName(endDef1.getType()); - AtlasEntityType endDef2Type = typeRegistry.getEntityTypeByName(endDef2.getType()); - AtlasRelationshipEndDef attributeEndDef = null; - - if (endDef1Type.isTypeOrSuperTypeOf(entityType.getTypeName()) && StringUtils.equals(endDef1.getName(), attribute.getName())) { - attributeEndDef = endDef1; - - } else if (endDef2Type.isTypeOrSuperTypeOf(entityType.getTypeName()) && StringUtils.equals(endDef2.getName(), attribute.getName())) { - attributeEndDef = endDef2; - } - - if (attributeEndDef == null) { - throw new AtlasBaseException(AtlasErrorCode.RELATIONSHIPDEF_INVALID, relationshipDef.toString()); - } - - String relationshipLabel = attribute.getRelationshipEdgeLabel(); - - switch (attributeEndDef.getCardinality()) { - case SINGLE: - ret = mapVertexToObjectId(entityVertex, relationshipLabel, null, entityExtInfo, attributeEndDef.getIsContainer()); - break; - - case LIST: - case SET: - ret = mapVertexToRelationshipArrayAttribute(entityVertex, (AtlasArrayType) attribute.getAttributeType(), relationshipLabel, - entityExtInfo, attributeEndDef.getIsContainer()); - break; - } - - return ret; - } - private Map<String, Object> mapVertexToMap(AtlasVertex entityVertex, AtlasMapType atlasMapType, final String propertyName, - AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute) throws AtlasBaseException { + AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute, + AtlasRelationshipEdgeDirection edgeDirection) throws AtlasBaseException { + List<String> mapKeys = GraphHelper.getListProperty(entityVertex, propertyName); if (CollectionUtils.isEmpty(mapKeys)) { @@ -496,7 +444,9 @@ public final class EntityGraphRetriever { final String edgeLabel = EDGE_LABEL_PREFIX + keyPropertyName; final Object keyValue = GraphHelper.getMapValueProperty(mapValueType, entityVertex, keyPropertyName); - Object mapValue = mapVertexToCollectionEntry(entityVertex, mapValueType, keyValue, edgeLabel, entityExtInfo, isOwnedAttribute); + Object mapValue = mapVertexToCollectionEntry(entityVertex, mapValueType, keyValue, edgeLabel, + entityExtInfo, isOwnedAttribute, edgeDirection); + if (mapValue != null) { ret.put(mapKey, mapValue); } @@ -506,7 +456,9 @@ public final class EntityGraphRetriever { } private List<Object> mapVertexToArray(AtlasVertex entityVertex, AtlasArrayType arrayType, String propertyName, - AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute) throws AtlasBaseException { + AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute, + AtlasRelationshipEdgeDirection edgeDirection) throws AtlasBaseException { + AtlasType arrayElementType = arrayType.getElementType(); List<Object> arrayElements = GraphHelper.getArrayElementsProperty(arrayElementType, entityVertex, propertyName); @@ -522,8 +474,8 @@ public final class EntityGraphRetriever { String edgeLabel = EDGE_LABEL_PREFIX + propertyName; for (Object element : arrayElements) { - Object arrValue = mapVertexToCollectionEntry(entityVertex, arrayElementType, element, - edgeLabel, entityExtInfo, isOwnedAttribute); + Object arrValue = mapVertexToCollectionEntry(entityVertex, arrayElementType, element, edgeLabel, + entityExtInfo, isOwnedAttribute, edgeDirection); if (arrValue != null) { arrValues.add(arrValue); @@ -533,42 +485,9 @@ public final class EntityGraphRetriever { return arrValues; } - private List<Object> mapVertexToRelationshipArrayAttribute(AtlasVertex entityVertex, AtlasArrayType arrayType, - String relationshipName, AtlasEntityExtInfo entityExtInfo, - boolean isContainer) throws AtlasBaseException { - - Iterator<AtlasEdge> relationshipEdges = graphHelper.getBothEdgesByLabel(entityVertex, relationshipName); - AtlasType arrayElementType = arrayType.getElementType(); - List<AtlasEdge> arrayElements = new ArrayList<>(); - - if (LOG.isDebugEnabled()) { - LOG.debug("Mapping array attribute {} for vertex {}", arrayElementType.getTypeName(), entityVertex); - } - - while (relationshipEdges.hasNext()) { - arrayElements.add(relationshipEdges.next()); - } - - if (CollectionUtils.isEmpty(arrayElements)) { - return null; - } - - List arrValues = new ArrayList(arrayElements.size()); - - for (Object element : arrayElements) { - Object arrValue = mapVertexToCollectionEntry(entityVertex, arrayElementType, element, relationshipName, - entityExtInfo, isContainer); - - if (arrValue != null) { - arrValues.add(arrValue); - } - } - - return arrValues; - } - - private Object mapVertexToCollectionEntry(AtlasVertex entityVertex, AtlasType arrayElement, Object value, String edgeLabel, - AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute) throws AtlasBaseException { + private Object mapVertexToCollectionEntry(AtlasVertex entityVertex, AtlasType arrayElement, Object value, + String edgeLabel, AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute, + AtlasRelationshipEdgeDirection edgeDirection) throws AtlasBaseException { Object ret = null; switch (arrayElement.getTypeCategory()) { @@ -587,7 +506,7 @@ public final class EntityGraphRetriever { break; case OBJECT_ID_TYPE: - ret = mapVertexToObjectId(entityVertex, edgeLabel, (AtlasEdge) value, entityExtInfo, isOwnedAttribute); + ret = mapVertexToObjectId(entityVertex, edgeLabel, (AtlasEdge) value, entityExtInfo, isOwnedAttribute, edgeDirection); break; default: @@ -646,11 +565,12 @@ public final class EntityGraphRetriever { } private AtlasObjectId mapVertexToObjectId(AtlasVertex entityVertex, String edgeLabel, AtlasEdge edge, - AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute) throws AtlasBaseException { + AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute, + AtlasRelationshipEdgeDirection edgeDirection) throws AtlasBaseException { AtlasObjectId ret = null; if (edge == null) { - edge = graphHelper.getEdgeForLabel(entityVertex, edgeLabel); + edge = graphHelper.getEdgeForLabel(entityVertex, edgeLabel, edgeDirection); } if (GraphHelper.elementExists(edge)) { @@ -697,7 +617,102 @@ public final class EntityGraphRetriever { return vertex != null && attribute != null ? mapVertexToAttribute(vertex, attribute, null) : null; } - public AtlasEntityHeader toAtlasEntityHeader(AtlasVertex atlasVertex, Set<String> attributes) throws AtlasBaseException { - return atlasVertex != null ? mapVertexToAtlasEntityHeader(atlasVertex, attributes) : null; + private void mapRelationshipAttributes(AtlasVertex entityVertex, AtlasEntity entity) throws AtlasBaseException { + AtlasEntityType entityType = typeRegistry.getEntityTypeByName(entity.getTypeName()); + + if (entityType == null) { + throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_INVALID, entity.getTypeName()); + } + + for (AtlasAttribute attribute : entityType.getRelationshipAttributes().values()) { + Object attrValue = mapVertexToRelationshipAttribute(entityVertex, entityType, attribute); + + entity.setRelationshipAttribute(attribute.getName(), attrValue); + } + } + + private Object mapVertexToRelationshipAttribute(AtlasVertex entityVertex, AtlasEntityType entityType, AtlasAttribute attribute) throws AtlasBaseException { + Object ret = null; + AtlasRelationshipDef relationshipDef = graphHelper.getRelationshipDef(entityVertex, entityType, attribute.getName()); + + if (relationshipDef == null) { + throw new AtlasBaseException(AtlasErrorCode.RELATIONSHIPDEF_INVALID, "relationshipDef is null"); + } + + AtlasRelationshipEndDef endDef1 = relationshipDef.getEndDef1(); + AtlasRelationshipEndDef endDef2 = relationshipDef.getEndDef2(); + AtlasEntityType endDef1Type = typeRegistry.getEntityTypeByName(endDef1.getType()); + AtlasEntityType endDef2Type = typeRegistry.getEntityTypeByName(endDef2.getType()); + AtlasRelationshipEndDef attributeEndDef = null; + + if (endDef1Type.isTypeOrSuperTypeOf(entityType.getTypeName()) && StringUtils.equals(endDef1.getName(), attribute.getName())) { + attributeEndDef = endDef1; + } else if (endDef2Type.isTypeOrSuperTypeOf(entityType.getTypeName()) && StringUtils.equals(endDef2.getName(), attribute.getName())) { + attributeEndDef = endDef2; + } + + if (attributeEndDef == null) { + throw new AtlasBaseException(AtlasErrorCode.RELATIONSHIPDEF_INVALID, relationshipDef.toString()); + } + + switch (attributeEndDef.getCardinality()) { + case SINGLE: + ret = mapRelatedVertexToObjectId(entityVertex, attribute); + break; + + case LIST: + case SET: + ret = mapRelationshipArrayAttribute(entityVertex, attribute); + break; + } + + return ret; + } + + private AtlasObjectId mapRelatedVertexToObjectId(AtlasVertex entityVertex, AtlasAttribute attribute) throws AtlasBaseException { + AtlasEdge edge = graphHelper.getEdgeForLabel(entityVertex, attribute.getRelationshipEdgeLabel(), attribute.getRelationshipEdgeDirection()); + + return mapRelatedVertexToObjectId(entityVertex, edge); + } + + private List<AtlasObjectId> mapRelationshipArrayAttribute(AtlasVertex entityVertex, AtlasAttribute attribute) throws AtlasBaseException { + List<AtlasObjectId> ret = new ArrayList<>(); + Iterator<AtlasEdge> edges = null; + + if (attribute.getRelationshipEdgeDirection() == AtlasRelationshipEdgeDirection.IN) { + edges = graphHelper.getIncomingEdgesByLabel(entityVertex, attribute.getRelationshipEdgeLabel()); + } else if (attribute.getRelationshipEdgeDirection() == AtlasRelationshipEdgeDirection.OUT) { + edges = graphHelper.getOutGoingEdgesByLabel(entityVertex, attribute.getRelationshipEdgeLabel()); + } + + if (edges != null) { + while (edges.hasNext()) { + AtlasEdge relationshipEdge = edges.next(); + + AtlasObjectId objectId = mapRelatedVertexToObjectId(entityVertex, relationshipEdge); + + ret.add(objectId); + } + } + + return ret; + } + + private AtlasObjectId mapRelatedVertexToObjectId(AtlasVertex entityVertex, AtlasEdge edge) throws AtlasBaseException { + AtlasObjectId ret = null; + + if (GraphHelper.elementExists(edge)) { + AtlasVertex referenceVertex = edge.getInVertex(); + + if (StringUtils.equals(getIdFromVertex(referenceVertex), getIdFromVertex(entityVertex))) { + referenceVertex = edge.getOutVertex(); + } + + if (referenceVertex != null) { + ret = new AtlasObjectId(GraphHelper.getGuid(referenceVertex), GraphHelper.getTypeName(referenceVertex)); + } + } + + return ret; } } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/02e4e86b/repository/src/test/java/org/apache/atlas/repository/impexp/ImportServiceTest.java ---------------------------------------------------------------------- diff --git a/repository/src/test/java/org/apache/atlas/repository/impexp/ImportServiceTest.java b/repository/src/test/java/org/apache/atlas/repository/impexp/ImportServiceTest.java index 404225c..7210799 100644 --- a/repository/src/test/java/org/apache/atlas/repository/impexp/ImportServiceTest.java +++ b/repository/src/test/java/org/apache/atlas/repository/impexp/ImportServiceTest.java @@ -18,6 +18,7 @@ package org.apache.atlas.repository.impexp; import com.google.inject.Inject; +import org.apache.atlas.RequestContextV1; import org.apache.atlas.TestModules; import org.apache.atlas.exception.AtlasBaseException; import org.apache.atlas.model.impexp.AtlasImportRequest; http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/02e4e86b/repository/src/test/java/org/apache/atlas/repository/impexp/ZipFileResourceTestUtils.java ---------------------------------------------------------------------- diff --git a/repository/src/test/java/org/apache/atlas/repository/impexp/ZipFileResourceTestUtils.java b/repository/src/test/java/org/apache/atlas/repository/impexp/ZipFileResourceTestUtils.java index d901731..82692cf 100644 --- a/repository/src/test/java/org/apache/atlas/repository/impexp/ZipFileResourceTestUtils.java +++ b/repository/src/test/java/org/apache/atlas/repository/impexp/ZipFileResourceTestUtils.java @@ -18,6 +18,7 @@ package org.apache.atlas.repository.impexp; import com.google.common.collect.Sets; +import org.apache.atlas.RequestContextV1; import org.apache.atlas.exception.AtlasBaseException; import org.apache.atlas.model.impexp.AtlasExportResult; import org.apache.atlas.model.impexp.AtlasImportRequest; @@ -149,6 +150,8 @@ public class ZipFileResourceTestUtils { AtlasExportResult exportResult = zipSource.getExportResult(); List<String> creationOrder = zipSource.getCreationOrder(); + RequestContextV1.clear(); + AtlasImportRequest request = getDefaultImportRequest(); AtlasImportResult result = runImportWithParameters(importService, request, zipSource); http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/02e4e86b/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreHardDeleteV1Test.java ---------------------------------------------------------------------- diff --git a/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreHardDeleteV1Test.java b/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreHardDeleteV1Test.java new file mode 100644 index 0000000..2c31140 --- /dev/null +++ b/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreHardDeleteV1Test.java @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.atlas.repository.store.graph.v1; + +import com.google.common.collect.ImmutableList; +import org.apache.atlas.TestModules; +import org.apache.atlas.model.instance.AtlasEntity; +import org.testng.annotations.Guice; + +import static org.apache.atlas.type.AtlasTypeUtil.getAtlasObjectId; + +/** + * Inverse reference update test with {@link HardDeleteHandlerV1} + */ +@Guice(modules = TestModules.HardDeleteModule.class) +public class AtlasRelationshipStoreHardDeleteV1Test extends AtlasRelationshipStoreV1Test { + + @Override + protected void verifyRelationshipAttributeUpdate_NonComposite_OneToMany(AtlasEntity jane) throws Exception { + // Max should have been removed from the subordinates list, leaving only John. + verifyRelationshipAttributeList(jane, "subordinates", ImmutableList.of(employeeNameIdMap.get("John"))); + } + + @Override + protected void verifyRelationshipAttributeUpdate_NonComposite_ManyToOne(AtlasEntity a1, AtlasEntity a2, + AtlasEntity a3, AtlasEntity b) { + + verifyRelationshipAttributeValue(a1, "oneB", null); + + verifyRelationshipAttributeValue(a2, "oneB", null); + + verifyRelationshipAttributeList(b, "manyA", ImmutableList.of(getAtlasObjectId(a3))); + } + + @Override + protected void verifyRelationshipAttributeUpdate_NonComposite_OneToOne(AtlasEntity a1, AtlasEntity b) { + verifyRelationshipAttributeValue(a1, "b", null); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/02e4e86b/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreSoftDeleteV1Test.java ---------------------------------------------------------------------- diff --git a/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreSoftDeleteV1Test.java b/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreSoftDeleteV1Test.java new file mode 100644 index 0000000..33ef8c0 --- /dev/null +++ b/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreSoftDeleteV1Test.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.atlas.repository.store.graph.v1; + +import com.google.common.collect.ImmutableList; +import org.apache.atlas.TestModules; +import org.apache.atlas.model.instance.AtlasEntity; +import org.testng.annotations.Guice; + +import static org.apache.atlas.type.AtlasTypeUtil.getAtlasObjectId; + + +/** + * Inverse reference update test with {@link SoftDeleteHandlerV1} + */ +@Guice(modules = TestModules.SoftDeleteModule.class) +public class AtlasRelationshipStoreSoftDeleteV1Test extends AtlasRelationshipStoreV1Test { + + @Override + protected void verifyRelationshipAttributeUpdate_NonComposite_OneToMany(AtlasEntity jane) throws Exception { + // Max is still in the subordinates list, as the edge still exists with state DELETED + verifyRelationshipAttributeList(jane, "subordinates", ImmutableList.of(employeeNameIdMap.get("John"), employeeNameIdMap.get("Max"))); + } + + @Override + protected void verifyRelationshipAttributeUpdate_NonComposite_ManyToOne(AtlasEntity a1, AtlasEntity a2, + AtlasEntity a3, AtlasEntity b) { + + verifyRelationshipAttributeValue(a1, "oneB", b.getGuid()); + + verifyRelationshipAttributeValue(a2, "oneB", b.getGuid()); + + verifyRelationshipAttributeList(b, "manyA", ImmutableList.of(getAtlasObjectId(a1), getAtlasObjectId(a2), getAtlasObjectId(a3))); + } + + @Override + protected void verifyRelationshipAttributeUpdate_NonComposite_OneToOne(AtlasEntity a1, AtlasEntity b) { + verifyRelationshipAttributeValue(a1, "b", b.getGuid()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/02e4e86b/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreV1Test.java ---------------------------------------------------------------------- diff --git a/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreV1Test.java b/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreV1Test.java index 6770223..31efe86 100644 --- a/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreV1Test.java +++ b/repository/src/test/java/org/apache/atlas/repository/store/graph/v1/AtlasRelationshipStoreV1Test.java @@ -17,18 +17,25 @@ */ package org.apache.atlas.repository.store.graph.v1; +import com.google.common.collect.ImmutableList; import org.apache.atlas.RequestContextV1; import org.apache.atlas.TestModules; -import org.apache.atlas.TestUtilsV2; +import org.apache.atlas.exception.AtlasBaseException; +import org.apache.atlas.model.instance.AtlasEntity; +import org.apache.atlas.model.instance.AtlasEntity.AtlasEntitiesWithExtInfo; import org.apache.atlas.model.instance.AtlasEntity.AtlasEntityWithExtInfo; +import org.apache.atlas.model.instance.AtlasEntityHeader; +import org.apache.atlas.model.instance.AtlasObjectId; +import org.apache.atlas.model.instance.EntityMutationResponse; import org.apache.atlas.model.typedef.AtlasTypesDef; import org.apache.atlas.repository.graph.AtlasGraphProvider; import org.apache.atlas.repository.graph.GraphBackedSearchIndexer; -import org.apache.atlas.repository.store.bootstrap.AtlasTypeDefStoreInitializer; import org.apache.atlas.repository.store.graph.AtlasEntityStore; import org.apache.atlas.repository.store.graph.AtlasRelationshipStore; import org.apache.atlas.store.AtlasTypeDefStore; +import org.apache.atlas.type.AtlasEntityType; import org.apache.atlas.type.AtlasTypeRegistry; +import org.apache.commons.collections.CollectionUtils; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeTest; @@ -36,11 +43,25 @@ import org.testng.annotations.Guice; import org.testng.annotations.Test; import javax.inject.Inject; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import static org.apache.atlas.TestRelationshipUtilsV2.EMPLOYEE_TYPE; +import static org.apache.atlas.TestRelationshipUtilsV2.getDepartmentEmployeeInstances; +import static org.apache.atlas.TestRelationshipUtilsV2.getDepartmentEmployeeTypes; +import static org.apache.atlas.TestRelationshipUtilsV2.getInverseReferenceTestTypes; +import static org.apache.atlas.TestUtils.NAME; +import static org.apache.atlas.type.AtlasTypeUtil.getAtlasObjectId; import static org.mockito.Mockito.mock; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; @Guice(modules = TestModules.TestOnlyModule.class) -public class AtlasRelationshipStoreV1Test { +public abstract class AtlasRelationshipStoreV1Test { @Inject AtlasTypeRegistry typeRegistry; @@ -56,32 +77,28 @@ public class AtlasRelationshipStoreV1Test { AtlasEntityStore entityStore; AtlasRelationshipStore relationshipStore; - AtlasEntityWithExtInfo dbEntity; - AtlasEntityWithExtInfo tblEntity; AtlasEntityChangeNotifier mockChangeNotifier = mock(AtlasEntityChangeNotifier.class); + protected Map<String, AtlasObjectId> employeeNameIdMap = new HashMap<>(); + @BeforeClass public void setUp() throws Exception { new GraphBackedSearchIndexer(typeRegistry); - AtlasTypesDef[] testTypesDefs = new AtlasTypesDef[] { TestUtilsV2.defineDeptEmployeeTypes(), - TestUtilsV2.defineHiveTypes() }; + // create employee relationship types + AtlasTypesDef employeeTypes = getDepartmentEmployeeTypes(); + typeDefStore.createTypesDef(employeeTypes); - for (AtlasTypesDef typesDef : testTypesDefs) { - AtlasTypesDef typesToCreate = AtlasTypeDefStoreInitializer.getTypesToCreate(typesDef, typeRegistry); + AtlasEntitiesWithExtInfo employeeInstances = getDepartmentEmployeeInstances(); + EntityMutationResponse response = entityStore.createOrUpdate(new AtlasEntityStream(employeeInstances), false); - if (!typesToCreate.isEmpty()) { - typeDefStore.createTypesDef(typesToCreate); - } + for (AtlasEntityHeader entityHeader : response.getCreatedEntities()) { + employeeNameIdMap.put((String) entityHeader.getAttribute(NAME), getAtlasObjectId(entityHeader)); } - dbEntity = TestUtilsV2.createDBEntityV2(); - tblEntity = TestUtilsV2.createTableEntityV2(dbEntity.getEntity()); - } - - @AfterClass - public void clear() { - AtlasGraphProvider.cleanup(); + init(); + AtlasTypesDef testTypes = getInverseReferenceTestTypes(); + typeDefStore.createTypesDef(testTypes); } @BeforeTest @@ -92,8 +109,384 @@ public class AtlasRelationshipStoreV1Test { RequestContextV1.clear(); } + @AfterClass + public void clear() { + AtlasGraphProvider.cleanup(); + } + + @Test + public void testDepartmentEmployeeEntitiesUsingRelationship() throws Exception { + AtlasObjectId hrId = employeeNameIdMap.get("hr"); + AtlasObjectId maxId = employeeNameIdMap.get("Max"); + AtlasObjectId johnId = employeeNameIdMap.get("John"); + AtlasObjectId juliusId = employeeNameIdMap.get("Julius"); + AtlasObjectId janeId = employeeNameIdMap.get("Jane"); + + AtlasEntity hrDept = getEntityFromStore(hrId.getGuid()); + AtlasEntity max = getEntityFromStore(maxId.getGuid()); + AtlasEntity john = getEntityFromStore(johnId.getGuid()); + AtlasEntity julius = getEntityFromStore(juliusId.getGuid()); + AtlasEntity jane = getEntityFromStore(janeId.getGuid()); + + // Department relationship attributes + List<AtlasObjectId> deptEmployees = toAtlasObjectIds(hrDept.getRelationshipAttribute("employees")); + assertNotNull(deptEmployees); + assertEquals(deptEmployees.size(), 4); + assertObjectIdsContains(deptEmployees, maxId); + assertObjectIdsContains(deptEmployees, johnId); + assertObjectIdsContains(deptEmployees, juliusId); + assertObjectIdsContains(deptEmployees, janeId); + + // Max employee validation + AtlasObjectId maxDepartmentId = toAtlasObjectId(max.getRelationshipAttribute("department")); + assertNotNull(maxDepartmentId); + assertObjectIdEquals(maxDepartmentId, hrId); + + AtlasObjectId maxManagerId = toAtlasObjectId(max.getRelationshipAttribute("manager")); + assertNotNull(maxManagerId); + assertObjectIdEquals(maxManagerId, janeId); + + AtlasObjectId maxMentorId = toAtlasObjectId(max.getRelationshipAttribute("mentor")); + assertNotNull(maxMentorId); + assertObjectIdEquals(maxMentorId, juliusId); + + List<AtlasObjectId> maxMenteesId = toAtlasObjectIds(max.getRelationshipAttribute("mentees")); + assertNotNull(maxMenteesId); + assertEquals(maxMenteesId.size(), 1); + assertObjectIdEquals(maxMenteesId.get(0), johnId); + + // John Employee validation + AtlasObjectId johnDepartmentId = toAtlasObjectId(john.getRelationshipAttribute("department")); + assertNotNull(johnDepartmentId); + assertObjectIdEquals(johnDepartmentId, hrId); + + AtlasObjectId johnManagerId = toAtlasObjectId(john.getRelationshipAttribute("manager")); + assertNotNull(johnManagerId); + assertObjectIdEquals(johnManagerId, janeId); + + AtlasObjectId johnMentorId = toAtlasObjectId(john.getRelationshipAttribute("mentor")); + assertNotNull(johnMentorId); + assertObjectIdEquals(johnMentorId, maxId); + + List<AtlasObjectId> johnMenteesId = toAtlasObjectIds(john.getRelationshipAttribute("mentees")); + assertNull(johnMenteesId); + + // Jane Manager validation + AtlasObjectId janeDepartmentId = toAtlasObjectId(jane.getRelationshipAttribute("department")); + assertNotNull(janeDepartmentId); + assertObjectIdEquals(janeDepartmentId, hrId); + + AtlasObjectId janeManagerId = toAtlasObjectId(jane.getRelationshipAttribute("manager")); + assertNull(janeManagerId); + + AtlasObjectId janeMentorId = toAtlasObjectId(jane.getRelationshipAttribute("mentor")); + assertNull(janeMentorId); + + List<AtlasObjectId> janeMenteesId = toAtlasObjectIds(jane.getRelationshipAttribute("mentees")); + assertNull(janeMenteesId); + + List<AtlasObjectId> janeSubordinateIds = toAtlasObjectIds(jane.getRelationshipAttribute("subordinates")); + assertNotNull(janeSubordinateIds); + assertEquals(janeSubordinateIds.size(), 2); + assertObjectIdsContains(janeSubordinateIds, maxId); + assertObjectIdsContains(janeSubordinateIds, johnId); + + // Julius Manager validation + AtlasObjectId juliusDepartmentId = toAtlasObjectId(julius.getRelationshipAttribute("department")); + assertNotNull(juliusDepartmentId); + assertObjectIdEquals(juliusDepartmentId, hrId); + + AtlasObjectId juliusManagerId = toAtlasObjectId(julius.getRelationshipAttribute("manager")); + assertNull(juliusManagerId); + + AtlasObjectId juliusMentorId = toAtlasObjectId(julius.getRelationshipAttribute("mentor")); + assertNull(juliusMentorId); + + List<AtlasObjectId> juliusMenteesId = toAtlasObjectIds(julius.getRelationshipAttribute("mentees")); + assertNotNull(juliusMenteesId); + assertEquals(juliusMenteesId.size(), 1); + assertObjectIdsContains(juliusMenteesId, maxId); + + List<AtlasObjectId> juliusSubordinateIds = toAtlasObjectIds(julius.getRelationshipAttribute("subordinates")); + assertNull(juliusSubordinateIds); + } + + @Test + public void testRelationshipAttributeUpdate_NonComposite_OneToMany() throws Exception { + AtlasObjectId maxId = employeeNameIdMap.get("Max"); + AtlasObjectId juliusId = employeeNameIdMap.get("Julius"); + AtlasObjectId janeId = employeeNameIdMap.get("Jane"); + + // Change Max's Employee.manager reference to Julius and apply the change as a partial update. + // This should also update Julius to add Max to the inverse Manager.subordinates reference. + AtlasEntity maxEntityForUpdate = new AtlasEntity(EMPLOYEE_TYPE); + maxEntityForUpdate.setRelationshipAttribute("manager", juliusId); + + AtlasEntityType employeeType = typeRegistry.getEntityTypeByName(EMPLOYEE_TYPE); + Map<String, Object> uniqAttributes = Collections.<String, Object>singletonMap("name", "Max"); + EntityMutationResponse updateResponse = entityStore.updateByUniqueAttributes(employeeType, uniqAttributes , new AtlasEntityWithExtInfo(maxEntityForUpdate)); + + List<AtlasEntityHeader> partialUpdatedEntities = updateResponse.getPartialUpdatedEntities(); + assertEquals(partialUpdatedEntities.size(), 3); + // 3 entities should have been updated: + // * Max to change the Employee.manager reference + // * Julius to add Max to Manager.subordinates + // * Jane to remove Max from Manager.subordinates + + AtlasEntitiesWithExtInfo updatedEntities = entityStore.getByIds(ImmutableList.of(maxId.getGuid(), juliusId.getGuid(), janeId.getGuid())); + + // Max's manager updated as Julius + AtlasEntity maxEntity = updatedEntities.getEntity(maxId.getGuid()); + verifyRelationshipAttributeValue(maxEntity, "manager", juliusId.getGuid()); + + // Max added to the subordinate list of Julius + AtlasEntity juliusEntity = updatedEntities.getEntity(juliusId.getGuid()); + verifyRelationshipAttributeList(juliusEntity, "subordinates", ImmutableList.of(maxId)); + + // Max removed from the subordinate list of Julius + AtlasEntity janeEntity = updatedEntities.getEntity(janeId.getGuid()); + + // Jane's subordinates list includes John and Max for soft delete + // Jane's subordinates list includes only John for hard delete + verifyRelationshipAttributeUpdate_NonComposite_OneToMany(janeEntity); + } + + @Test + public void testRelationshipAttributeUpdate_NonComposite_ManyToOne() throws Exception { + AtlasEntity a1 = new AtlasEntity("A"); + a1.setAttribute(NAME, "a1_name"); + + AtlasEntity a2 = new AtlasEntity("A"); + a2.setAttribute(NAME, "a2_name"); + + AtlasEntity a3 = new AtlasEntity("A"); + a3.setAttribute(NAME, "a3_name"); + + AtlasEntity b = new AtlasEntity("B"); + b.setAttribute(NAME, "b_name"); + + AtlasEntitiesWithExtInfo entitiesWithExtInfo = new AtlasEntitiesWithExtInfo(); + entitiesWithExtInfo.addEntity(a1); + entitiesWithExtInfo.addEntity(a2); + entitiesWithExtInfo.addEntity(a3); + entitiesWithExtInfo.addEntity(b); + entityStore.createOrUpdate(new AtlasEntityStream(entitiesWithExtInfo) , false); + + AtlasEntity bPartialUpdate = new AtlasEntity("B"); + bPartialUpdate.setRelationshipAttribute("manyA", ImmutableList.of(getAtlasObjectId(a1), getAtlasObjectId(a2))); + + init(); + EntityMutationResponse response = entityStore.updateByUniqueAttributes(typeRegistry.getEntityTypeByName("B"), + Collections.singletonMap(NAME, b.getAttribute(NAME)), + new AtlasEntityWithExtInfo(bPartialUpdate)); + // Verify 3 entities were updated: + // * set b.manyA reference to a1 and a2 + // * set inverse a1.oneB reference to b + // * set inverse a2.oneB reference to b + assertEquals(response.getPartialUpdatedEntities().size(), 3); + AtlasEntitiesWithExtInfo updatedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), a2.getGuid(), b.getGuid())); + + AtlasEntity a1Entity = updatedEntities.getEntity(a1.getGuid()); + verifyRelationshipAttributeValue(a1Entity, "oneB", b.getGuid()); + + AtlasEntity a2Entity = updatedEntities.getEntity(a2.getGuid()); + verifyRelationshipAttributeValue(a2Entity, "oneB", b.getGuid()); + + AtlasEntity bEntity = updatedEntities.getEntity(b.getGuid()); + verifyRelationshipAttributeList(bEntity, "manyA", ImmutableList.of(getAtlasObjectId(a1), getAtlasObjectId(a2))); + + + bPartialUpdate.setRelationshipAttribute("manyA", ImmutableList.of(getAtlasObjectId(a3))); + init(); + response = entityStore.updateByUniqueAttributes(typeRegistry.getEntityTypeByName("B"), + Collections.singletonMap(NAME, b.getAttribute(NAME)), + new AtlasEntityWithExtInfo(bPartialUpdate)); + // Verify 4 entities were updated: + // * set b.manyA reference to a3 + // * set inverse a3.oneB reference to b + // * disconnect inverse a1.oneB reference to b + // * disconnect inverse a2.oneB reference to b + assertEquals(response.getPartialUpdatedEntities().size(), 4); + init(); + + updatedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), a2.getGuid(), a3.getGuid(), b.getGuid())); + a1Entity = updatedEntities.getEntity(a1.getGuid()); + a2Entity = updatedEntities.getEntity(a2.getGuid()); + bEntity = updatedEntities.getEntity(b.getGuid()); + + AtlasEntity a3Entity = updatedEntities.getEntity(a3.getGuid()); + verifyRelationshipAttributeValue(a3Entity, "oneB", b.getGuid()); + + verifyRelationshipAttributeUpdate_NonComposite_ManyToOne(a1Entity, a2Entity, a3Entity, bEntity); + } + + @Test + public void testRelationshipAttributeUpdate_NonComposite_OneToOne() throws Exception { + AtlasEntity a1 = new AtlasEntity("A"); + a1.setAttribute(NAME, "a1_name"); + + AtlasEntity a2 = new AtlasEntity("A"); + a2.setAttribute(NAME, "a2_name"); + + AtlasEntity b = new AtlasEntity("B"); + b.setAttribute(NAME, "b_name"); + + AtlasEntitiesWithExtInfo entitiesWithExtInfo = new AtlasEntitiesWithExtInfo(); + entitiesWithExtInfo.addEntity(a1); + entitiesWithExtInfo.addEntity(a2); + entitiesWithExtInfo.addEntity(b); + + EntityMutationResponse response = entityStore.createOrUpdate(new AtlasEntityStream(entitiesWithExtInfo) , false); + + AtlasEntity partialUpdateB = new AtlasEntity("B"); + partialUpdateB.setRelationshipAttribute("a", getAtlasObjectId(a1)); + + init(); + AtlasEntityType bType = typeRegistry.getEntityTypeByName("B"); + + response = entityStore.updateByUniqueAttributes(bType, Collections.singletonMap(NAME, b.getAttribute(NAME)), new AtlasEntityWithExtInfo(partialUpdateB)); + List<AtlasEntityHeader> partialUpdatedEntitiesHeader = response.getPartialUpdatedEntities(); + // Verify 2 entities were updated: + // * set b.a reference to a1 + // * set inverse a1.b reference to b + assertEquals(partialUpdatedEntitiesHeader.size(), 2); + AtlasEntitiesWithExtInfo partialUpdatedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), b.getGuid())); + + AtlasEntity a1Entity = partialUpdatedEntities.getEntity(a1.getGuid()); + verifyRelationshipAttributeValue(a1Entity, "b", b.getGuid()); + + AtlasEntity bEntity = partialUpdatedEntities.getEntity(b.getGuid()); + verifyRelationshipAttributeValue(bEntity, "a", a1.getGuid()); + + init(); + + // Update b.a to reference a2. + partialUpdateB.setRelationshipAttribute("a", getAtlasObjectId(a2)); + response = entityStore.updateByUniqueAttributes(bType, Collections.<String, Object>singletonMap(NAME, b.getAttribute(NAME)), new AtlasEntityWithExtInfo(partialUpdateB)); + partialUpdatedEntitiesHeader = response.getPartialUpdatedEntities(); + // Verify 3 entities were updated: + // * set b.a reference to a2 + // * set a2.b reference to b + // * disconnect a1.b reference + assertEquals(partialUpdatedEntitiesHeader.size(), 3); + partialUpdatedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), a2.getGuid(), b.getGuid())); + + bEntity = partialUpdatedEntities.getEntity(b.getGuid()); + verifyRelationshipAttributeValue(bEntity, "a", a2.getGuid()); + + AtlasEntity a2Entity = partialUpdatedEntities.getEntity(a2.getGuid()); + verifyRelationshipAttributeValue(a2Entity, "b", b.getGuid()); + + a1Entity = partialUpdatedEntities.getEntity(a1.getGuid()); + verifyRelationshipAttributeUpdate_NonComposite_OneToOne(a1Entity, bEntity); + } + @Test - public void testDbTableRelationship() throws Exception { - // Add tests - in progress + public void testRelationshipAttributeUpdate_NonComposite_ManyToMany() throws Exception { + AtlasEntity a1 = new AtlasEntity("A"); + a1.setAttribute(NAME, "a1_name"); + + AtlasEntity a2 = new AtlasEntity("A"); + a2.setAttribute(NAME, "a2_name"); + + AtlasEntity a3 = new AtlasEntity("A"); + a3.setAttribute(NAME, "a3_name"); + + AtlasEntity b1 = new AtlasEntity("B"); + b1.setAttribute(NAME, "b1_name"); + + AtlasEntity b2 = new AtlasEntity("B"); + b2.setAttribute(NAME, "b2_name"); + + AtlasEntitiesWithExtInfo entitiesWithExtInfo = new AtlasEntitiesWithExtInfo(); + entitiesWithExtInfo.addEntity(a1); + entitiesWithExtInfo.addEntity(a2); + entitiesWithExtInfo.addEntity(a3); + entitiesWithExtInfo.addEntity(b1); + entitiesWithExtInfo.addEntity(b2); + entityStore.createOrUpdate(new AtlasEntityStream(entitiesWithExtInfo) , false); + + AtlasEntity b1PartialUpdate = new AtlasEntity("B"); + b1PartialUpdate.setRelationshipAttribute("manyToManyA", ImmutableList.of(getAtlasObjectId(a1), getAtlasObjectId(a2))); + + init(); + EntityMutationResponse response = entityStore.updateByUniqueAttributes(typeRegistry.getEntityTypeByName("B"), + Collections.singletonMap(NAME, b1.getAttribute(NAME)), + new AtlasEntityWithExtInfo(b1PartialUpdate)); + + List<AtlasEntityHeader> updatedEntityHeaders = response.getPartialUpdatedEntities(); + assertEquals(updatedEntityHeaders.size(), 3); + + AtlasEntitiesWithExtInfo updatedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), a2.getGuid(), b1.getGuid())); + + AtlasEntity b1Entity = updatedEntities.getEntity(b1.getGuid()); + verifyRelationshipAttributeList(b1Entity, "manyToManyA", ImmutableList.of(getAtlasObjectId(a1), getAtlasObjectId(a2))); + + AtlasEntity a1Entity = updatedEntities.getEntity(a1.getGuid()); + verifyRelationshipAttributeList(a1Entity, "manyB", ImmutableList.of(getAtlasObjectId(b1))); + + AtlasEntity a2Entity = updatedEntities.getEntity(a2.getGuid()); + verifyRelationshipAttributeList(a2Entity, "manyB", ImmutableList.of(getAtlasObjectId(b1))); + } + + protected abstract void verifyRelationshipAttributeUpdate_NonComposite_OneToOne(AtlasEntity a1, AtlasEntity b); + + protected abstract void verifyRelationshipAttributeUpdate_NonComposite_OneToMany(AtlasEntity entity) throws Exception; + + protected abstract void verifyRelationshipAttributeUpdate_NonComposite_ManyToOne(AtlasEntity a1, AtlasEntity a2, AtlasEntity a3, AtlasEntity b); + + private static void assertObjectIdsContains(List<AtlasObjectId> objectIds, AtlasObjectId objectId) { + assertTrue(CollectionUtils.isNotEmpty(objectIds)); + assertTrue(objectIds.contains(objectId)); + } + + private static void assertObjectIdEquals(AtlasObjectId objId1, AtlasObjectId objId2) { + assertTrue(objId1.equals(objId2)); + } + + private static List<AtlasObjectId> toAtlasObjectIds(Object objectIds) { + if (objectIds instanceof List) { + return (List<AtlasObjectId>) objectIds; + } + + return null; + } + + private static AtlasObjectId toAtlasObjectId(Object objectId) { + if (objectId instanceof AtlasObjectId) { + return (AtlasObjectId) objectId; + } + + return null; + } + + private AtlasEntity getEntityFromStore(String guid) throws AtlasBaseException { + AtlasEntityWithExtInfo entity = guid != null ? entityStore.getById(guid) : null; + + return entity != null ? entity.getEntity() : null; + } + + protected static void verifyRelationshipAttributeList(AtlasEntity entity, String relationshipAttrName, List<AtlasObjectId> expectedValues) { + Object refValue = entity.getRelationshipAttribute(relationshipAttrName); + assertTrue(refValue instanceof List); + + List<AtlasObjectId> refList = (List<AtlasObjectId>) refValue; + assertEquals(refList.size(), expectedValues.size()); + + if (expectedValues.size() > 0) { + assertTrue(refList.containsAll(expectedValues)); + } + } + + protected static void verifyRelationshipAttributeValue(AtlasEntity entity, String relationshipAttrName, String expectedGuid) { + Object refValue = entity.getRelationshipAttribute(relationshipAttrName); + if (expectedGuid == null) { + assertNull(refValue); + } + else { + assertTrue(refValue instanceof AtlasObjectId); + AtlasObjectId referencedObjectId = (AtlasObjectId) refValue; + assertEquals(referencedObjectId.getGuid(), expectedGuid); + } } -} +} \ No newline at end of file