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

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

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

import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.removeAll;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static org.molgenis.data.meta.AttributeType.STRING;
import static org.molgenis.data.meta.model.AttributeMetadata.AttributeCopyMode;
import static org.molgenis.data.meta.model.AttributeMetadata.CHILDREN;
import static org.molgenis.data.meta.model.AttributeMetadata.DEFAULT_VALUE;
import static org.molgenis.data.meta.model.AttributeMetadata.DESCRIPTION;
import static org.molgenis.data.meta.model.AttributeMetadata.ENTITY;
import static org.molgenis.data.meta.model.AttributeMetadata.ENUM_OPTIONS;
import static org.molgenis.data.meta.model.AttributeMetadata.EXPRESSION;
import static org.molgenis.data.meta.model.AttributeMetadata.ID;
import static org.molgenis.data.meta.model.AttributeMetadata.IS_AGGREGATABLE;
import static org.molgenis.data.meta.model.AttributeMetadata.IS_AUTO;
import static org.molgenis.data.meta.model.AttributeMetadata.IS_CASCADE_DELETE;
import static org.molgenis.data.meta.model.AttributeMetadata.IS_ID_ATTRIBUTE;
import static org.molgenis.data.meta.model.AttributeMetadata.IS_LABEL_ATTRIBUTE;
import static org.molgenis.data.meta.model.AttributeMetadata.IS_NULLABLE;
import static org.molgenis.data.meta.model.AttributeMetadata.IS_READ_ONLY;
import static org.molgenis.data.meta.model.AttributeMetadata.IS_UNIQUE;
import static org.molgenis.data.meta.model.AttributeMetadata.IS_VISIBLE;
import static org.molgenis.data.meta.model.AttributeMetadata.LABEL;
import static org.molgenis.data.meta.model.AttributeMetadata.LOOKUP_ATTRIBUTE_INDEX;
import static org.molgenis.data.meta.model.AttributeMetadata.MAPPED_BY;
import static org.molgenis.data.meta.model.AttributeMetadata.NAME;
import static org.molgenis.data.meta.model.AttributeMetadata.NULLABLE_EXPRESSION;
import static org.molgenis.data.meta.model.AttributeMetadata.ORDER_BY;
import static org.molgenis.data.meta.model.AttributeMetadata.PARENT;
import static org.molgenis.data.meta.model.AttributeMetadata.RANGE_MAX;
import static org.molgenis.data.meta.model.AttributeMetadata.RANGE_MIN;
import static org.molgenis.data.meta.model.AttributeMetadata.REF_ENTITY_TYPE;
import static org.molgenis.data.meta.model.AttributeMetadata.SEQUENCE_NR;
import static org.molgenis.data.meta.model.AttributeMetadata.TAGS;
import static org.molgenis.data.meta.model.AttributeMetadata.TYPE;
import static org.molgenis.data.meta.model.AttributeMetadata.VALIDATION_EXPRESSION;
import static org.molgenis.data.meta.model.AttributeMetadata.VISIBLE_EXPRESSION;
import static org.molgenis.data.meta.model.EntityType.AttributeCopyMode.DEEP_COPY_ATTRS;
import static org.molgenis.data.util.AttributeUtils.getI18nAttributeName;

import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.molgenis.data.Entity;
import org.molgenis.data.Range;
import org.molgenis.data.Sort;
import org.molgenis.data.meta.AttributeType;
import org.molgenis.data.support.StaticEntity;
import org.molgenis.data.util.EntityTypeUtils;
import org.molgenis.util.i18n.Labeled;

/** Attribute defines the properties of an entity. Synonyms: feature, column, data item. */
public class Attribute extends StaticEntity implements Labeled {
  private AttributeType cachedDataType;

  public Attribute(Entity entity) {
    super(entity);
  }

  /**
   * Creates a new attribute. Normally called by its {@link AttributeFactory entity factory}.
   *
   * @param entityType attribute meta data
   */
  public Attribute(EntityType entityType) {
    super(entityType);
    setDefaultValues();
  }

  /**
   * Creates a new attribute with the given identifier. Normally called by its {@link
   * AttributeFactory entity factory}.
   *
   * @param attrId attribute identifier (not the attribute name)
   * @param entityType attribute meta data
   */
  public Attribute(String attrId, EntityType entityType) {
    super(entityType);
    setDefaultValues();
    setIdentifier(attrId);
  }

  /**
   * Copy-factory (instead of copy-constructor to avoid accidental method overloading to {@link
   * #Attribute(EntityType)}). Creates a copy of attribute with a shallow copy of referenced entity
   * and tags.
   *
   * @param attrMeta attribute
   * @param attrCopyMode attribute copy mode that defines whether to deep-copy or shallow-copy
   *     attribute parts
   * @param attrFactory attribute factory used to create new attributes in deep-copy mode
   * @return shallow or deep copy of attribute
   */
  public static Attribute newInstance(
      Attribute attrMeta, AttributeCopyMode attrCopyMode, AttributeFactory attrFactory) {
    Attribute attrMetaCopy = attrFactory.create(); // create new attribute with unique identifier
    attrMetaCopy.setName(attrMeta.getName());
    attrMetaCopy.setEntity(attrMeta.getEntity());
    attrMetaCopy.setSequenceNumber(attrMeta.getSequenceNumber());
    attrMetaCopy.setDataType(attrMeta.getDataType());
    attrMetaCopy.setIdAttribute(attrMeta.isIdAttribute());
    attrMetaCopy.setLabelAttribute(attrMeta.isLabelAttribute());
    attrMetaCopy.setLookupAttributeIndex(attrMeta.getLookupAttributeIndex());
    if (attrMeta.hasRefEntity()) {
      attrMetaCopy.setRefEntity(attrMeta.getRefEntity());
    }
    attrMetaCopy.setMappedBy(attrMeta.getMappedBy()); // do not deep-copy
    attrMetaCopy.setOrderBy(attrMeta.getOrderBy());
    attrMetaCopy.setExpression(attrMeta.getExpression());
    attrMetaCopy.setNillable(attrMeta.isNillable());
    attrMetaCopy.setAuto(attrMeta.isAuto());
    attrMetaCopy.setVisible(attrMeta.isVisible());
    attrMetaCopy.setLabel(attrMeta.getLabel());
    attrMetaCopy.setDescription(attrMeta.getDescription());
    attrMetaCopy.setAggregatable(attrMeta.isAggregatable());
    attrMetaCopy.setEnumOptions(attrMeta.getEnumOptions());
    attrMetaCopy.setRangeMin(attrMeta.getRangeMin());
    attrMetaCopy.setRangeMax(attrMeta.getRangeMax());
    attrMetaCopy.setReadOnly(attrMeta.isReadOnly());
    attrMetaCopy.setUnique(attrMeta.isUnique());
    Attribute parentAttr = attrMeta.getParent();
    if (attrCopyMode == DEEP_COPY_ATTRS) {
      attrMetaCopy.setParent(
          parentAttr != null ? Attribute.newInstance(parentAttr, attrCopyMode, attrFactory) : null);
    } else {
      attrMetaCopy.setParent(parentAttr);
    }

    attrMetaCopy.setTags(Lists.newArrayList(attrMeta.getTags())); // do not deep-copy
    attrMetaCopy.setNullableExpression(attrMeta.getNullableExpression());
    attrMetaCopy.setValidationExpression(attrMeta.getValidationExpression());
    attrMetaCopy.setVisibleExpression(attrMeta.getVisibleExpression());
    attrMetaCopy.setDefaultValue(attrMeta.getDefaultValue());
    return attrMetaCopy;
  }

  public String getIdentifier() {
    return getString(ID);
  }

  public Attribute setIdentifier(String identifier) {
    set(ID, identifier);
    return this;
  }

  /**
   * Name of the attribute
   *
   * @return attribute name
   */
  public String getName() {
    return getString(NAME);
  }

  public Attribute setName(String name) {
    set(NAME, name);
    return this;
  }

  /**
   * Attribute sequence number that determines attribute order within an entity
   *
   * @return attribute sequence number
   */
  public Integer getSequenceNumber() {
    return getInt(SEQUENCE_NR);
  }

  public Attribute setSequenceNumber(int seqNr) {
    set(SEQUENCE_NR, seqNr);
    return this;
  }

  public EntityType getEntity() {
    return getEntity(ENTITY, EntityType.class);
  }

  public Attribute setEntity(EntityType entityMeta) {
    set(ENTITY, entityMeta);
    return this;
  }

  public boolean isIdAttribute() {
    Boolean isIdAttr = getBoolean(IS_ID_ATTRIBUTE);
    return isIdAttr != null && isIdAttr;
  }

  public Attribute setIdAttribute(Boolean isIdAttr) {
    set(IS_ID_ATTRIBUTE, isIdAttr);
    if (isIdAttr != null && isIdAttr) {
      setReadOnly(true);
      setUnique(true);
      setNillable(false);
    }
    return this;
  }

  public boolean isLabelAttribute() {
    Boolean isLabelAttr = getBoolean(IS_LABEL_ATTRIBUTE);
    return isLabelAttr != null && isLabelAttr;
  }

  public Attribute setLabelAttribute(Boolean isLabelAttr) {
    set(IS_LABEL_ATTRIBUTE, isLabelAttr);
    return this;
  }

  @Nullable
  @CheckForNull
  public Integer getLookupAttributeIndex() {
    return getInt(LOOKUP_ATTRIBUTE_INDEX);
  }

  public Attribute setLookupAttributeIndex(Integer lookupAttrIdx) {
    set(LOOKUP_ATTRIBUTE_INDEX, lookupAttrIdx);
    return this;
  }

  /**
   * Label of the attribute in the default language if set else returns name
   *
   * @return attribute label
   */
  @Nullable
  @CheckForNull
  public String getLabel() {
    String label = getString(LABEL);
    return label != null ? label : getName();
  }

  /**
   * Label of the attribute in the default language if set else returns name
   *
   * @return attribute label
   */
  @Nullable
  @CheckForNull
  public String getLabel(String languageCode) {
    String i18nString = getString(getI18nAttributeName(LABEL, languageCode));
    return i18nString != null ? i18nString : getLabel();
  }

  public Attribute setLabel(String label) {
    set(LABEL, label);
    return this;
  }

  public Attribute setLabel(String languageCode, String label) {
    set(getI18nAttributeName(LABEL, languageCode), label);
    return this;
  }

  /**
   * Description of the attribute
   *
   * @return attribute description or null
   */
  @Nullable
  @CheckForNull
  public String getDescription() {
    return getString(DESCRIPTION);
  }

  /**
   * Description of the attribute in the requested languages
   *
   * @return attribute description or null
   */
  @Nullable
  @CheckForNull
  public String getDescription(String languageCode) {
    String i18nDescription = getString(getI18nAttributeName(DESCRIPTION, languageCode));
    return i18nDescription != null ? i18nDescription : getDescription();
  }

  public Attribute setDescription(String description) {
    set(DESCRIPTION, description);
    return this;
  }

  public Attribute setDescription(String languageCode, String description) {
    set(getI18nAttributeName(DESCRIPTION, languageCode), description);
    return this;
  }

  /**
   * Data type of the attribute
   *
   * @return attribute data type
   */
  public AttributeType getDataType() {
    return getCachedDataType();
  }

  public Attribute setDataType(AttributeType dataType) {
    invalidateCachedDataType();

    set(TYPE, AttributeType.getValueString(dataType));
    return this;
  }

  @Nullable
  @CheckForNull
  public Boolean getCascadeDelete() {
    return getBoolean(IS_CASCADE_DELETE);
  }

  public Attribute setCascadeDelete(Boolean isCascadeDelete) {
    set(IS_CASCADE_DELETE, isCascadeDelete);
    return this;
  }

  /**
   * When getDataType=compound, get compound attribute parts
   *
   * @return Iterable of attributes or empty Iterable if no attribute parts exist
   */
  public Iterable getChildren() {
    return getEntities(CHILDREN, Attribute.class);
  }

  /** @return whether this attribute references an entity type */
  public boolean hasRefEntity() {
    return EntityTypeUtils.isReferenceType(this);
  }

  /**
   * When getDataType=xref/mref, get other end of xref
   *
   * @return referenced entity
   * @throws UnsupportedOperationException if attribute type is not a reference type
   */
  public EntityType getRefEntity() {
    if (!hasRefEntity()) {
      throw new UnsupportedOperationException(
          String.format("getRefEntity not supported for attribute type '%s'", getDataType()));
    }
    return getEntity(REF_ENTITY_TYPE, EntityType.class);
  }

  public Attribute setRefEntity(EntityType refEntity) {
    set(REF_ENTITY_TYPE, refEntity);
    return this;
  }

  @Nullable
  @CheckForNull
  public Attribute getMappedBy() {
    return getEntity(MAPPED_BY, Attribute.class);
  }

  public Attribute setMappedBy(Attribute mappedByAttr) {
    set(MAPPED_BY, mappedByAttr);
    return this;
  }

  /**
   * Indicates if this attribute is the one-to-many back-reference of a bidirectionally navigable
   * relationship.
   */
  public boolean isMappedBy() {
    return getMappedBy() != null;
  }

  @Nullable
  @CheckForNull
  public Sort getOrderBy() {
    String orderByStr = getString(ORDER_BY);
    return orderByStr != null ? Sort.parse(orderByStr) : null;
  }

  public Attribute setOrderBy(Sort sort) {
    String orderByStr = sort != null ? sort.toSortString() : null;
    set(ORDER_BY, orderByStr);
    return this;
  }

  /**
   * Expression used to compute this attribute.
   *
   * @return String representation of expression, in JSON format
   */
  @Nullable
  @CheckForNull
  public String getExpression() {
    return getString(EXPRESSION);
  }

  public Attribute setExpression(String expression) {
    set(EXPRESSION, expression);
    return this;
  }

  /**
   * Wheter attribute has an expression or not
   *
   * @return true if attribute has expression
   */
  public boolean hasExpression() {
    return getExpression() != null;
  }

  /**
   * Whether attribute has not null constraint
   *
   * @return true if this attribute is nillable
   */
  public boolean isNillable() {
    return requireNonNull(getBoolean(IS_NULLABLE));
  }

  public Attribute setNillable(boolean nillable) {
    set(IS_NULLABLE, nillable);
    return this;
  }

  /**
   * When true the attribute is automatically assigned a value when persisted (for example the
   * current date)
   *
   * @return true if this attribute is automatically assigned
   */
  public boolean isAuto() {
    return requireNonNull(getBoolean(IS_AUTO));
  }

  public Attribute setAuto(boolean auto) {
    set(IS_AUTO, auto);
    return this;
  }

  /**
   * Should this attribute be visible to the user?
   *
   * @return true if this attribute is visible
   */
  public boolean isVisible() {
    return requireNonNull(getBoolean(IS_VISIBLE));
  }

  public Attribute setVisible(boolean visible) {
    set(IS_VISIBLE, visible);
    return this;
  }

  /**
   * Whether this attribute can be used to aggregate on. Default only attributes of type 'BOOL',
   * 'XREF' and 'CATEGORICAL' are isAggregatable.
   *
   * @return true if this attribute is isAggregatable
   */
  public boolean isAggregatable() {
    return requireNonNull(getBoolean(IS_AGGREGATABLE));
  }

  public Attribute setAggregatable(boolean isAggregatable) {
    set(IS_AGGREGATABLE, isAggregatable);
    return this;
  }

  /**
   * For enum fields returns the possible enum values
   *
   * @return enum values
   */
  public List getEnumOptions() {
    String enumOptionsStr = getString(ENUM_OPTIONS);
    return enumOptionsStr != null ? asList(enumOptionsStr.split(",")) : emptyList();
  }

  public Attribute setEnumOptions(Class> e) {
    return setEnumOptions(stream(e.getEnumConstants()).map(Enum::name).collect(toList()));
  }

  public Attribute setEnumOptions(List enumOptions) {
    set(ENUM_OPTIONS, toEnumOptionsString(enumOptions));
    return this;
  }

  @Nullable
  @CheckForNull
  public Long getRangeMin() {
    return getLong(RANGE_MIN);
  }

  public Attribute setRangeMin(Long rangeMin) {
    set(RANGE_MIN, rangeMin);
    return this;
  }

  @Nullable
  @CheckForNull
  public Long getRangeMax() {
    return getLong(RANGE_MAX);
  }

  public Attribute setRangeMax(Long rangeMax) {
    set(RANGE_MAX, rangeMax);
    return this;
  }

  /**
   * Whether attribute is readonly
   *
   * @return true if this attribute is read-only
   */
  public boolean isReadOnly() {
    return requireNonNull(getBoolean(IS_READ_ONLY));
  }

  public Attribute setReadOnly(boolean readOnly) {
    set(IS_READ_ONLY, readOnly);
    return this;
  }

  /**
   * Whether attribute should have an unique value for each entity
   *
   * @return true if this attribute is unique
   */
  public boolean isUnique() {
    return requireNonNull(getBoolean(IS_UNIQUE));
  }

  public Attribute setUnique(boolean unique) {
    set(IS_UNIQUE, unique);
    return this;
  }

  /**
   * JavaScript expression to determine at runtime if the attribute value is required
   *
   * @return expression
   */
  @Nullable
  @CheckForNull
  public String getNullableExpression() {
    return getString(NULLABLE_EXPRESSION);
  }

  public Attribute setNullableExpression(String nullableExpression) {
    set(NULLABLE_EXPRESSION, nullableExpression);
    return this;
  }

  /**
   * JavaScript expression to determine at runtime if the attribute must be visible or not in the
   * form
   *
   * @return expression
   */
  @Nullable
  @CheckForNull
  public String getVisibleExpression() {
    return getString(VISIBLE_EXPRESSION);
  }

  public Attribute setVisibleExpression(String visibleExpression) {
    set(VISIBLE_EXPRESSION, visibleExpression);
    return this;
  }

  /** JavaScript expression to validate the value of the attribute */
  @Nullable
  @CheckForNull
  public String getValidationExpression() {
    return getString(VALIDATION_EXPRESSION);
  }

  public Attribute setValidationExpression(String validationExpression) {
    set(VALIDATION_EXPRESSION, validationExpression);
    return this;
  }

  public boolean hasDefaultValue() {
    return getDefaultValue() != null;
  }

  /**
   * Default value expression
   *
   * @return attribute default value
   */
  @Nullable
  @CheckForNull
  public String getDefaultValue() {
    return getString(DEFAULT_VALUE);
  }

  public Attribute setDefaultValue(String defaultValue) {
    set(DEFAULT_VALUE, defaultValue);
    return this;
  }

  /**
   * For int and long fields, the value must be between min and max (included) of the range
   *
   * @return attribute value range
   */
  public Range getRange() {
    Long rangeMin = getRangeMin();
    Long rangeMax = getRangeMax();
    return rangeMin != null || rangeMax != null ? new Range(rangeMin, rangeMax) : null;
  }

  public Attribute setRange(Range range) {
    set(RANGE_MIN, range.getMin());
    set(RANGE_MAX, range.getMax());
    return this;
  }

  @Nullable
  @CheckForNull
  public Attribute getParent() {
    return getEntity(PARENT, Attribute.class);
  }

  public Attribute setParent(Attribute parentAttr) {
    Attribute currentParent = getParent();
    if (currentParent != null) {
      currentParent.removeChild(this);
    }
    set(PARENT, parentAttr);

    if (parentAttr != null) {
      parentAttr.addChild(this);
    }
    return this;
  }

  /**
   * Get attribute part by name (case insensitive), returns null if not found
   *
   * @param attrName attribute name (case insensitive)
   * @return attribute or null
   */
  public Attribute getChild(String attrName) {
    Iterable attrParts = getEntities(CHILDREN, Attribute.class);
    return Streams.stream(attrParts)
        .filter(attrPart -> attrPart.getName().equals(attrName))
        .findFirst()
        .orElse(null);
  }

  void addChild(Attribute attrPart) {
    Iterable attrParts = getEntities(CHILDREN, Attribute.class);
    set(CHILDREN, concat(attrParts, singletonList(attrPart)));
  }

  void removeChild(Attribute attrPart) {
    Iterable attrParts = getEntities(CHILDREN, Attribute.class);
    set(
        CHILDREN,
        Streams.stream(attrParts)
            .filter(attr -> !attr.getName().equals(attrPart.getName()))
            .collect(toList()));
  }

  /**
   * Get all tags for this attribute
   *
   * @return attribute tags
   */
  public Iterable getTags() {
    return getEntities(TAGS, Tag.class);
  }

  /**
   * Set tags for this attribute
   *
   * @param tags attribute tags
   * @return this entity
   */
  public Attribute setTags(Iterable tags) {
    set(TAGS, tags);
    return this;
  }

  /**
   * Add a tag for this attribute
   *
   * @param tag attribute tag
   */
  public void addTag(Tag tag) {
    set(TAGS, concat(getTags(), singletonList(tag)));
  }

  /**
   * Add a tag for this attribute
   *
   * @param tag attribute tag
   */
  public void removeTag(Tag tag) {
    Iterable tags = getTags();
    removeAll(tags, singletonList(tag));
    set(TAGS, tag);
  }

  public void setDefaultValues() {
    setDataType(STRING);
    setNillable(true);
    setAuto(false);
    setVisible(true);
    setAggregatable(false);
    setReadOnly(false);
    setUnique(false);
  }

  private static String toEnumOptionsString(List enumOptions) {
    return !enumOptions.isEmpty() ? enumOptions.stream().collect(joining(",")) : null;
  }

  private AttributeType getCachedDataType() {
    if (cachedDataType == null) {
      String dataTypeStr = getString(TYPE);
      cachedDataType = dataTypeStr != null ? AttributeType.toEnum(dataTypeStr) : null;
    }
    return cachedDataType;
  }

  private void invalidateCachedDataType() {
    cachedDataType = null;
  }

  @Override
  public String toString() {
    return "Attribute{" + "name=" + getName() + " id=" + getIdValue() + '}';
  }

  /**
   * For a reference type attribute, searches the referenced entity for its inversed attribute. This
   * is the one-to-many attribute that has "mappedBy" set to this attribute. Returns null if this is
   * not a reference type attribute, or no inverse attribute exists.
   */
  public Attribute getInversedBy() {
    if (hasRefEntity()) {
      EntityType entityType = getEntity();
      EntityType refEntityType = getRefEntity();
      return Streams.stream(refEntityType.getAtomicAttributes())
          .filter(Attribute::isMappedBy)
          .filter(
              attr ->
                  getName().equals(attr.getMappedBy().getName())
                      && entityType.getId().equals(attr.getRefEntity().getId()))
          .findFirst()
          .orElse(null);
    } else {
      return null;
    }
  }

  /**
   * Determines if this is a reference type attribute whose refEntity has an attribute that has
   * mappedBy set to this attribute.
   */
  public boolean isInversedBy() {
    return getInversedBy() != null;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy