content: /**
   * 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
   * <p>
   * http://www.apache.org/licenses/LICENSE-2.0
   * <p>
   * 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.v2;
  
  import com.fasterxml.jackson.core.type.TypeReference;
  import org.apache.atlas.AtlasErrorCode;
  import org.apache.atlas.RequestContext;
  import org.apache.atlas.exception.AtlasBaseException;
  import org.apache.atlas.model.TimeBoundary;
  import org.apache.atlas.model.glossary.AtlasGlossaryCategory;
  import org.apache.atlas.model.glossary.enums.AtlasTermAssignmentStatus;
  import org.apache.atlas.model.glossary.relations.AtlasTermAssignmentHeader;
  import org.apache.atlas.model.instance.AtlasClassification;
  import org.apache.atlas.model.instance.AtlasEntity;
  import org.apache.atlas.model.instance.AtlasEntity.AtlasEntitiesWithExtInfo;
  import org.apache.atlas.model.instance.AtlasEntity.AtlasEntityExtInfo;
  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.AtlasRelatedObjectId;
  import org.apache.atlas.model.instance.AtlasRelationship;
  import org.apache.atlas.model.instance.AtlasRelationship.AtlasRelationshipWithExtInfo;
  import org.apache.atlas.model.instance.AtlasRelationshipHeader;
  import org.apache.atlas.model.instance.AtlasStruct;
  import org.apache.atlas.model.typedef.AtlasRelationshipDef;
  import org.apache.atlas.model.typedef.AtlasRelationshipDef.PropagateTags;
  import org.apache.atlas.model.typedef.AtlasRelationshipEndDef;
  import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef;
  import org.apache.atlas.repository.Constants;
  import org.apache.atlas.repository.graph.GraphHelper;
  import org.apache.atlas.repository.graphdb.AtlasEdge;
  import org.apache.atlas.repository.graphdb.AtlasEdgeDirection;
  import org.apache.atlas.repository.graphdb.AtlasElement;
  import org.apache.atlas.repository.graphdb.AtlasGraph;
  import org.apache.atlas.repository.graphdb.AtlasVertex;
  import org.apache.atlas.type.AtlasArrayType;
  import org.apache.atlas.type.AtlasBuiltInTypes.AtlasObjectIdType;
  import org.apache.atlas.type.AtlasBusinessMetadataType.AtlasBusinessAttribute;
  import org.apache.atlas.type.AtlasEntityType;
  import org.apache.atlas.type.AtlasMapType;
  import org.apache.atlas.type.AtlasRelationshipType;
  import org.apache.atlas.type.AtlasStructType;
  import org.apache.atlas.type.AtlasStructType.AtlasAttribute;
  import org.apache.atlas.type.AtlasType;
  import org.apache.atlas.type.AtlasTypeRegistry;
  import org.apache.atlas.type.AtlasTypeUtil;
  import org.apache.atlas.utils.AtlasEntityUtil;
  import org.apache.atlas.utils.AtlasJson;
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.collections.MapUtils;
  import org.apache.commons.lang3.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.stereotype.Component;
  
  import javax.inject.Inject;
  
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.util.ArrayDeque;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Queue;
  import java.util.Set;
  import java.util.stream.Collectors;
  
  import static org.apache.atlas.glossary.GlossaryUtils.TERM_ASSIGNMENT_ATTR_CONFIDENCE;
  import static org.apache.atlas.glossary.GlossaryUtils.TERM_ASSIGNMENT_ATTR_CREATED_BY;
  import static org.apache.atlas.glossary.GlossaryUtils.TERM_ASSIGNMENT_ATTR_DESCRIPTION;
  import static org.apache.atlas.glossary.GlossaryUtils.TERM_ASSIGNMENT_ATTR_EXPRESSION;
  import static org.apache.atlas.glossary.GlossaryUtils.TERM_ASSIGNMENT_ATTR_SOURCE;
  import static org.apache.atlas.glossary.GlossaryUtils.TERM_ASSIGNMENT_ATTR_STATUS;
  import static org.apache.atlas.glossary.GlossaryUtils.TERM_ASSIGNMENT_ATTR_STEWARD;
  import static org.apache.atlas.model.instance.AtlasRelationship.Status.ACTIVE;
  import static org.apache.atlas.model.instance.AtlasRelationship.Status.DELETED;
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_BIGDECIMAL;
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_BIGINTEGER;
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_BOOLEAN;
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_BYTE;
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_DATE;
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_DOUBLE;
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_FLOAT;
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_INT;
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_LONG;
  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.model.typedef.AtlasRelationshipDef.PropagateTags.NONE;
  import static org.apache.atlas.model.typedef.AtlasRelationshipDef.PropagateTags.ONE_TO_TWO;
  import static org.apache.atlas.model.typedef.AtlasRelationshipDef.PropagateTags.TWO_TO_ONE;
  import static org.apache.atlas.repository.Constants.CLASSIFICATION_ENTITY_GUID;
  import static org.apache.atlas.repository.Constants.CLASSIFICATION_LABEL;
  import static org.apache.atlas.repository.Constants.CLASSIFICATION_VALIDITY_PERIODS_KEY;
  import static org.apache.atlas.repository.Constants.TERM_ASSIGNMENT_LABEL;
  import static org.apache.atlas.repository.graph.GraphHelper.getAdjacentEdgesByLabel;
  import static org.apache.atlas.repository.graph.GraphHelper.getAllClassificationEdges;
  import static org.apache.atlas.repository.graph.GraphHelper.getAllPropagatedEntityVertices;
  import static org.apache.atlas.repository.graph.GraphHelper.getAllTraitNames;
  import static org.apache.atlas.repository.graph.GraphHelper.getArrayElementsProperty;
  import static org.apache.atlas.repository.graph.GraphHelper.getBlockedClassificationIds;
  import static org.apache.atlas.repository.graph.GraphHelper.getClassificationEntityGuid;
  import static org.apache.atlas.repository.graph.GraphHelper.getClassificationEntityStatus;
  import static org.apache.atlas.repository.graph.GraphHelper.getCustomAttributes;
  import static org.apache.atlas.repository.graph.GraphHelper.getEdgeStatus;
  import static org.apache.atlas.repository.graph.GraphHelper.getGuid;
  import static org.apache.atlas.repository.graph.GraphHelper.getIncomingEdgesByLabel;
  import static org.apache.atlas.repository.graph.GraphHelper.getLabels;
  import static org.apache.atlas.repository.graph.GraphHelper.getOutGoingEdgesByLabel;
  import static org.apache.atlas.repository.graph.GraphHelper.getPrimitiveMap;
  import static org.apache.atlas.repository.graph.GraphHelper.getPropagatableClassifications;
  import static org.apache.atlas.repository.graph.GraphHelper.getPropagateTags;
  import static org.apache.atlas.repository.graph.GraphHelper.getReferenceMap;
  import static org.apache.atlas.repository.graph.GraphHelper.getRelationshipGuid;
  import static org.apache.atlas.repository.graph.GraphHelper.getRemovePropagations;
  import static org.apache.atlas.repository.graph.GraphHelper.getStatus;
  import static org.apache.atlas.repository.graph.GraphHelper.getTypeName;
  import static org.apache.atlas.repository.graph.GraphHelper.isEntityIncomplete;
  import static org.apache.atlas.repository.graph.GraphHelper.isPropagationEnabled;
  import static org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2.getIdFromVertex;
  import static org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2.isReference;
  import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection;
  import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.BOTH;
  import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.IN;
  import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.OUT;
  import static org.apache.atlas.type.Constants.PENDING_TASKS_PROPERTY_KEY;
  
  @Component
  public class EntityGraphRetriever {
      private static final Logger LOG = LoggerFactory.getLogger(EntityGraphRetriever.class);
  
      public static final String TERM_RELATION_NAME = "AtlasGlossarySemanticAssignment";
      public static final String NAME               = "name";
      public static final String DISPLAY_NAME       = "displayName";
      public static final String DESCRIPTION        = "description";
      public static final String OWNER              = "owner";
      public static final String CREATE_TIME        = "createTime";
      public static final String QUALIFIED_NAME     = "qualifiedName";
  
      private static final String GLOSSARY_TERM_DISPLAY_NAME_ATTR        = "AtlasGlossaryTerm.name";
      private static final String GLOSSARY_TERM_QUALIFIED_NAME_ATTR      = "AtlasGlossaryTerm.qualifiedName";
      private static final String GLOSSARY_CATEGORY_HIERARCHY_EDGE_LABEL = "r:AtlasGlossaryCategoryHierarchyLink";
      private static final String GLOSSARY_CATEGORY_TYPE_NAME            = AtlasGlossaryCategory.class.getSimpleName();
      private static final String PARENT_GLOSSARY_CATEGORY_GUID          = "parentCategoryGuid";
  
      private static final TypeReference<List<TimeBoundary>> TIME_BOUNDARIES_LIST_TYPE = new TypeReference<List<TimeBoundary>>() {};
  
      private final GraphHelper       graphHelper;
      private final AtlasTypeRegistry typeRegistry;
      private final boolean           ignoreRelationshipAttr;
      private final AtlasGraph        graph;
  
      @Inject
      public EntityGraphRetriever(AtlasGraph graph, AtlasTypeRegistry typeRegistry) {
          this(graph, typeRegistry, false);
      }
  
      public EntityGraphRetriever(AtlasGraph graph, AtlasTypeRegistry typeRegistry, boolean ignoreRelationshipAttr) {
          this.graph                  = graph;
          this.graphHelper            = new GraphHelper(graph);
          this.typeRegistry           = typeRegistry;
          this.ignoreRelationshipAttr = ignoreRelationshipAttr;
      }
  
      public static Object mapVertexToPrimitive(AtlasElement entityVertex, final String vertexPropertyName, AtlasAttributeDef attrDef) {
          Object ret = null;
  
          if (AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, Object.class) == null) {
              return null;
          }
  
          switch (attrDef.getTypeName().toLowerCase()) {
              case ATLAS_TYPE_STRING:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, String.class);
                  break;
              case ATLAS_TYPE_SHORT:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, Short.class);
                  break;
              case ATLAS_TYPE_INT:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, Integer.class);
                  break;
              case ATLAS_TYPE_BIGINTEGER:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, BigInteger.class);
                  break;
              case ATLAS_TYPE_BOOLEAN:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, Boolean.class);
                  break;
              case ATLAS_TYPE_BYTE:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, Byte.class);
                  break;
              case ATLAS_TYPE_LONG:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, Long.class);
                  break;
              case ATLAS_TYPE_FLOAT:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, Float.class);
                  break;
              case ATLAS_TYPE_DOUBLE:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, Double.class);
                  break;
              case ATLAS_TYPE_BIGDECIMAL:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, BigDecimal.class);
                  break;
              case ATLAS_TYPE_DATE:
                  ret = new Date(AtlasGraphUtilsV2.getEncodedProperty(entityVertex, vertexPropertyName, Long.class));
                  break;
              default:
                  break;
          }
  
          return ret;
      }
  
      public AtlasEntity toAtlasEntity(String guid, boolean includeReferences) throws AtlasBaseException {
          return mapVertexToAtlasEntity(getEntityVertex(guid), null, false, includeReferences);
      }
  
      public AtlasEntity toAtlasEntity(String guid) throws AtlasBaseException {
          return toAtlasEntity(getEntityVertex(guid));
      }
  
      public AtlasEntity toAtlasEntity(AtlasObjectId objId) throws AtlasBaseException {
          return toAtlasEntity(getEntityVertex(objId));
      }
  
      public AtlasEntity toAtlasEntity(AtlasVertex entityVertex) throws AtlasBaseException {
          return mapVertexToAtlasEntity(entityVertex, null);
      }
  
      public AtlasEntityWithExtInfo toAtlasEntityWithExtInfo(String guid) throws AtlasBaseException {
          return toAtlasEntityWithExtInfo(getEntityVertex(guid));
      }
  
      public AtlasEntityWithExtInfo toAtlasEntityWithExtInfo(String guid, boolean isMinExtInfo) throws AtlasBaseException {
          return toAtlasEntityWithExtInfo(getEntityVertex(guid), isMinExtInfo);
      }
  
      public AtlasEntityWithExtInfo toAtlasEntityWithExtInfo(AtlasObjectId objId) throws AtlasBaseException {
          return toAtlasEntityWithExtInfo(getEntityVertex(objId));
      }
  
      public AtlasEntityWithExtInfo toAtlasEntityWithExtInfo(AtlasVertex entityVertex) throws AtlasBaseException {
          return toAtlasEntityWithExtInfo(entityVertex, false);
      }
  
      public AtlasEntityWithExtInfo toAtlasEntityWithExtInfo(AtlasVertex entityVertex, boolean isMinExtInfo) throws AtlasBaseException {
          AtlasEntityExtInfo     entityExtInfo = new AtlasEntityExtInfo();
          AtlasEntity            entity        = mapVertexToAtlasEntity(entityVertex, entityExtInfo, isMinExtInfo);
          AtlasEntityWithExtInfo ret           = new AtlasEntityWithExtInfo(entity, entityExtInfo);
  
          ret.compact();
  
          return ret;
      }
  
      public AtlasEntitiesWithExtInfo toAtlasEntitiesWithExtInfo(List<String> guids) throws AtlasBaseException {
          return toAtlasEntitiesWithExtInfo(guids, false);
      }
  
      public AtlasEntityHeader toAtlasEntityHeader(String guid) throws AtlasBaseException {
          return toAtlasEntityHeader(getEntityVertex(guid));
      }
  
      public AtlasEntityHeader toAtlasEntityHeader(String guid, Set<String> attributes) throws AtlasBaseException {
          return toAtlasEntityHeader(getEntityVertex(guid), attributes);
      }
  
      public AtlasEntityHeader toAtlasEntityHeader(AtlasVertex entityVertex) throws AtlasBaseException {
          return toAtlasEntityHeader(entityVertex, Collections.emptySet());
      }
  
      public AtlasEntityHeader toAtlasEntityHeader(AtlasVertex atlasVertex, Set<String> attributes) throws AtlasBaseException {
          return atlasVertex != null ? mapVertexToAtlasEntityHeader(atlasVertex, attributes) : null;
      }
  
      public AtlasEntityHeader toAtlasEntityHeaderWithClassifications(String guid) throws AtlasBaseException {
          return toAtlasEntityHeaderWithClassifications(getEntityVertex(guid), Collections.emptySet());
      }
  
      public AtlasEntityHeader toAtlasEntityHeaderWithClassifications(AtlasVertex entityVertex) throws AtlasBaseException {
          return toAtlasEntityHeaderWithClassifications(entityVertex, Collections.emptySet());
      }
  
      public AtlasEntityHeader toAtlasEntityHeaderWithClassifications(AtlasVertex entityVertex, Set<String> attributes) throws AtlasBaseException {
          AtlasEntityHeader ret = toAtlasEntityHeader(entityVertex, attributes);
  
          ret.setClassifications(getAllClassifications(entityVertex));
  
          return ret;
      }
  
      public Map<String, Map<String, Object>> getBusinessMetadata(AtlasVertex entityVertex) throws AtlasBaseException {
          Map<String, Map<String, Object>>                 ret            = null;
          String                                           entityTypeName = getTypeName(entityVertex);
          AtlasEntityType                                  entityType     = typeRegistry.getEntityTypeByName(entityTypeName);
          Map<String, Map<String, AtlasBusinessAttribute>> entityTypeBm   = entityType != null ? entityType.getBusinessAttributes() : null;
  
          if (MapUtils.isNotEmpty(entityTypeBm)) {
              for (Map.Entry<String, Map<String, AtlasBusinessAttribute>> entry : entityTypeBm.entrySet()) {
                  String                              bmName        = entry.getKey();
                  Map<String, AtlasBusinessAttribute> bmAttributes  = entry.getValue();
                  Map<String, Object>                 entityBmAttrs = null;
  
                  for (AtlasBusinessAttribute bmAttribute : bmAttributes.values()) {
                      Object bmAttrValue = mapVertexToAttribute(entityVertex, bmAttribute, null, false, false);
  
                      if (bmAttrValue != null) {
                          if (ret == null) {
                              ret = new HashMap<>();
                          }
  
                          if (entityBmAttrs == null) {
                              entityBmAttrs = new HashMap<>();
  
                              ret.put(bmName, entityBmAttrs);
                          }
  
                          entityBmAttrs.put(bmAttribute.getName(), bmAttrValue);
                      }
                  }
              }
          }
  
          return ret;
      }
  
      public Object getEntityAttribute(AtlasVertex entityVertex, AtlasAttribute attribute) {
          Object ret = null;
  
          try {
              ret = getVertexAttributeIgnoreInactive(entityVertex, attribute);
          } catch (AtlasBaseException excp) {
              // ignore
          }
  
          return ret;
      }
  
      public AtlasObjectId toAtlasObjectId(AtlasVertex entityVertex) throws AtlasBaseException {
          AtlasObjectId   ret        = null;
          String          typeName   = entityVertex.getProperty(Constants.TYPE_NAME_PROPERTY_KEY, String.class);
          AtlasEntityType entityType = typeRegistry.getEntityTypeByName(typeName);
  
          if (entityType != null) {
              Map<String, Object> uniqueAttributes = new HashMap<>();
  
              for (AtlasAttribute attribute : entityType.getUniqAttributes().values()) {
                  Object attrValue = getVertexAttribute(entityVertex, attribute);
  
                  if (attrValue != null) {
                      uniqueAttributes.put(attribute.getName(), attrValue);
                  }
              }
  
              ret = new AtlasObjectId(entityVertex.getProperty(Constants.GUID_PROPERTY_KEY, String.class), typeName, uniqueAttributes);
          }
  
          return ret;
      }
  
      public AtlasObjectId toAtlasObjectId(AtlasEntity entity) {
          AtlasObjectId   ret        = null;
          AtlasEntityType entityType = typeRegistry.getEntityTypeByName(entity.getTypeName());
  
          if (entityType != null) {
              Map<String, Object> uniqueAttributes = new HashMap<>();
  
              for (String attributeName : entityType.getUniqAttributes().keySet()) {
                  Object attrValue = entity.getAttribute(attributeName);
  
                  if (attrValue != null) {
                      uniqueAttributes.put(attributeName, attrValue);
                  }
              }
  
              ret = new AtlasObjectId(entity.getGuid(), entity.getTypeName(), uniqueAttributes);
          }
  
          return ret;
      }
  
      public AtlasObjectId toAtlasObjectIdWithoutGuid(AtlasEntity entity) {
          AtlasObjectId objectId = toAtlasObjectId(entity);
  
          objectId.setGuid(null);
  
          return objectId;
      }
  
      public AtlasClassification toAtlasClassification(AtlasVertex classificationVertex) throws AtlasBaseException {
          AtlasClassification ret                = null;
          String              classificationName = getTypeName(classificationVertex);
  
          if (StringUtils.isEmpty(classificationName)) {
              LOG.warn("Ignoring invalid classification vertex: {}", AtlasGraphUtilsV2.toString(classificationVertex));
          } else {
              ret = new AtlasClassification(classificationName);
  
              ret.setEntityGuid(AtlasGraphUtilsV2.getEncodedProperty(classificationVertex, CLASSIFICATION_ENTITY_GUID, String.class));
              ret.setEntityStatus(getClassificationEntityStatus(classificationVertex));
              ret.setPropagate(isPropagationEnabled(classificationVertex));
              ret.setRemovePropagationsOnEntityDelete(getRemovePropagations(classificationVertex));
  
              String strValidityPeriods = AtlasGraphUtilsV2.getEncodedProperty(classificationVertex, CLASSIFICATION_VALIDITY_PERIODS_KEY, String.class);
  
              if (strValidityPeriods != null) {
                  ret.setValidityPeriods(AtlasJson.fromJson(strValidityPeriods, TIME_BOUNDARIES_LIST_TYPE));
              }
  
              mapAttributes(classificationVertex, ret, null);
          }
  
          return ret;
      }
  
      public AtlasVertex getReferencedEntityVertex(AtlasEdge edge, AtlasRelationshipEdgeDirection relationshipDirection, AtlasVertex parentVertex) {
          AtlasVertex entityVertex = null;
  
          if (relationshipDirection == OUT) {
              entityVertex = edge.getInVertex();
          } else if (relationshipDirection == IN) {
              entityVertex = edge.getOutVertex();
          } else if (relationshipDirection == BOTH) {
              // since relationship direction is BOTH, edge direction can be inward or outward
              // compare with parent entity vertex and pick the right reference vertex
              if (StringUtils.equals(GraphHelper.getGuid(parentVertex), GraphHelper.getGuid(edge.getOutVertex()))) {
                  entityVertex = edge.getInVertex();
              } else {
                  entityVertex = edge.getOutVertex();
              }
          }
  
          return entityVertex;
      }
  
      public AtlasVertex getEntityVertex(String guid) throws AtlasBaseException {
          AtlasVertex ret = AtlasGraphUtilsV2.findByGuid(this.graph, guid);
  
          if (ret == null) {
              throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, guid);
          }
  
          if (StringUtils.isEmpty(GraphHelper.getTypeName(ret))) {
              throw new AtlasBaseException(AtlasErrorCode.NO_TYPE_NAME_ON_VERTEX, guid);
          }
  
          return ret;
      }
  
      public AtlasEntitiesWithExtInfo toAtlasEntitiesWithExtInfo(List<String> guids, boolean isMinExtInfo) throws AtlasBaseException {
          AtlasEntitiesWithExtInfo ret = new AtlasEntitiesWithExtInfo();
  
          for (String guid : guids) {
              AtlasVertex vertex = getEntityVertex(guid);
  
              AtlasEntity entity = mapVertexToAtlasEntity(vertex, ret, isMinExtInfo);
  
              ret.addEntity(entity);
          }
  
          ret.compact();
  
          return ret;
      }
  
      public List<AtlasVertex> findAllConnectedVertices(AtlasVertex vertex) {
          return graph.getAllEdgesVertices(vertex);
      }
  
      public Map<String, Object> getEntityUniqueAttribute(AtlasVertex entityVertex) throws AtlasBaseException {
          Map<String, Object> ret        = null;
          String              typeName   = AtlasGraphUtilsV2.getTypeName(entityVertex);
          AtlasEntityType     entityType = typeRegistry.getEntityTypeByName(typeName);
  
          if (entityType != null && MapUtils.isNotEmpty(entityType.getUniqAttributes())) {
              for (AtlasAttribute attribute : entityType.getUniqAttributes().values()) {
                  Object val = mapVertexToAttribute(entityVertex, attribute, null, false);
  
                  if (val != null) {
                      if (ret == null) {
                          ret = new HashMap<>();
                      }
  
                      ret.put(attribute.getName(), val);
                  }
              }
          }
  
          return ret;
      }
  
      public AtlasEntitiesWithExtInfo getEntitiesByUniqueAttributes(String typeName, List<Map<String, Object>> uniqueAttributesList, boolean isMinExtInfo) throws AtlasBaseException {
          AtlasEntitiesWithExtInfo ret        = new AtlasEntitiesWithExtInfo();
          AtlasEntityType          entityType = typeRegistry.getEntityTypeByName(typeName);
  
          if (entityType != null) {
              for (Map<String, Object> uniqAttributes : uniqueAttributesList) {
                  try {
                      AtlasVertex vertex = AtlasGraphUtilsV2.getVertexByUniqueAttributes(this.graph, entityType, uniqAttributes);
  
                      if (vertex != null) {
                          AtlasEntity entity = mapVertexToAtlasEntity(vertex, ret, isMinExtInfo);
  
                          ret.addEntity(entity);
                      }
                  } catch (AtlasBaseException e) {
                      if (e.getAtlasErrorCode() != AtlasErrorCode.INSTANCE_BY_UNIQUE_ATTRIBUTE_NOT_FOUND) {
                          throw e;
                      }
                  }
              }
          }
  
          ret.compact();
  
          return ret;
      }
  
      public void evaluateClassificationPropagation(AtlasVertex classificationVertex, List<AtlasVertex> entitiesToAddPropagation, List<AtlasVertex> entitiesToRemovePropagation) {
          if (classificationVertex != null) {
              String            entityGuid         = getClassificationEntityGuid(classificationVertex);
              AtlasVertex       entityVertex       = AtlasGraphUtilsV2.findByGuid(this.graph, entityGuid);
              String            classificationId   = classificationVertex.getIdForDisplay();
              List<AtlasVertex> propagatedEntities = getAllPropagatedEntityVertices(classificationVertex);
              List<AtlasVertex> impactedEntities   = getImpactedVerticesV2(entityVertex, null, classificationId);
  
              List<AtlasVertex> entityVertices = (List<AtlasVertex>) CollectionUtils.subtract(propagatedEntities, impactedEntities);
  
              if (CollectionUtils.isNotEmpty(entityVertices)) {
                  entitiesToRemovePropagation.addAll(entityVertices);
              }
  
              entityVertices = (List<AtlasVertex>) CollectionUtils.subtract(impactedEntities, propagatedEntities);
  
              if (CollectionUtils.isNotEmpty(entityVertices)) {
                  entitiesToAddPropagation.addAll(entityVertices);
              }
          }
      }
  
      public Map<AtlasVertex, List<AtlasVertex>> getClassificationPropagatedEntitiesMapping(List<AtlasVertex> classificationVertices) {
          return getClassificationPropagatedEntitiesMapping(classificationVertices, null);
      }
  
      public Map<AtlasVertex, List<AtlasVertex>> getClassificationPropagatedEntitiesMapping(List<AtlasVertex> classificationVertices, String relationshipGuidToExclude) {
          Map<AtlasVertex, List<AtlasVertex>> ret = new HashMap<>();
  
          if (CollectionUtils.isNotEmpty(classificationVertices)) {
              for (AtlasVertex classificationVertex : classificationVertices) {
                  String            classificationId      = classificationVertex.getIdForDisplay();
                  String            sourceEntityId        = getClassificationEntityGuid(classificationVertex);
                  AtlasVertex       sourceEntityVertex    = AtlasGraphUtilsV2.findByGuid(this.graph, sourceEntityId);
                  List<AtlasVertex> entitiesPropagatingTo = getImpactedVerticesV2(sourceEntityVertex, relationshipGuidToExclude, classificationId);
  
                  ret.put(classificationVertex, entitiesPropagatingTo);
              }
          }
  
          return ret;
      }
  
      public List<AtlasVertex> getImpactedVerticesV2(AtlasVertex entityVertex) {
          return getImpactedVerticesV2(entityVertex, null);
      }
  
      public List<AtlasVertex> getImpactedVerticesV2(AtlasVertex entityVertex, String relationshipGuidToExclude) {
          List<AtlasVertex> ret = new ArrayList<>();
  
          traverseImpactedVertices(entityVertex, relationshipGuidToExclude, null, ret);
  
          return ret;
      }
  
      public List<AtlasVertex> getIncludedImpactedVerticesV2(AtlasVertex entityVertex, String relationshipGuidToExclude) {
          return getIncludedImpactedVerticesV2(entityVertex, relationshipGuidToExclude, null);
      }
  
      public List<AtlasVertex> getIncludedImpactedVerticesV2(AtlasVertex entityVertex, String relationshipGuidToExclude, String classificationId) {
          List<AtlasVertex> ret = new ArrayList<>(Collections.singletonList(entityVertex));
  
          traverseImpactedVertices(entityVertex, relationshipGuidToExclude, classificationId, ret);
  
          return ret;
      }
  
      public List<AtlasVertex> getImpactedVerticesV2(AtlasVertex entityVertex, String relationshipGuidToExclude, String classificationId) {
          List<AtlasVertex> ret = new ArrayList<>();
  
          traverseImpactedVertices(entityVertex, relationshipGuidToExclude, classificationId, ret);
  
          return ret;
      }
  
      public AtlasRelationshipHeader mapEdgeToAtlasRelationshipHeader(AtlasEdge edge) throws AtlasBaseException {
          AtlasRelationshipWithExtInfo withExtInfo = new AtlasRelationshipWithExtInfo();
  
          mapSystemAttributes(edge, withExtInfo, false);
          mapAttributes(edge, withExtInfo);
  
          return new AtlasRelationshipHeader(withExtInfo.getRelationship(), true);
      }
  
      public List<AtlasClassification> getAllClassifications(AtlasVertex entityVertex) throws AtlasBaseException {
          List<AtlasClassification> ret   = new ArrayList<>();
          Iterable                  edges = entityVertex.query().direction(AtlasEdgeDirection.OUT).label(CLASSIFICATION_LABEL).edges();
  
          if (edges != null) {
              for (AtlasEdge classificationEdge : (Iterable<AtlasEdge>) edges) {
                  AtlasVertex         classificationVertex = classificationEdge != null ? classificationEdge.getInVertex() : null;
                  AtlasClassification classification       = toAtlasClassification(classificationVertex);
  
                  if (classification != null) {
                      ret.add(classification);
                  }
              }
          }
  
          return ret;
      }
  
      public List<AtlasTermAssignmentHeader> mapAssignedTerms(AtlasVertex entityVertex) {
          List<AtlasTermAssignmentHeader> ret = new ArrayList<>();
  
          Iterable edges = entityVertex.query().direction(AtlasEdgeDirection.IN).label(TERM_ASSIGNMENT_LABEL).edges();
  
          if (edges != null) {
              for (final AtlasEdge edge : (Iterable<AtlasEdge>) edges) {
                  if (edge != null) {
                      ret.add(toTermAssignmentHeader(edge));
                  }
              }
          }
  
          return ret;
      }
  
      public AtlasRelationship mapEdgeToAtlasRelationship(AtlasEdge edge) throws AtlasBaseException {
          return mapEdgeToAtlasRelationship(edge, false).getRelationship();
      }
  
      public AtlasRelationshipWithExtInfo mapEdgeToAtlasRelationshipWithExtInfo(AtlasEdge edge) throws AtlasBaseException {
          return mapEdgeToAtlasRelationship(edge, true);
      }
  
      public AtlasRelationshipWithExtInfo mapEdgeToAtlasRelationship(AtlasEdge edge, boolean extendedInfo) throws AtlasBaseException {
          AtlasRelationshipWithExtInfo ret = new AtlasRelationshipWithExtInfo();
  
          mapSystemAttributes(edge, ret, extendedInfo);
  
          mapAttributes(edge, ret);
  
          return ret;
      }
  
      private void traverseImpactedVertices(final AtlasVertex entityVertexStart, final String relationshipGuidToExclude, final String classificationId, final List<AtlasVertex> result) {
          Set<String>              visitedVertices = new HashSet<>();
          Queue<AtlasVertex>       queue           = new ArrayDeque<>(Collections.singletonList(entityVertexStart));
          Map<String, AtlasVertex> resultsMap      = new HashMap<>();
  
          while (!queue.isEmpty()) {
              AtlasVertex entityVertex   = queue.poll();
              String      entityVertexId = entityVertex.getIdForDisplay();
  
              if (visitedVertices.contains(entityVertexId)) {
                  LOG.info("Already visited: {}", entityVertexId);
  
                  continue;
              }
  
              visitedVertices.add(entityVertexId);
  
              AtlasEntityType entityType          = typeRegistry.getEntityTypeByName(getTypeName(entityVertex));
              String[]        tagPropagationEdges = entityType != null ? entityType.getTagPropagationEdgesArray() : null;
  
              if (tagPropagationEdges == null) {
                  continue;
              }
  
              Iterable<AtlasEdge> propagationEdges = entityVertex.getEdges(AtlasEdgeDirection.BOTH, tagPropagationEdges);
  
              for (AtlasEdge propagationEdge : propagationEdges) {
                  if (getEdgeStatus(propagationEdge) != ACTIVE && !RequestContext.get().isImportInProgress()) {
                      continue;
                  }
  
                  PropagateTags tagPropagation = getPropagateTags(propagationEdge);
  
                  if (tagPropagation == null || tagPropagation == NONE) {
                      continue;
                  } else if (tagPropagation == TWO_TO_ONE) {
                      if (isOutVertex(entityVertex, propagationEdge)) {
                          continue;
                      }
                  } else if (tagPropagation == ONE_TO_TWO) {
                      if (!isOutVertex(entityVertex, propagationEdge)) {
                          continue;
                      }
                  }
  
                  if (relationshipGuidToExclude != null) {
                      if (StringUtils.equals(getRelationshipGuid(propagationEdge), relationshipGuidToExclude)) {
                          continue;
                      }
                  }
  
                  if (classificationId != null) {
                      List<String> blockedClassificationIds = getBlockedClassificationIds(propagationEdge);
  
                      if (CollectionUtils.isNotEmpty(blockedClassificationIds) && blockedClassificationIds.contains(classificationId)) {
                          continue;
                      }
                  }
  
                  AtlasVertex adjacentVertex             = getOtherVertex(propagationEdge, entityVertex);
                  String      adjacentVertexIdForDisplay = adjacentVertex.getIdForDisplay();
  
                  if (!visitedVertices.contains(adjacentVertexIdForDisplay) && !resultsMap.containsKey(adjacentVertexIdForDisplay)) {
                      resultsMap.put(adjacentVertexIdForDisplay, adjacentVertex);
  
                      queue.add(adjacentVertex);
                  }
              }
          }
  
          result.addAll(resultsMap.values());
      }
  
      private boolean isOutVertex(AtlasVertex vertex, AtlasEdge edge) {
          return StringUtils.equals(vertex.getIdForDisplay(), edge.getOutVertex().getIdForDisplay());
      }
  
      private AtlasVertex getOtherVertex(AtlasEdge edge, AtlasVertex vertex) {
          AtlasVertex outVertex = edge.getOutVertex();
          AtlasVertex inVertex  = edge.getInVertex();
  
          return StringUtils.equals(outVertex.getIdForDisplay(), vertex.getIdForDisplay()) ? inVertex : outVertex;
      }
  
      private AtlasVertex getEntityVertex(AtlasObjectId objId) throws AtlasBaseException {
          if (!AtlasTypeUtil.isValid(objId)) {
              throw new AtlasBaseException(AtlasErrorCode.INVALID_OBJECT_ID, objId.toString());
          }
  
          AtlasVertex ret;
  
          if (AtlasTypeUtil.isAssignedGuid(objId)) {
              ret = AtlasGraphUtilsV2.findByGuid(this.graph, objId.getGuid());
          } else {
              AtlasEntityType     entityType     = typeRegistry.getEntityTypeByName(objId.getTypeName());
              Map<String, Object> uniqAttributes = objId.getUniqueAttributes();
  
              ret = AtlasGraphUtilsV2.getVertexByUniqueAttributes(this.graph, entityType, uniqAttributes);
          }
  
          if (ret == null) {
              throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, objId.toString());
          }
  
          return ret;
      }
  
      private AtlasEntity mapVertexToAtlasEntity(AtlasVertex entityVertex, AtlasEntityExtInfo entityExtInfo) throws AtlasBaseException {
          return mapVertexToAtlasEntity(entityVertex, entityExtInfo, false);
      }
  
      private AtlasEntity mapVertexToAtlasEntity(AtlasVertex entityVertex, AtlasEntityExtInfo entityExtInfo, boolean isMinExtInfo) throws AtlasBaseException {
          return mapVertexToAtlasEntity(entityVertex, entityExtInfo, isMinExtInfo, true);
      }
  
      private AtlasEntity mapVertexToAtlasEntity(AtlasVertex entityVertex, AtlasEntityExtInfo entityExtInfo, boolean isMinExtInfo, boolean includeReferences) throws AtlasBaseException {
          String      guid   = GraphHelper.getGuid(entityVertex);
          AtlasEntity entity = entityExtInfo != null ? entityExtInfo.getEntity(guid) : null;
  
          if (entity == null) {
              LOG.debug("Mapping graph vertex to atlas entity for guid {}", guid);
  
              entity = new AtlasEntity();
  
              if (entityExtInfo != null) {
                  entityExtInfo.addReferredEntity(guid, entity);
              }
  
              mapSystemAttributes(entityVertex, entity);
  
              mapBusinessAttributes(entityVertex, entity);
  
              mapAttributes(entityVertex, entity, entityExtInfo, isMinExtInfo, includeReferences);
  
              if (!ignoreRelationshipAttr) { // only map when really needed
                  mapRelationshipAttributes(entityVertex, entity, entityExtInfo, isMinExtInfo);
              }
  
              mapClassifications(entityVertex, entity);
          }
  
          return entity;
      }
  
      private AtlasEntity mapVertexToAtlasEntityMin(AtlasVertex entityVertex, AtlasEntityExtInfo entityExtInfo) throws AtlasBaseException {
          return mapVertexToAtlasEntityMin(entityVertex, entityExtInfo, null);
      }
  
      private AtlasEntity mapVertexToAtlasEntityMin(AtlasVertex entityVertex, AtlasEntityExtInfo entityExtInfo, Set<String> attributes) throws AtlasBaseException {
          String      guid   = GraphHelper.getGuid(entityVertex);
          AtlasEntity entity = entityExtInfo != null ? entityExtInfo.getEntity(guid) : null;
  
          if (entity == null) {
              entity = new AtlasEntity();
  
              if (entityExtInfo != null) {
                  entityExtInfo.addReferredEntity(guid, entity);
              }
  
              mapSystemAttributes(entityVertex, entity);
  
              mapClassifications(entityVertex, entity);
  
              AtlasEntityType entityType = typeRegistry.getEntityTypeByName(entity.getTypeName());
  
              if (entityType != null) {
                  for (AtlasAttribute attribute : entityType.getMinInfoAttributes().values()) {
                      Object attrValue = getVertexAttribute(entityVertex, attribute);
  
                      if (attrValue != null) {
                          entity.setAttribute(attribute.getName(), attrValue);
                      }
                  }
              }
          }
  
          return entity;
      }
  
      private AtlasEntityHeader mapVertexToAtlasEntityHeader(AtlasVertex entityVertex) throws AtlasBaseException {
          return mapVertexToAtlasEntityHeader(entityVertex, Collections.emptySet());
      }
  
      private AtlasEntityHeader mapVertexToAtlasEntityHeader(AtlasVertex entityVertex, Set<String> attributes) throws AtlasBaseException {
          AtlasEntityHeader ret = new AtlasEntityHeader();
  
          String  typeName     = entityVertex.getProperty(Constants.TYPE_NAME_PROPERTY_KEY, String.class);
          String  guid         = entityVertex.getProperty(Constants.GUID_PROPERTY_KEY, String.class);
          Boolean isIncomplete = isEntityIncomplete(entityVertex);
  
          ret.setTypeName(typeName);
          ret.setGuid(guid);
          ret.setStatus(GraphHelper.getStatus(entityVertex));
          ret.setClassificationNames(getAllTraitNames(entityVertex));
          ret.setIsIncomplete(isIncomplete);
          ret.setLabels(getLabels(entityVertex));
  
          List<AtlasTermAssignmentHeader> termAssignmentHeaders = mapAssignedTerms(entityVertex);
          ret.setMeanings(termAssignmentHeaders);
          ret.setMeaningNames(termAssignmentHeaders.stream().map(AtlasTermAssignmentHeader::getDisplayText).collect(Collectors.toList()));
  
          AtlasEntityType entityType = typeRegistry.getEntityTypeByName(typeName);
  
          if (entityType != null) {
              for (AtlasAttribute headerAttribute : entityType.getHeaderAttributes().values()) {
                  Object attrValue = getVertexAttribute(entityVertex, headerAttribute);
  
                  if (attrValue != null) {
                      ret.setAttribute(headerAttribute.getName(), attrValue);
                  } else {
                      ret.setAttribute(headerAttribute.getName(), StringUtils.EMPTY);
                  }
              }
  
              Object displayText = getDisplayText(entityVertex, entityType);
  
              if (displayText != null) {
                  ret.setDisplayText(displayText.toString());
              }
  
              if (CollectionUtils.isNotEmpty(attributes)) {
                  for (String attrName : attributes) {
                      AtlasAttribute attribute = entityType.getAttribute(attrName);
  
                      if (attribute == null) {
                          attrName = toNonQualifiedName(attrName);
  
                          if (ret.hasAttribute(attrName)) {
                              continue;
                          }
  
                          attribute = entityType.getAttribute(attrName);
  
                          if (attribute == null) {
                              attribute = entityType.getRelationshipAttribute(attrName, null);
                          }
                      }
  
                      Object attrValue = getVertexAttribute(entityVertex, attribute);
  
                      if (attrValue != null) {
                          ret.setAttribute(attrName, attrValue);
                      }
                  }
              }
          }
  
          return ret;
      }
  
      private String toNonQualifiedName(String attrName) {
          String ret;
  
          if (attrName.contains(".")) {
              String[] attributeParts = attrName.split("\\.");
  
              ret = attributeParts[attributeParts.length - 1];
          } else {
              ret = attrName;
          }
  
          return ret;
      }
  
      private AtlasEntity mapSystemAttributes(AtlasVertex entityVertex, AtlasEntity entity) {
          LOG.debug("Mapping system attributes for type {}", entity.getTypeName());
  
          try {
              if (entityVertex != null) {
                  entity.setGuid(getGuid(entityVertex));
                  entity.setTypeName(getTypeName(entityVertex));
                  entity.setStatus(GraphHelper.getStatus(entityVertex));
                  entity.setVersion(GraphHelper.getVersion(entityVertex));
  
                  entity.setCreatedBy(GraphHelper.getCreatedByAsString(entityVertex));
                  entity.setUpdatedBy(GraphHelper.getModifiedByAsString(entityVertex));
  
                  entity.setCreateTime(new Date(GraphHelper.getCreatedTime(entityVertex)));
                  entity.setUpdateTime(new Date(GraphHelper.getModifiedTime(entityVertex)));
  
                  entity.setHomeId(GraphHelper.getHomeId(entityVertex));
  
                  entity.setIsProxy(GraphHelper.isProxy(entityVertex));
                  entity.setIsIncomplete(isEntityIncomplete(entityVertex));
  
                  entity.setProvenanceType(GraphHelper.getProvenanceType(entityVertex));
                  entity.setCustomAttributes(getCustomAttributes(entityVertex));
                  entity.setLabels(getLabels(entityVertex));
                  entity.setPendingTasks(getPendingTasks(entityVertex));
              }
          } catch (Throwable t) {
              LOG.warn("Got exception while mapping system attributes for type {} : ", entity.getTypeName(), t);
          }
  
          return entity;
      }
  
      private void mapAttributes(AtlasVertex entityVertex, AtlasStruct struct, AtlasEntityExtInfo entityExtInfo) throws AtlasBaseException {
          mapAttributes(entityVertex, struct, entityExtInfo, false);
      }
  
      private void mapAttributes(AtlasVertex entityVertex, AtlasStruct struct, AtlasEntityExtInfo entityExtInfo, boolean isMinExtInfo) throws AtlasBaseException {
          mapAttributes(entityVertex, struct, entityExtInfo, isMinExtInfo, true);
      }
  
      private void mapAttributes(AtlasVertex entityVertex, AtlasStruct struct, AtlasEntityExtInfo entityExtInfo, boolean isMinExtInfo, boolean includeReferences) throws AtlasBaseException {
          AtlasType objType = typeRegistry.getType(struct.getTypeName());
  
          if (!(objType instanceof AtlasStructType)) {
              throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_INVALID, struct.getTypeName());
          }
  
          AtlasStructType structType = (AtlasStructType) objType;
  
          for (AtlasAttribute attribute : structType.getAllAttributes().values()) {
              Object attrValue = mapVertexToAttribute(entityVertex, attribute, entityExtInfo, isMinExtInfo, includeReferences);
  
              struct.setAttribute(attribute.getName(), attrValue);
          }
      }
  
      private void mapBusinessAttributes(AtlasVertex entityVertex, AtlasEntity entity) throws AtlasBaseException {
          entity.setBusinessAttributes(getBusinessMetadata(entityVertex));
      }
  
      private AtlasTermAssignmentHeader toTermAssignmentHeader(final AtlasEdge edge) {
          AtlasTermAssignmentHeader ret = new AtlasTermAssignmentHeader();
  
          AtlasVertex termVertex = edge.getOutVertex();
  
          String guid = GraphHelper.getGuid(termVertex);
          if (guid != null) {
              ret.setTermGuid(guid);
          }
  
          String relationGuid = edge.getProperty(Constants.RELATIONSHIP_GUID_PROPERTY_KEY, String.class);
          if (relationGuid != null) {
              ret.setRelationGuid(relationGuid);
          }
  
          Object displayName = AtlasGraphUtilsV2.getEncodedProperty(termVertex, GLOSSARY_TERM_DISPLAY_NAME_ATTR, Object.class);
          if (displayName instanceof String) {
              ret.setDisplayText((String) displayName);
          }
  
          Object qualifiedName = AtlasGraphUtilsV2.getEncodedProperty(termVertex, GLOSSARY_TERM_QUALIFIED_NAME_ATTR, Object.class);
          if (qualifiedName instanceof String) {
              ret.setQualifiedName((String) qualifiedName);
          }
  
          String description = edge.getProperty(TERM_ASSIGNMENT_ATTR_DESCRIPTION, String.class);
          if (description != null) {
              ret.setDescription(description);
          }
  
          String expression = edge.getProperty(TERM_ASSIGNMENT_ATTR_EXPRESSION, String.class);
          if (expression != null) {
              ret.setExpression(expression);
          }
  
          String status = edge.getProperty(TERM_ASSIGNMENT_ATTR_STATUS, String.class);
          if (status != null) {
              AtlasTermAssignmentStatus assignmentStatus = AtlasTermAssignmentStatus.valueOf(status);
              ret.setStatus(assignmentStatus);
          }
  
          Integer confidence = edge.getProperty(TERM_ASSIGNMENT_ATTR_CONFIDENCE, Integer.class);
          if (confidence != null) {
              ret.setConfidence(confidence);
          }
  
          String createdBy = edge.getProperty(TERM_ASSIGNMENT_ATTR_CREATED_BY, String.class);
          if (createdBy != null) {
              ret.setCreatedBy(createdBy);
          }
  
          String steward = edge.getProperty(TERM_ASSIGNMENT_ATTR_STEWARD, String.class);
          if (steward != null) {
              ret.setSteward(steward);
          }
  
          String source = edge.getProperty(TERM_ASSIGNMENT_ATTR_SOURCE, String.class);
          if (source != null) {
              ret.setSource(source);
          }
  
          return ret;
      }
  
      private void mapClassifications(AtlasVertex entityVertex, AtlasEntity entity) throws AtlasBaseException {
          List<AtlasEdge> edges = getAllClassificationEdges(entityVertex);
  
          if (CollectionUtils.isNotEmpty(edges)) {
              List<AtlasClassification> allClassifications = new ArrayList<>();
  
              for (AtlasEdge edge : edges) {
                  AtlasVertex         classificationVertex = edge.getInVertex();
                  AtlasClassification classification       = toAtlasClassification(classificationVertex);
  
                  if (classification != null) {
                      allClassifications.add(classification);
                  }
              }
  
              entity.setClassifications(allClassifications);
          }
      }
  
      private Object mapVertexToAttribute(AtlasVertex entityVertex, AtlasAttribute attribute, AtlasEntityExtInfo entityExtInfo, final boolean isMinExtInfo) throws AtlasBaseException {
          return mapVertexToAttribute(entityVertex, attribute, entityExtInfo, isMinExtInfo, true);
      }
  
      private Object mapVertexToAttribute(AtlasVertex entityVertex, AtlasAttribute attribute, AtlasEntityExtInfo entityExtInfo, final boolean isMinExtInfo, boolean includeReferences) throws AtlasBaseException {
          return mapVertexToAttribute(entityVertex, attribute, entityExtInfo, isMinExtInfo, includeReferences, false);
      }
  
      private Object mapVertexToAttribute(AtlasVertex entityVertex, AtlasAttribute attribute, AtlasEntityExtInfo entityExtInfo, final boolean isMinExtInfo, boolean includeReferences, boolean ignoreInactive) throws AtlasBaseException {
          Object                         ret              = null;
          AtlasType                      attrType         = attribute.getAttributeType();
          String                         edgeLabel        = attribute.getRelationshipEdgeLabel();
          boolean                        isOwnedAttribute = attribute.isOwnedRef();
          AtlasRelationshipEdgeDirection edgeDirection    = attribute.getRelationshipEdgeDirection();
  
          if (LOG.isDebugEnabled()) {
              LOG.debug("Mapping vertex {} to atlas entity {}.{}", entityVertex, attribute.getDefinedInDef().getName(), attribute.getName());
          }
  
          switch (attrType.getTypeCategory()) {
              case PRIMITIVE:
                  ret = mapVertexToPrimitive(entityVertex, attribute.getVertexPropertyName(), attribute.getAttributeDef());
                  break;
              case ENUM:
                  ret = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, attribute.getVertexPropertyName(), Object.class);
                  break;
              case STRUCT:
                  ret = mapVertexToStruct(entityVertex, edgeLabel, null, entityExtInfo, isMinExtInfo);
                  break;
              case OBJECT_ID_TYPE:
                  if (includeReferences) {
                      ret = attribute.getAttributeDef().isSoftReferenced() ? mapVertexToObjectIdForSoftRef(entityVertex, attribute, entityExtInfo, isMinExtInfo) :
                              mapVertexToObjectId(entityVertex, edgeLabel, null, entityExtInfo, isOwnedAttribute, edgeDirection, isMinExtInfo);
                  } else {
                      ret = null;
                  }
                  break;
              case ARRAY: {
                  final boolean skipAttribute;
  
                  if (!includeReferences) {
                      AtlasType elementType = ((AtlasArrayType) attrType).getElementType();
  
                      skipAttribute = (elementType instanceof AtlasObjectIdType || elementType instanceof AtlasEntityType);
                  } else {
                      skipAttribute = false;
                  }
  
                  if (skipAttribute) {
                      ret = null;
                  } else {
                      if (attribute.getAttributeDef().isSoftReferenced()) {
                          ret = mapVertexToArrayForSoftRef(entityVertex, attribute, entityExtInfo, isMinExtInfo);
                      } else {
                          ret = mapVertexToArray(entityVertex, entityExtInfo, isOwnedAttribute, attribute, isMinExtInfo, includeReferences, ignoreInactive);
                      }
                  }
              }
              break;
              case MAP: {
                  final boolean skipAttribute;
  
                  if (!includeReferences) {
                      AtlasType valueType = ((AtlasMapType) attrType).getValueType();
  
                      skipAttribute = (valueType instanceof AtlasObjectIdType || valueType instanceof AtlasEntityType);
                  } else {
                      skipAttribute = false;
                  }
  
                  if (skipAttribute) {
                      ret = null;
                  } else {
                      if (attribute.getAttributeDef().isSoftReferenced()) {
                          ret = mapVertexToMapForSoftRef(entityVertex, attribute, entityExtInfo, isMinExtInfo);
                      } else {
                          ret = mapVertexToMap(entityVertex, entityExtInfo, isOwnedAttribute, attribute, isMinExtInfo, includeReferences);
                      }
                  }
              }
              break;
              case CLASSIFICATION:
                  // do nothing
                  break;
          }
  
          return ret;
      }
  
      private Map<String, AtlasObjectId> mapVertexToMapForSoftRef(AtlasVertex entityVertex, AtlasAttribute attribute, AtlasEntityExtInfo entityExtInfo, final boolean isMinExtInfo) {
          Map<String, AtlasObjectId> ret;
          Map                        softRefVal = entityVertex.getProperty(attribute.getVertexPropertyName(), Map.class);
  
          if (MapUtils.isEmpty(softRefVal)) {
              return softRefVal;
          } else {
              ret = new HashMap<>();
  
              for (Object mapKey : softRefVal.keySet()) {
                  AtlasObjectId objectId = getAtlasObjectIdFromSoftRefFormat(Objects.toString(softRefVal.get(mapKey)), attribute, entityExtInfo, isMinExtInfo);
  
                  if (objectId != null) {
                      ret.put(Objects.toString(mapKey), objectId);
                  }
              }
          }
          return ret;
      }
  
      private List<AtlasObjectId> mapVertexToArrayForSoftRef(AtlasVertex entityVertex, AtlasAttribute attribute, AtlasEntityExtInfo entityExtInfo, final boolean isMinExtInfo) {
          List<AtlasObjectId> ret;
          List                softRefVal = entityVertex.getListProperty(attribute.getVertexPropertyName(), List.class);
  
          if (CollectionUtils.isEmpty(softRefVal)) {
              ret = (List<AtlasObjectId>) softRefVal;
          } else {
              ret = new ArrayList<>();
  
              for (Object o : softRefVal) {
                  AtlasObjectId objectId = getAtlasObjectIdFromSoftRefFormat(Objects.toString(o), attribute, entityExtInfo, isMinExtInfo);
  
                  if (objectId != null) {
                      ret.add(objectId);
                  }
              }
          }
  
          return ret;
      }
  
      private AtlasObjectId mapVertexToObjectIdForSoftRef(AtlasVertex entityVertex, AtlasAttribute attribute, AtlasEntityExtInfo entityExtInfo, final boolean isMinExtInfo) {
          String softRefVal = AtlasGraphUtilsV2.getEncodedProperty(entityVertex, attribute.getVertexPropertyName(), String.class);
  
          return StringUtils.isNotEmpty(softRefVal) ? getAtlasObjectIdFromSoftRefFormat(softRefVal, attribute, entityExtInfo, isMinExtInfo) : null;
      }
  
      private AtlasObjectId getAtlasObjectIdFromSoftRefFormat(String softRefVal, AtlasAttribute attribute, AtlasEntityExtInfo entityExtInfo, final boolean isMinExtInfo) {
          AtlasObjectId ret = AtlasEntityUtil.parseSoftRefValue(softRefVal);
  
          if (ret != null) {
              if (entityExtInfo != null && attribute.isOwnedRef()) {
                  try {
                      AtlasVertex referenceVertex = getEntityVertex(ret.getGuid());
  
                      if (referenceVertex != null) {
                          final AtlasEntity entity;
  
                          if (isMinExtInfo) {
                              entity = mapVertexToAtlasEntityMin(referenceVertex, entityExtInfo);
                          } else {
                              entity = mapVertexToAtlasEntity(referenceVertex, entityExtInfo);
                          }
  
                          if (entity != null) {
                              ret = toAtlasObjectId(entity);
                          }
                      }
                  } catch (AtlasBaseException excp) {
                      LOG.info("failed to retrieve soft-referenced entity(typeName={}, guid={}); errorCode={}. Ignoring", ret.getTypeName(), ret.getGuid(), excp.getAtlasErrorCode());
                  }
              }
          }
  
          return ret;
      }
  
      private Map<String, Object> mapVertexToMap(AtlasVertex entityVertex, AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute, AtlasAttribute attribute, final boolean isMinExtInfo, boolean includeReferences) throws AtlasBaseException {
          Map<String, Object> ret          = null;
          AtlasMapType        mapType      = (AtlasMapType) attribute.getAttributeType();
          AtlasType           mapValueType = mapType.getValueType();
  
          LOG.debug("Mapping map attribute {} for vertex {}", mapType.getTypeName(), entityVertex);
  
          if (isReference(mapValueType)) {
              Map<String, Object> currentMap = getReferenceMap(entityVertex, attribute);
  
              if (MapUtils.isNotEmpty(currentMap)) {
                  ret = new HashMap<>();
  
                  for (Map.Entry<String, Object> entry : currentMap.entrySet()) {
                      String mapKey   = entry.getKey();
                      Object keyValue = entry.getValue();
                      Object mapValue = mapVertexToCollectionEntry(entityVertex, mapValueType, keyValue, attribute.getRelationshipEdgeLabel(), entityExtInfo, isOwnedAttribute, attribute.getRelationshipEdgeDirection(), isMinExtInfo, includeReferences);
  
                      if (mapValue != null) {
                          ret.put(mapKey, mapValue);
                      }
                  }
              }
          } else {
              ret = getPrimitiveMap(entityVertex, attribute.getVertexPropertyName());
          }
  
          return ret;
      }
  
      private List<Object> mapVertexToArray(AtlasVertex entityVertex, AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute, AtlasAttribute attribute, final boolean isMinExtInfo, boolean includeReferences, boolean ignoreInactive) throws AtlasBaseException {
          AtlasArrayType arrayType        = (AtlasArrayType) attribute.getAttributeType();
          AtlasType      arrayElementType = arrayType.getElementType();
          List<Object>   arrayElements    = getArrayElementsProperty(arrayElementType, entityVertex, attribute);
  
          LOG.debug("Mapping array attribute {} for vertex {}", arrayElementType.getTypeName(), entityVertex);
  
          if (CollectionUtils.isEmpty(arrayElements)) {
              return arrayElements;
          }
  
          List<Object>                   arrValues     = new ArrayList<>(arrayElements.size());
          String                         edgeLabel     = attribute.getRelationshipEdgeLabel();
          AtlasRelationshipEdgeDirection edgeDirection = attribute.getRelationshipEdgeDirection();
  
          for (Object element : arrayElements) {
              // When internal types are deleted, sometimes the collection type attribute will contain a null value
              // Graph layer does erroneous mapping of the null element, hence avoiding the processing of the null element
              if (element == null) {
                  LOG.debug("Skipping null arrayElement");
                  continue;
              }
  
              if (isInactiveEdge(element, ignoreInactive)) {
                  continue;
              }
  
              Object arrValue = mapVertexToCollectionEntry(entityVertex, arrayElementType, element, edgeLabel, entityExtInfo, isOwnedAttribute, edgeDirection, isMinExtInfo, includeReferences);
  
              if (arrValue != null) {
                  arrValues.add(arrValue);
              }
          }
  
          return arrValues;
      }
  
      private Object mapVertexToCollectionEntry(AtlasVertex entityVertex, AtlasType arrayElement, Object value,
              String edgeLabel, AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute,
              AtlasRelationshipEdgeDirection edgeDirection, final boolean isMinExtInfo, boolean includeReferences) throws AtlasBaseException {
          Object ret = null;
  
          switch (arrayElement.getTypeCategory()) {
              case PRIMITIVE:
              case ENUM:
              case ARRAY:
              case MAP:
                  ret = value;
                  break;
  
              case CLASSIFICATION:
                  break;
  
              case STRUCT:
                  ret = mapVertexToStruct(entityVertex, edgeLabel, (AtlasEdge) value, entityExtInfo, isMinExtInfo);
                  break;
  
              case OBJECT_ID_TYPE:
                  ret = includeReferences ? mapVertexToObjectId(entityVertex, edgeLabel, (AtlasEdge) value, entityExtInfo, isOwnedAttribute, edgeDirection, isMinExtInfo) : null;
                  break;
  
              default:
                  break;
          }
  
          return ret;
      }
  
      private AtlasObjectId mapVertexToObjectId(AtlasVertex entityVertex, String edgeLabel, AtlasEdge edge,
              AtlasEntityExtInfo entityExtInfo, boolean isOwnedAttribute,
              AtlasRelationshipEdgeDirection edgeDirection, final boolean isMinExtInfo) throws AtlasBaseException {
          AtlasObjectId ret = null;
  
          if (edge == null) {
              edge = graphHelper.getEdgeForLabel(entityVertex, edgeLabel, edgeDirection);
          }
  
          if (GraphHelper.elementExists(edge)) {
              AtlasVertex referenceVertex = edge.getInVertex();
  
              if (StringUtils.equals(getIdFromVertex(referenceVertex), getIdFromVertex(entityVertex))) {
                  referenceVertex = edge.getOutVertex();
              }
  
              if (referenceVertex != null) {
                  if (entityExtInfo != null && isOwnedAttribute) {
                      final AtlasEntity entity;
  
                      if (isMinExtInfo) {
                          entity = mapVertexToAtlasEntityMin(referenceVertex, entityExtInfo);
                      } else {
                          entity = mapVertexToAtlasEntity(referenceVertex, entityExtInfo);
                      }
  
                      if (entity != null) {
                          ret = AtlasTypeUtil.getAtlasObjectId(entity);
                      }
                  } else {
                      ret = toAtlasObjectId(referenceVertex);
                  }
              }
          }
  
          return ret;
      }
  
      private AtlasStruct mapVertexToStruct(AtlasVertex entityVertex, String edgeLabel, AtlasEdge edge, AtlasEntityExtInfo entityExtInfo, final boolean isMinExtInfo) throws AtlasBaseException {
          AtlasStruct ret = null;
  
          if (edge == null) {
              edge = graphHelper.getEdgeForLabel(entityVertex, edgeLabel);
          }
  
          if (GraphHelper.elementExists(edge)) {
              final AtlasVertex referenceVertex = edge.getInVertex();
              ret = new AtlasStruct(getTypeName(referenceVertex));
  
              mapAttributes(referenceVertex, ret, entityExtInfo, isMinExtInfo);
          }
  
          return ret;
      }
  
      private Object getVertexAttribute(AtlasVertex vertex, AtlasAttribute attribute) throws AtlasBaseException {
          return vertex != null && attribute != null ? mapVertexToAttribute(vertex, attribute, null, false) : null;
      }
  
      private Object getVertexAttributeIgnoreInactive(AtlasVertex vertex, AtlasAttribute attribute) throws AtlasBaseException {
          return vertex != null && attribute != null ? mapVertexToAttribute(vertex, attribute, null, false, true, true) : null;
      }
  
      private void mapRelationshipAttributes(AtlasVertex entityVertex, AtlasEntity entity, AtlasEntityExtInfo entityExtInfo, boolean isMinExtInfo) throws AtlasBaseException {
          AtlasEntityType entityType = typeRegistry.getEntityTypeByName(entity.getTypeName());
  
          if (entityType == null) {
              throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_INVALID, entity.getTypeName());
          }
  
          for (String attributeName : entityType.getRelationshipAttributes().keySet()) {
              mapVertexToRelationshipAttribute(entityVertex, entityType, attributeName, entity, entityExtInfo, isMinExtInfo);
          }
      }
  
      private Object mapVertexToRelationshipAttribute(AtlasVertex entityVertex, AtlasEntityType entityType, String attributeName, AtlasEntity entity, AtlasEntityExtInfo entityExtInfo, boolean isMinExtInfo) throws AtlasBaseException {
          Object                ret                  = null;
          String                relationshipTypeName = graphHelper.getRelationshipTypeName(entityVertex, entityType, attributeName);
          AtlasRelationshipType relationshipType     = relationshipTypeName != null ? typeRegistry.getRelationshipTypeByName(relationshipTypeName) : null;
  
          if (relationshipType == null) {
              throw new AtlasBaseException(AtlasErrorCode.RELATIONSHIPDEF_INVALID, "relationshipDef is null");
          }
  
          AtlasAttribute          attribute       = entityType.getRelationshipAttribute(attributeName, relationshipTypeName);
          AtlasRelationshipDef    relationshipDef = relationshipType.getRelationshipDef();
          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(), attributeName)) {
              attributeEndDef = endDef1;
          } else if (endDef2Type.isTypeOrSuperTypeOf(entityType.getTypeName()) && StringUtils.equals(endDef2.getName(), attributeName)) {
              attributeEndDef = endDef2;
          }
  
          if (attributeEndDef == null) {
              throw new AtlasBaseException(AtlasErrorCode.RELATIONSHIPDEF_INVALID, relationshipDef.toString());
          }
  
          switch (attributeEndDef.getCardinality()) {
              case SINGLE:
                  ret = mapRelatedVertexToObjectId(entityVertex, attribute, entityExtInfo, isMinExtInfo);
                  break;
  
              case LIST:
              case SET:
                  ret = mapRelationshipArrayAttribute(entityVertex, attribute, entityExtInfo, isMinExtInfo);
                  break;
          }
  
          // Set Relationship attributes, even if the value is null
          entity.setRelationshipAttribute(attributeName, ret);
  
          if (attributeEndDef.getIsLegacyAttribute() && !entity.hasAttribute(attributeName)) {
              entity.setAttribute(attributeName, toLegacyAttribute(ret));
          }
  
          return ret;
      }
  
      private Object toLegacyAttribute(Object obj) {
          final Object ret;
  
          if (obj instanceof AtlasRelatedObjectId) {
              ret = toLegacyAttribute((AtlasRelatedObjectId) obj);
          } else if (obj instanceof Collection) {
              ret = toLegacyAttribute((Collection<?>) obj);
          } else if (obj instanceof Map) {
              ret = toLegacyAttribute((Map<?, ?>) obj);
          } else {
              ret = obj;
          }
  
          return ret;
      }
  
      private AtlasObjectId toLegacyAttribute(AtlasRelatedObjectId relatedObjId) {
          final AtlasObjectId ret;
  
          if (relatedObjId.getRelationshipStatus() == DELETED && relatedObjId.getEntityStatus() == AtlasEntity.Status.ACTIVE) {
              ret = null;
          } else {
              ret = new AtlasObjectId(relatedObjId);
          }
  
          return ret;
      }
  
      private Collection<Object> toLegacyAttribute(Collection<?> collection) {
          final List<Object> ret = new ArrayList<>();
  
          for (Object elem : collection) {
              Object objId = toLegacyAttribute(elem);
  
              if (objId != null) {
                  ret.add(objId);
              }
          }
  
          return ret;
      }
  
      private Map<Object, Object> toLegacyAttribute(Map<?, ?> map) {
          final Map<Object, Object> ret = new HashMap<>();
  
          for (Object key : map.keySet()) {
              Object elem = toLegacyAttribute(map.get(key));
  
              if (elem != null) {
                  ret.put(key, elem);
              }
          }
  
          return ret;
      }
  
      private AtlasObjectId mapRelatedVertexToObjectId(AtlasVertex entityVertex, AtlasAttribute attribute, AtlasEntityExtInfo entityExtInfo, boolean isMinExtInfo) throws AtlasBaseException {
          AtlasEdge edge = graphHelper.getEdgeForLabel(entityVertex, attribute.getRelationshipEdgeLabel(), attribute.getRelationshipEdgeDirection());
  
          return mapVertexToRelatedObjectId(entityVertex, edge, attribute.isOwnedRef(), entityExtInfo, isMinExtInfo);
      }
  
      private List<AtlasRelatedObjectId> mapRelationshipArrayAttribute(AtlasVertex entityVertex, AtlasAttribute attribute, AtlasEntityExtInfo entityExtInfo, boolean isMinExtInfo) throws AtlasBaseException {
          List<AtlasRelatedObjectId> ret   = new ArrayList<>();
          Iterator<AtlasEdge>        edges = null;
  
          if (attribute.getRelationshipEdgeDirection() == IN) {
              edges = getIncomingEdgesByLabel(entityVertex, attribute.getRelationshipEdgeLabel());
          } else if (attribute.getRelationshipEdgeDirection() == OUT) {
              edges = getOutGoingEdgesByLabel(entityVertex, attribute.getRelationshipEdgeLabel());
          } else if (attribute.getRelationshipEdgeDirection() == BOTH) {
              edges = getAdjacentEdgesByLabel(entityVertex, AtlasEdgeDirection.BOTH, attribute.getRelationshipEdgeLabel());
          }
  
          if (edges != null) {
              while (edges.hasNext()) {
                  AtlasEdge relationshipEdge = edges.next();
  
                  AtlasRelatedObjectId relatedObjectId = mapVertexToRelatedObjectId(entityVertex, relationshipEdge, attribute.isOwnedRef(), entityExtInfo, isMinExtInfo);
  
                  ret.add(relatedObjectId);
              }
          }
  
          return ret;
      }
  
      private AtlasRelatedObjectId mapVertexToRelatedObjectId(AtlasVertex entityVertex, AtlasEdge edge, boolean isOwnedRef, AtlasEntityExtInfo entityExtInfo, boolean isMinExtInfo) throws AtlasBaseException {
          AtlasRelatedObjectId ret = null;
  
          if (GraphHelper.elementExists(edge)) {
              AtlasVertex referenceVertex = edge.getInVertex();
  
              if (StringUtils.equals(getIdFromVertex(referenceVertex), getIdFromVertex(entityVertex))) {
                  referenceVertex = edge.getOutVertex();
              }
  
              if (referenceVertex != null) {
                  if (CollectionUtils.isEmpty(referenceVertex.getPropertyKeys())) {
                      LOG.warn("Reference vertex found empty with vertexId: {} . Returning AtlasRelatedObjectId object as null", referenceVertex.getId());
                      return null;
                  }
  
                  String             entityTypeName = getTypeName(referenceVertex);
                  String             entityGuid     = getGuid(referenceVertex);
                  AtlasEntity.Status entityStatus   = GraphHelper.getStatus(referenceVertex);
                  AtlasRelationship  relationship   = mapEdgeToAtlasRelationship(edge);
  
                  ret = new AtlasRelatedObjectId(entityGuid, entityTypeName, entityStatus, relationship.getGuid(), relationship.getStatus(), new AtlasStruct(relationship.getTypeName(), relationship.getAttributes()));
  
                  Object displayText = getDisplayText(referenceVertex, entityTypeName);
  
                  if (displayText != null) {
                      ret.setDisplayText(displayText.toString());
                  }
  
                  Object qualifiedName = getQualifiedName(referenceVertex, entityTypeName);
  
                  if (qualifiedName != null) {
                      ret.setQualifiedName(qualifiedName.toString());
                  }
  
                  if (isOwnedRef && entityExtInfo != null) {
                      if (isMinExtInfo) {
                          mapVertexToAtlasEntityMin(referenceVertex, entityExtInfo);
                      } else {
                          mapVertexToAtlasEntity(referenceVertex, entityExtInfo);
                      }
                  }
  
                  populateGlossaryAttributesIfApplicable(ret, referenceVertex, entityTypeName);
              }
          }
  
          return ret;
      }
  
      private void populateGlossaryAttributesIfApplicable(AtlasRelatedObjectId ret, AtlasVertex referenceVertex, String entityTypeName) {
          if (!StringUtils.equals(entityTypeName, GLOSSARY_CATEGORY_TYPE_NAME)) {
              return;
          }
  
          Iterator<AtlasEdge> edgeIterator = GraphHelper.getIncomingEdgesByLabel(referenceVertex, GLOSSARY_CATEGORY_HIERARCHY_EDGE_LABEL);
  
          while (edgeIterator.hasNext()) {
              AtlasEdge   atlasEdge            = edgeIterator.next();
              AtlasVertex parentCategoryVertex = atlasEdge.getOutVertex();
              String      parentCategoryGuid   = GraphHelper.getGuid(parentCategoryVertex);
  
              ret.getRelationshipAttributes().setAttribute(PARENT_GLOSSARY_CATEGORY_GUID, parentCategoryGuid);
          }
      }
  
      private Object getDisplayText(AtlasVertex entityVertex, String entityTypeName) throws AtlasBaseException {
          return getDisplayText(entityVertex, typeRegistry.getEntityTypeByName(entityTypeName));
      }
  
      private Object getDisplayText(AtlasVertex entityVertex, AtlasEntityType entityType) throws AtlasBaseException {
          Object ret = null;
  
          if (entityType != null) {
              String displayTextAttribute = entityType.getDisplayTextAttribute();
  
              if (displayTextAttribute != null) {
                  ret = getVertexAttribute(entityVertex, entityType.getAttribute(displayTextAttribute));
              }
  
              if (ret == null) {
                  ret = getVertexAttribute(entityVertex, entityType.getAttribute(NAME));
  
                  if (ret == null) {
                      ret = getVertexAttribute(entityVertex, entityType.getAttribute(DISPLAY_NAME));
  
                      if (ret == null) {
                          ret = getVertexAttribute(entityVertex, entityType.getAttribute(QUALIFIED_NAME));
                      }
                  }
              }
          }
  
          return ret;
      }
  
      private Object getQualifiedName(AtlasVertex entityVertex, String entityTypeName) throws AtlasBaseException {
          return getQualifiedName(entityVertex, typeRegistry.getEntityTypeByName(entityTypeName));
      }
  
      private Object getQualifiedName(AtlasVertex entityVertex, AtlasEntityType entityType) throws AtlasBaseException {
          Object ret = null;
  
          if (entityType != null) {
              ret = getVertexAttribute(entityVertex, entityType.getAttribute(QUALIFIED_NAME));
          }
  
          return ret;
      }
  
      private AtlasRelationshipWithExtInfo mapSystemAttributes(AtlasEdge edge, AtlasRelationshipWithExtInfo relationshipWithExtInfo, boolean extendedInfo) throws AtlasBaseException {
          LOG.debug("Mapping system attributes for relationship");
  
          AtlasRelationship relationship = relationshipWithExtInfo.getRelationship();
  
          if (relationship == null) {
              relationship = new AtlasRelationship();
  
              relationshipWithExtInfo.setRelationship(relationship);
          }
  
          relationship.setGuid(getRelationshipGuid(edge));
          relationship.setTypeName(getTypeName(edge));
  
          relationship.setCreatedBy(GraphHelper.getCreatedByAsString(edge));
          relationship.setUpdatedBy(GraphHelper.getModifiedByAsString(edge));
  
          relationship.setCreateTime(new Date(GraphHelper.getCreatedTime(edge)));
          relationship.setUpdateTime(new Date(GraphHelper.getModifiedTime(edge)));
  
          Long version = GraphHelper.getVersion(edge);
  
          if (version == null) {
              version = 1L;
          }
  
          relationship.setVersion(version);
  
          Integer provenanceType = GraphHelper.getProvenanceType(edge);
  
          if (provenanceType == null) {
              provenanceType = 0;
          }
  
          relationship.setProvenanceType(provenanceType);
          relationship.setHomeId(GraphHelper.getHomeId(edge));
  
          relationship.setStatus(GraphHelper.getEdgeStatus(edge));
  
          AtlasVertex end1Vertex = edge.getOutVertex();
          AtlasVertex end2Vertex = edge.getInVertex();
  
          relationship.setEnd1(new AtlasObjectId(getGuid(end1Vertex), getTypeName(end1Vertex), getEntityUniqueAttribute(end1Vertex)));
          relationship.setEnd2(new AtlasObjectId(getGuid(end2Vertex), getTypeName(end2Vertex), getEntityUniqueAttribute(end2Vertex)));
  
          relationship.setLabel(edge.getLabel());
          relationship.setPropagateTags(getPropagateTags(edge));
  
          if (extendedInfo) {
              addToReferredEntities(relationshipWithExtInfo, end1Vertex);
              addToReferredEntities(relationshipWithExtInfo, end2Vertex);
          }
  
          // set propagated and blocked propagated classifications
          readClassificationsFromEdge(edge, relationshipWithExtInfo, extendedInfo);
  
          return relationshipWithExtInfo;
      }
  
      private void readClassificationsFromEdge(AtlasEdge edge, AtlasRelationshipWithExtInfo relationshipWithExtInfo, boolean extendedInfo) throws AtlasBaseException {
          List<AtlasVertex>        classificationVertices    = getPropagatableClassifications(edge);
          List<String>             blockedClassificationIds  = getBlockedClassificationIds(edge);
          AtlasRelationship        relationship              = relationshipWithExtInfo.getRelationship();
          Set<AtlasClassification> propagatedClassifications = new HashSet<>();
          Set<AtlasClassification> blockedClassifications    = new HashSet<>();
  
          for (AtlasVertex classificationVertex : classificationVertices) {
              String              classificationId = classificationVertex.getIdForDisplay();
              AtlasClassification classification   = toAtlasClassification(classificationVertex);
  
              if (classification == null) {
                  continue;
              }
  
              if (blockedClassificationIds.contains(classificationId)) {
                  blockedClassifications.add(classification);
              } else {
                  propagatedClassifications.add(classification);
              }
  
              // add entity headers to referred entities
              if (extendedInfo) {
                  addToReferredEntities(relationshipWithExtInfo, classification.getEntityGuid());
              }
          }
  
          relationship.setPropagatedClassifications(propagatedClassifications);
          relationship.setBlockedPropagatedClassifications(blockedClassifications);
      }
  
      private void addToReferredEntities(AtlasRelationshipWithExtInfo relationshipWithExtInfo, String guid) throws AtlasBaseException {
          if (!relationshipWithExtInfo.referredEntitiesContains(guid)) {
              addToReferredEntities(relationshipWithExtInfo, getEntityVertex(guid));
          }
      }
  
      private void addToReferredEntities(AtlasRelationshipWithExtInfo relationshipWithExtInfo, AtlasVertex entityVertex) throws AtlasBaseException {
          String entityGuid = getGuid(entityVertex);
  
          if (!relationshipWithExtInfo.referredEntitiesContains(entityGuid)) {
              relationshipWithExtInfo.addReferredEntity(entityGuid, toAtlasEntityHeader(entityVertex));
          }
      }
  
      private void mapAttributes(AtlasEdge edge, AtlasRelationshipWithExtInfo relationshipWithExtInfo) throws AtlasBaseException {
          AtlasRelationship relationship = relationshipWithExtInfo.getRelationship();
          AtlasType         objType      = typeRegistry.getType(relationship.getTypeName());
  
          if (!(objType instanceof AtlasRelationshipType)) {
              throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_INVALID, relationship.getTypeName());
          }
  
          AtlasRelationshipType relationshipType = (AtlasRelationshipType) objType;
  
          for (AtlasAttribute attribute : relationshipType.getAllAttributes().values()) {
              // mapping only primitive attributes
              Object attrValue = mapVertexToPrimitive(edge, attribute.getVertexPropertyName(), attribute.getAttributeDef());
  
              relationship.setAttribute(attribute.getName(), attrValue);
          }
      }
  
      private Set<String> getPendingTasks(AtlasVertex entityVertex) {
          Collection<String> ret = entityVertex.getPropertyValues(PENDING_TASKS_PROPERTY_KEY, String.class);
  
          if (CollectionUtils.isEmpty(ret)) {
              return null;
          }
  
          return new HashSet<>(ret);
      }
  
      private boolean isInactiveEdge(Object element, boolean ignoreInactive) {
          return ignoreInactive && element instanceof AtlasEdge && getStatus((AtlasEdge) element) != AtlasEntity.Status.ACTIVE;
      }
  }
  