@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 updateByName(String name, AtlasStructDef structDef)
      throws AtlasBaseException {
    if (LOG.isDebugEnabled()) {
      LOG.debug("==> AtlasStructDefStoreV1.updateByName({}, {})", name, 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 vertex = typeDefStore.findTypeVertexByNameAndCategory(name, TypeCategory.STRUCT);

    if (vertex == null) {
      throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_NOT_FOUND, name);
    }

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

    AtlasStructDef ret = toStructDef(vertex);

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

    return ret;
  }
  public static void updateVertexPreUpdate(
      AtlasStructDef structDef,
      AtlasStructType structType,
      AtlasVertex vertex,
      AtlasTypeDefGraphStoreV1 typeDefStore)
      throws AtlasBaseException {

    List<String> attrNames = new ArrayList<>();
    if (CollectionUtils.isNotEmpty(structDef.getAttributeDefs())) {
      for (AtlasAttributeDef attributeDef : structDef.getAttributeDefs()) {
        attrNames.add(attributeDef.getName());
      }
    }

    List<String> currAttrNames =
        vertex.getProperty(AtlasGraphUtilsV1.getPropertyKey(structDef), List.class);

    // delete attributes that are not present in updated structDef
    if (CollectionUtils.isNotEmpty(currAttrNames)) {
      for (String currAttrName : currAttrNames) {
        if (!attrNames.contains(currAttrName)) {
          throw new AtlasBaseException(
              AtlasErrorCode.ATTRIBUTE_DELETION_NOT_SUPPORTED, structDef.getName(), currAttrName);
        }
      }
    }

    typeDefStore.updateTypeVertex(structDef, vertex);

    // add/update attributes that are present in updated structDef
    if (CollectionUtils.isNotEmpty(structDef.getAttributeDefs())) {
      for (AtlasAttributeDef attributeDef : structDef.getAttributeDefs()) {
        if (CollectionUtils.isEmpty(currAttrNames)
            || !currAttrNames.contains(attributeDef.getName())) {
          // new attribute - only allow if optional
          if (!attributeDef.isOptional()) {
            throw new AtlasBaseException(
                AtlasErrorCode.CANNOT_ADD_MANDATORY_ATTRIBUTE,
                structDef.getName(),
                attributeDef.getName());
          }
        }

        String propertyKey = AtlasGraphUtilsV1.getPropertyKey(structDef, attributeDef.getName());

        AtlasGraphUtilsV1.setProperty(
            vertex, propertyKey, toJsonFromAttributeDef(attributeDef, structType));
      }
    }

    AtlasGraphUtilsV1.setProperty(vertex, AtlasGraphUtilsV1.getPropertyKey(structDef), attrNames);
  }
  @Override
  public AtlasStructDef update(AtlasStructDef structDef) throws AtlasBaseException {
    if (LOG.isDebugEnabled()) {
      LOG.debug("==> AtlasStructDefStoreV1.update({})", structDef);
    }

    AtlasStructDef ret =
        StringUtils.isNotBlank(structDef.getGuid())
            ? updateByGuid(structDef.getGuid(), structDef)
            : updateByName(structDef.getName(), structDef);

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

    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;
  }