@Override
  public AtlasVertex preCreate(AtlasStructDef structDef) throws AtlasBaseException {
    if (LOG.isDebugEnabled()) {
      LOG.debug("==> AtlasStructDefStoreV1.preCreate({})", structDef);
    }

    AtlasType type = typeRegistry.getType(structDef.getName());

    if (type.getTypeCategory() != AtlasType.TypeCategory.STRUCT) {
      throw new AtlasBaseException(
          AtlasErrorCode.TYPE_MATCH_FAILED, structDef.getName(), TypeCategory.STRUCT.name());
    }

    AtlasVertex ret = typeDefStore.findTypeVertexByName(structDef.getName());

    if (ret != null) {
      throw new AtlasBaseException(AtlasErrorCode.TYPE_ALREADY_EXISTS, structDef.getName());
    }

    ret = typeDefStore.createTypeVertex(structDef);

    AtlasStructDefStoreV1.updateVertexPreCreate(
        structDef, (AtlasStructType) type, ret, typeDefStore);

    if (LOG.isDebugEnabled()) {
      LOG.debug("<== AtlasStructDefStoreV1.preCreate({}): {}", structDef, ret);
    }

    return ret;
  }
  @Override
  public AtlasStructDef updateByGuid(String guid, AtlasStructDef structDef)
      throws AtlasBaseException {
    if (LOG.isDebugEnabled()) {
      LOG.debug("==> AtlasStructDefStoreV1.updateByGuid({})", guid);
    }

    AtlasType type = typeRegistry.getTypeByGuid(guid);

    if (type.getTypeCategory() != AtlasType.TypeCategory.STRUCT) {
      throw new AtlasBaseException(
          AtlasErrorCode.TYPE_MATCH_FAILED, structDef.getName(), TypeCategory.STRUCT.name());
    }

    AtlasVertex vertex = typeDefStore.findTypeVertexByGuidAndCategory(guid, TypeCategory.STRUCT);

    if (vertex == null) {
      throw new AtlasBaseException(AtlasErrorCode.TYPE_GUID_NOT_FOUND, guid);
    }

    AtlasStructDefStoreV1.updateVertexPreUpdate(
        structDef, (AtlasStructType) type, vertex, typeDefStore);
    AtlasStructDefStoreV1.updateVertexAddReferences(structDef, vertex, typeDefStore);

    AtlasStructDef ret = toStructDef(vertex);

    if (LOG.isDebugEnabled()) {
      LOG.debug("<== AtlasStructDefStoreV1.updateByGuid({}): {}", guid, ret);
    }

    return ret;
  }
  private static String toJsonFromAttributeDef(
      AtlasAttributeDef attributeDef, AtlasStructType structType) {
    boolean isForeignKey = structType.isForeignKeyAttribute(attributeDef.getName());
    boolean isMappedFromRef = structType.isMappedFromRefAttribute(attributeDef.getName());
    String reverseAttribName = null;

    if (isForeignKey) { // check if the referenced entity has foreignKeyRef to this attribute
      AtlasType attribType = structType.getAttributeType(attributeDef.getName());

      if (attribType.getTypeCategory() == AtlasType.TypeCategory.ARRAY) {
        attribType = ((AtlasArrayType) attribType).getElementType();
      }

      if (attribType.getTypeCategory() == AtlasType.TypeCategory.ENTITY) {
        reverseAttribName =
            ((AtlasStructType) attribType)
                .getMappedFromRefAttribute(structType.getTypeName(), attributeDef.getName());
      }
    }

    boolean isComposite =
        isMappedFromRef || (isForeignKey && StringUtils.isBlank(reverseAttribName));

    Map<String, Object> attribInfo = new HashMap<>();

    attribInfo.put("name", attributeDef.getName());
    attribInfo.put("dataType", attributeDef.getTypeName());
    attribInfo.put("isUnique", attributeDef.isUnique());
    attribInfo.put("isIndexable", attributeDef.isIndexable());
    attribInfo.put("isComposite", isComposite);
    attribInfo.put("reverseAttributeName", reverseAttribName);
    Map<String, Object> multiplicity = new HashMap<>();
    multiplicity.put("lower", attributeDef.getValuesMinCount());
    multiplicity.put("upper", attributeDef.getValuesMaxCount());
    multiplicity.put(
        "isUnique", AtlasAttributeDef.Cardinality.SET.equals(attributeDef.getCardinality()));

    attribInfo.put("multiplicity", AtlasType.toJson(multiplicity));

    return AtlasType.toJson(attribInfo);
  }
  public static AtlasStructDef toStructDef(
      AtlasVertex vertex, AtlasStructDef structDef, AtlasTypeDefGraphStoreV1 typeDefStore)
      throws AtlasBaseException {
    AtlasStructDef ret = (structDef != null) ? structDef : new AtlasStructDef();

    typeDefStore.vertexToTypeDef(vertex, ret);

    List<AtlasAttributeDef> attributeDefs = new ArrayList<>();
    List<String> attrNames = vertex.getProperty(AtlasGraphUtilsV1.getPropertyKey(ret), List.class);

    if (CollectionUtils.isNotEmpty(attrNames)) {
      for (String attrName : attrNames) {
        String propertyKey = AtlasGraphUtilsV1.getPropertyKey(ret, attrName);
        String attribJson = vertex.getProperty(propertyKey, String.class);

        attributeDefs.add(
            toAttributeDefFromJson(
                structDef, AtlasType.fromJson(attribJson, Map.class), typeDefStore));
      }
    }
    ret.setAttributeDefs(attributeDefs);

    return ret;
  }
  private static AtlasAttributeDef toAttributeDefFromJson(
      AtlasStructDef structDef, Map attribInfo, AtlasTypeDefGraphStoreV1 typeDefStore)
      throws AtlasBaseException {
    AtlasAttributeDef ret = new AtlasAttributeDef();

    ret.setName((String) attribInfo.get("name"));
    ret.setTypeName((String) attribInfo.get("dataType"));
    ret.setUnique((Boolean) attribInfo.get("isUnique"));
    ret.setIndexable((Boolean) attribInfo.get("isIndexable"));

    String attrTypeName = ret.getTypeName();

    if (AtlasTypeUtil.isArrayType(attrTypeName)) {
      Set<String> typeNames = AtlasTypeUtil.getReferencedTypeNames(ret.getTypeName());

      if (typeNames.size() > 0) {
        attrTypeName = typeNames.iterator().next();
      }
    }

    if (!AtlasTypeUtil.isBuiltInType(attrTypeName)) {
      AtlasVertex attributeType = typeDefStore.findTypeVertexByName(attrTypeName);

      // check for isComposite/reverseAttributeName for entity types
      if (attributeType != null && typeDefStore.isTypeVertex(attributeType, TypeCategory.CLASS)) {
        String reverseAttribName = (String) attribInfo.get("reverseAttributeName");
        Boolean isComposite = (Boolean) attribInfo.get("isComposite");

        if (StringUtils.isNotBlank(reverseAttribName) || isComposite) {
          if (AtlasTypeUtil.isMapType(attrTypeName)) {
            throw new AtlasBaseException(
                AtlasErrorCode.CONSTRAINT_NOT_SUPPORTED_ON_MAP_TYPE,
                structDef.getName(),
                ret.getName(),
                attrTypeName);
          }

          String refAttributeName = null;
          List<String> attrNames =
              attributeType.getProperty(AtlasGraphUtilsV1.getPropertyKey(attrTypeName), List.class);

          if (CollectionUtils.isNotEmpty(attrNames)) {
            for (String attrName : attrNames) {
              String attribJson =
                  attributeType.getProperty(
                      AtlasGraphUtilsV1.getPropertyKey(attrTypeName, attrName), String.class);

              Map refAttrInfo = AtlasType.fromJson(attribJson, Map.class);
              String refAttribType = (String) refAttrInfo.get("dataType");
              String refAttribRevAttribName = (String) refAttrInfo.get("reverseAttributeName");

              if (StringUtils.equals(refAttribType, structDef.getName())
                  && StringUtils.equals(refAttribRevAttribName, ret.getName())) {
                refAttributeName = (String) refAttrInfo.get("name");

                break;
              }
            }
          }

          if (isComposite) {
            if (StringUtils.isNotBlank(
                refAttributeName)) { // ex: hive_table.columns, hive_column.table
              Map<String, Object> params = new HashMap<>();
              params.put(AtlasConstraintDef.CONSTRAINT_PARAM_REF_ATTRIBUTE, refAttributeName);

              ret.addConstraint(new AtlasConstraintDef(CONSTRAINT_TYPE_MAPPED_FROM_REF, params));
            } else { // ex: hive_table.partitionKeys, with no reverseAttribute-reference
              ret.addConstraint(new AtlasConstraintDef(CONSTRAINT_TYPE_FOREIGN_KEY));
            }
          }

          if (StringUtils.isNotBlank(reverseAttribName)) { // ex: hive_column.table
            Map<String, Object> params = new HashMap<>();
            params.put(CONSTRAINT_PARAM_ON_DELETE, CONSTRAINT_PARAM_VAL_CASCADE);

            ret.addConstraint(new AtlasConstraintDef(CONSTRAINT_TYPE_FOREIGN_KEY, params));
          }
        }
      }
    }

    Map multiplicity = AtlasType.fromJson((String) attribInfo.get("multiplicity"), Map.class);
    Number minCount = (Number) multiplicity.get("lower");
    Number maxCount = (Number) multiplicity.get("upper");
    Boolean isUnique = (Boolean) multiplicity.get("isUnique");

    if (minCount == null || minCount.intValue() == 0) {
      ret.setOptional(true);
      ret.setValuesMinCount(0);
    } else {
      ret.setOptional(false);
      ret.setValuesMinCount(minCount.intValue());
    }

    if (maxCount == null || maxCount.intValue() < 2) {
      ret.setCardinality(AtlasAttributeDef.Cardinality.SINGLE);
      ret.setValuesMaxCount(1);
    } else {
      if (isUnique == null || isUnique == Boolean.FALSE) {
        ret.setCardinality(AtlasAttributeDef.Cardinality.LIST);
      } else {
        ret.setCardinality(AtlasAttributeDef.Cardinality.SET);
      }

      ret.setValuesMaxCount(maxCount.intValue());
    }

    return ret;
  }