All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.molgenis.data.meta.model.AttributeMetadata Maven / Gradle / Ivy

There is a newer version: 8.4.5
Show newest version
package org.molgenis.data.meta.model;

import static java.util.Objects.requireNonNull;
import static org.molgenis.data.meta.AttributeType.BOOL;
import static org.molgenis.data.meta.AttributeType.ENUM;
import static org.molgenis.data.meta.AttributeType.INT;
import static org.molgenis.data.meta.AttributeType.LONG;
import static org.molgenis.data.meta.AttributeType.MREF;
import static org.molgenis.data.meta.AttributeType.ONE_TO_MANY;
import static org.molgenis.data.meta.AttributeType.SCRIPT;
import static org.molgenis.data.meta.AttributeType.STRING;
import static org.molgenis.data.meta.AttributeType.TEXT;
import static org.molgenis.data.meta.AttributeType.XREF;
import static org.molgenis.data.meta.AttributeType.getValueString;
import static org.molgenis.data.meta.model.EntityType.AttributeRole.ROLE_ID;
import static org.molgenis.data.meta.model.EntityType.AttributeRole.ROLE_LABEL;
import static org.molgenis.data.meta.model.EntityType.AttributeRole.ROLE_LOOKUP;
import static org.molgenis.data.meta.model.MetaPackage.PACKAGE_META;
import static org.molgenis.data.meta.model.Package.PACKAGE_SEPARATOR;
import static org.molgenis.data.util.AttributeUtils.getValidIdAttributeTypes;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.molgenis.data.Sort;
import org.molgenis.data.meta.AttributeType;
import org.molgenis.data.meta.SystemEntityType;
import org.molgenis.data.util.EntityTypeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AttributeMetadata extends SystemEntityType {
  private static final String SIMPLE_NAME = "Attribute";
  public static final String ATTRIBUTE_META_DATA = PACKAGE_META + PACKAGE_SEPARATOR + SIMPLE_NAME;

  public static final String ID = "id";
  public static final String NAME = "name";
  public static final String ENTITY = "entity";
  public static final String SEQUENCE_NR = "sequenceNr";
  public static final String TYPE = "type";
  public static final String IS_ID_ATTRIBUTE = "isIdAttribute";
  public static final String IS_LABEL_ATTRIBUTE = "isLabelAttribute";
  public static final String LOOKUP_ATTRIBUTE_INDEX = "lookupAttributeIndex";
  public static final String REF_ENTITY_TYPE = "refEntityType";
  /** Deleting an entity also deletes the referenced entity when cascading delete is enabled */
  public static final String IS_CASCADE_DELETE = "isCascadeDelete";

  /**
   * For attributes with data type ONE_TO_MANY defines the attribute in the referenced entity that
   * owns the relationship.
   */
  public static final String MAPPED_BY = "mappedBy";

  /**
   * For attributes with data type ONE_TO_MANY defines how to sort the entity collection. Syntax:
   * attribute_name,[ASC | DESC] [;attribute_name,[ASC | DESC]]* - If ASC or DESC is not specified,
   * ASC (ascending order) is assumed. - If the ordering element is not specified, ordering by the
   * id attribute of the associated entity is assumed.
   */
  public static final String ORDER_BY = "orderBy";

  public static final String LABEL = "label";
  public static final String DESCRIPTION = "description";
  public static final String IS_NULLABLE = "isNullable";
  public static final String IS_AUTO = "isAuto";
  public static final String IS_VISIBLE = "isVisible";
  public static final String IS_UNIQUE = "isUnique";
  public static final String IS_READ_ONLY = "isReadOnly";
  public static final String IS_AGGREGATABLE = "isAggregatable";
  public static final String EXPRESSION = "expression";
  public static final String ENUM_OPTIONS = "enumOptions";
  public static final String RANGE_MIN = "rangeMin";
  public static final String RANGE_MAX = "rangeMax";
  public static final String PARENT = "parent";
  public static final String CHILDREN = "children";
  public static final String TAGS = "tags";
  public static final String NULLABLE_EXPRESSION = "nullableExpression";
  public static final String VISIBLE_EXPRESSION = "visibleExpression";
  public static final String VALIDATION_EXPRESSION = "validationExpression";
  public static final String DEFAULT_VALUE = "defaultValue";

  private TagMetadata tagMetadata;
  private EntityTypeMetadata entityTypeMeta;

  public AttributeMetadata() {
    super(SIMPLE_NAME, PACKAGE_META);
  }

  public void init() {
    setId(ATTRIBUTE_META_DATA);
    setLabel(SIMPLE_NAME);
    setDescription("Meta data for attributes");

    addAttribute(ID, ROLE_ID).setVisible(false).setAuto(true).setLabel("Identifier");
    addAttribute(NAME, ROLE_LABEL, ROLE_LOOKUP)
        .setNillable(false)
        .setReadOnly(true)
        .setLabel("Name");
    addAttribute(ENTITY)
        .setDataType(XREF)
        .setRefEntity(entityTypeMeta)
        .setLabel("Entity")
        .setNillable(false)
        .setReadOnly(true);
    addAttribute(SEQUENCE_NR)
        .setDataType(INT)
        .setLabel("Sequence number")
        .setDescription("Number that defines order of attributes in a entity")
        .setNillable(false);
    addAttribute(TYPE)
        .setDataType(ENUM)
        .setEnumOptions(AttributeType.getOptionsLowercase())
        .setNillable(false)
        .setLabel("Data type");
    addAttribute(IS_ID_ATTRIBUTE)
        .setDataType(BOOL)
        .setLabel("ID attribute")
        .setValidationExpression(getIdAttributeValidationExpression());
    addAttribute(IS_LABEL_ATTRIBUTE).setDataType(BOOL).setLabel("Label attribute");
    addAttribute(LOOKUP_ATTRIBUTE_INDEX)
        .setDataType(INT)
        .setLabel("Lookup attribute index")
        .setValidationExpression(getLookupAttributeValidationExpression());
    Attribute parentAttr =
        addAttribute(PARENT).setDataType(XREF).setRefEntity(this).setLabel("Attribute parent");
    addAttribute(CHILDREN)
        .setDataType(ONE_TO_MANY)
        .setRefEntity(this)
        .setMappedBy(parentAttr)
        .setOrderBy(new Sort(SEQUENCE_NR))
        .setLabel("Attribute parts")
        .setCascadeDelete(true);
    addAttribute(REF_ENTITY_TYPE)
        .setDataType(XREF)
        .setRefEntity(entityTypeMeta)
        .setLabel("Referenced entity")
        .setValidationExpression(getRefEntityValidationExpression());
    addAttribute(IS_CASCADE_DELETE)
        .setDataType(BOOL)
        .setLabel("Cascade delete")
        .setDescription("Delete corresponding referenced entities on delete")
        .setValidationExpression(getCascadeDeleteValidationExpression());
    addAttribute(MAPPED_BY)
        .setDataType(XREF)
        .setRefEntity(this)
        .setLabel("Mapped by")
        .setDescription(
            "Attribute in the referenced entity that owns the relationship of a onetomany attribute")
        .setValidationExpression(getMappedByValidationExpression())
        .setReadOnly(true);
    addAttribute(ORDER_BY)
        .setLabel("Order by")
        .setDescription(
            "Order expression that defines entity collection order of a onetomany attribute (e.g. \"attr0\", \"attr0,ASC\", \"attr0,DESC\" or \"attr0,ASC;attr1,DESC\"")
        .setValidationExpression(getOrderByValidationExpression());
    addAttribute(EXPRESSION)
        .setNillable(true)
        .setLabel("Expression")
        .setDescription("Computed value expression in Magma JavaScript");
    addAttribute(IS_NULLABLE)
        .setDataType(BOOL)
        .setNillable(false)
        .setLabel("Nillable")
        .setDefaultValue("true")
        .setValidationExpression(getNullableValidationExpression());
    addAttribute(IS_AUTO)
        .setDataType(BOOL)
        .setNillable(false)
        .setLabel("Auto")
        .setDescription("Auto generated values")
        .setValidationExpression(getAutoValidationExpression());
    addAttribute(IS_VISIBLE).setDataType(BOOL).setNillable(false).setLabel("Visible");
    addAttribute(LABEL, ROLE_LOOKUP).setLabel("Label");
    addAttribute(DESCRIPTION).setDataType(TEXT).setLabel("Description");
    addAttribute(IS_AGGREGATABLE)
        .setDataType(BOOL)
        .setNillable(false)
        .setLabel("Aggregatable")
        .setValidationExpression(getAggregatableExpression());
    addAttribute(ENUM_OPTIONS)
        .setDataType(TEXT)
        .setLabel("Enum values")
        .setDescription("For data type ENUM")
        .setValidationExpression(getEnumOptionsValidationExpression());
    addAttribute(RANGE_MIN)
        .setDataType(LONG)
        .setLabel("Range min")
        .setValidationExpression(getRangeValidationExpression(RANGE_MIN));
    addAttribute(RANGE_MAX)
        .setDataType(LONG)
        .setLabel("Range max")
        .setValidationExpression(getRangeValidationExpression(RANGE_MAX));
    addAttribute(IS_READ_ONLY).setDataType(BOOL).setNillable(false).setLabel("Read-only");
    addAttribute(IS_UNIQUE).setDataType(BOOL).setNillable(false).setLabel("Unique");
    addAttribute(TAGS).setDataType(MREF).setRefEntity(tagMetadata).setLabel("Tags");
    addAttribute(NULLABLE_EXPRESSION)
        .setDataType(SCRIPT)
        .setNillable(true)
        .setLabel("Nullable expression");
    addAttribute(VISIBLE_EXPRESSION)
        .setDataType(SCRIPT)
        .setNillable(true)
        .setLabel("Visible expression");
    addAttribute(VALIDATION_EXPRESSION)
        .setDataType(SCRIPT)
        .setNillable(true)
        .setLabel("Validation expression");
    addAttribute(DEFAULT_VALUE).setDataType(TEXT).setNillable(true).setLabel("Default value");
  }

  // setter injection instead of constructor injection to avoid unresolvable circular dependencies
  @Autowired
  public void setTagMetadata(TagMetadata tagMetadata) {
    this.tagMetadata = requireNonNull(tagMetadata);
  }

  @Autowired
  public void setEntityTypeMetadata(EntityTypeMetadata entityTypeMeta) {
    this.entityTypeMeta = requireNonNull(entityTypeMeta);
  }

  private static String getMappedByValidationExpression() {
    return "$('"
        + MAPPED_BY
        + "').isNull().and($('"
        + TYPE
        + "').eq('"
        + getValueString(ONE_TO_MANY)
        + "').not()).or("
        + "$('"
        + MAPPED_BY
        + "').isNull().not().and($('"
        + TYPE
        + "').eq('"
        + getValueString(ONE_TO_MANY)
        + "'))).value()";
  }

  private static String getOrderByValidationExpression() {
    String regex = "/^\\w+(,(ASC|DESC))?(;\\w+(,(ASC|DESC))?)*$/";
    return "$('"
        + ORDER_BY
        + "').isNull().or("
        + "$('"
        + ORDER_BY
        + "').matches("
        + regex
        + ").and($('"
        + TYPE
        + "').eq('"
        + getValueString(ONE_TO_MANY)
        + "'))).value()";
  }

  private static String getNullableValidationExpression() {
    return "$('"
        + IS_NULLABLE
        + "').eq(true).or("
        + "$('"
        + NULLABLE_EXPRESSION
        + "').isNull()).value()";
  }

  private static String getEnumOptionsValidationExpression() {
    return "$('"
        + ENUM_OPTIONS
        + "').isNull().and($('"
        + TYPE
        + "').eq('"
        + getValueString(ENUM)
        + "').not()).or("
        + "$('"
        + ENUM_OPTIONS
        + "').isNull().not().and($('"
        + TYPE
        + "').eq('"
        + getValueString(ENUM)
        + "'))).value()";
  }

  private static String getRefEntityValidationExpression() {
    String regex =
        "/^("
            + Arrays.stream(AttributeType.values())
                .filter(EntityTypeUtils::isReferenceType)
                .map(AttributeType::getValueString)
                .collect(Collectors.joining("|"))
            + ")$/";

    return "$('"
        + REF_ENTITY_TYPE
        + "').isNull().and($('"
        + TYPE
        + "').matches("
        + regex
        + ").not()).or("
        + "$('"
        + REF_ENTITY_TYPE
        + "').isNull().not().and($('"
        + TYPE
        + "').matches("
        + regex
        + "))).value()";
  }

  private static String getCascadeDeleteValidationExpression() {
    return "$('"
        + IS_CASCADE_DELETE
        + "').isNull().or("
        + "$('"
        + REF_ENTITY_TYPE
        + "').isNull().not()).value()";
  }

  private static String getAutoValidationExpression() {
    String dateTypeRegex =
        "/^("
            + Arrays.stream(AttributeType.values())
                .filter(EntityTypeUtils::isDateType)
                .map(AttributeType::getValueString)
                .collect(Collectors.joining("|"))
            + ")$/";

    String autoIsTrue = "$('" + IS_AUTO + "').eq(true)";
    String autoIsFalse = "$('" + IS_AUTO + "').eq(false)";
    String autoIsTrueAndIsIdIsTrueAndTypeIsStringOrNull =
        autoIsTrue
            + ".and($('"
            + IS_ID_ATTRIBUTE
            + "').eq(true).and($('"
            + TYPE
            + "').eq('"
            + getValueString(STRING)
            + "').or($('"
            + TYPE
            + "').isNull())))";
    String autoIsTrueAndIsIdIsFalseOrNullAndTypeIsDateType =
        autoIsTrue
            + ".and($('"
            + IS_ID_ATTRIBUTE
            + "').eq(false).or($('"
            + IS_ID_ATTRIBUTE
            + "').isNull())).and($('"
            + TYPE
            + "').matches("
            + dateTypeRegex
            + "))";

    return autoIsFalse
        + ".or("
        + autoIsTrueAndIsIdIsTrueAndTypeIsStringOrNull
        + ").or("
        + autoIsTrueAndIsIdIsFalseOrNullAndTypeIsDateType
        + ").value()";
  }

  private static String getRangeValidationExpression(String attribute) {
    String regex =
        "/^("
            + Arrays.stream(AttributeType.values())
                .filter(EntityTypeUtils::isIntegerType)
                .map(AttributeType::getValueString)
                .collect(Collectors.joining("|"))
            + ")$/";
    String rangeIsNull = "$('" + attribute + "').isNull()";

    return rangeIsNull
        + ".or("
        + rangeIsNull
        + ".not().and($('"
        + TYPE
        + "').matches("
        + regex
        + "))).value()";
  }

  // Package private for testability
  static String getIdAttributeValidationExpression() {
    String isIdIsTrue = "$('" + IS_ID_ATTRIBUTE + "').eq(true)";
    String isIdIsFalseOrNull =
        "$('" + IS_ID_ATTRIBUTE + "').eq(false).or($('" + IS_ID_ATTRIBUTE + "').isNull())";

    // Use the valid ID attribute types to constuct the validation expression
    List typeExpressions =
        getValidIdAttributeTypes().stream()
            .map(attributeType -> "$('" + TYPE + "').eq('" + getValueString(attributeType) + "')")
            .collect(Collectors.toList());

    boolean first = true;
    StringBuilder typeIsNullOrStringOrIntOrLong = new StringBuilder();
    for (String expression : typeExpressions) {
      if (first) {
        typeIsNullOrStringOrIntOrLong.append(expression);
        first = false;
        continue;
      }
      typeIsNullOrStringOrIntOrLong.append(".or(").append(expression).append(")");
    }
    typeIsNullOrStringOrIntOrLong.append(".or($('" + TYPE + "').isNull())");

    String nullableIsFalse = "$('" + IS_NULLABLE + "').eq(false)";

    return isIdIsFalseOrNull
        + ".or("
        + isIdIsTrue
        + ".and("
        + typeIsNullOrStringOrIntOrLong
        + ").and("
        + nullableIsFalse
        + ")).value()";
  }

  private static String getLookupAttributeValidationExpression() {
    String regex =
        "/^("
            + Arrays.stream(AttributeType.values())
                .filter(EntityTypeUtils::isReferenceType)
                .map(AttributeType::getValueString)
                .collect(Collectors.joining("|"))
            + ")$/";

    return "$('"
        + LOOKUP_ATTRIBUTE_INDEX
        + "').isNull().or("
        + "$('"
        + LOOKUP_ATTRIBUTE_INDEX
        + "').isNull().not().and($('"
        + TYPE
        + "').matches("
        + regex
        + ").not())).value()";
  }

  private static String getAggregatableExpression() {
    String aggregatableIsNullOrFalse =
        "$('" + IS_AGGREGATABLE + "').isNull().or($('" + IS_AGGREGATABLE + "').eq(false))";
    String regex =
        "/^("
            + Arrays.stream(AttributeType.values())
                .filter(EntityTypeUtils::isReferenceType)
                .map(AttributeType::getValueString)
                .collect(Collectors.joining("|"))
            + ")$/";
    return aggregatableIsNullOrFalse
        + ".or("
        + "$('"
        + TYPE
        + "')"
        + ".matches("
        + regex
        + ")"
        + ".and("
        + "$('"
        + IS_NULLABLE
        + "')"
        + ".eq(false)"
        + ")"
        + ")"
        + ".or("
        + "$('"
        + TYPE
        + "')"
        + ".matches("
        + regex
        + ").not()).value()";
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy