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

org.openmetadata.service.util.EntityUtil Maven / Gradle / Ivy

There is a newer version: 1.5.11
Show newest version
/*
 *  Copyright 2021 Collate
 *  Licensed 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
 *  http://www.apache.org/licenses/LICENSE-2.0
 *  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.openmetadata.service.util;

import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.schema.type.Include.ALL;
import static org.openmetadata.schema.type.Include.NON_DELETED;

import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.BiPredicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import javax.ws.rs.WebApplicationException;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.openmetadata.common.utils.CommonUtil;
import org.openmetadata.schema.EntityInterface;
import org.openmetadata.schema.FieldInterface;
import org.openmetadata.schema.api.data.TermReference;
import org.openmetadata.schema.entity.classification.Tag;
import org.openmetadata.schema.entity.data.GlossaryTerm;
import org.openmetadata.schema.entity.data.SearchIndex;
import org.openmetadata.schema.entity.data.Table;
import org.openmetadata.schema.entity.data.Topic;
import org.openmetadata.schema.entity.policies.accessControl.Rule;
import org.openmetadata.schema.entity.type.CustomProperty;
import org.openmetadata.schema.type.*;
import org.openmetadata.schema.type.TagLabel.TagSource;
import org.openmetadata.service.Entity;
import org.openmetadata.service.exception.CatalogExceptionMessage;
import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.jdbi3.CollectionDAO.EntityRelationshipRecord;
import org.openmetadata.service.jdbi3.CollectionDAO.EntityVersionPair;
import org.openmetadata.service.jdbi3.CollectionDAO.UsageDAO;
import org.openmetadata.service.resources.feeds.MessageParser.EntityLink;
import org.openmetadata.service.security.policyevaluator.ResourceContext;

@Slf4j
public final class EntityUtil {

  //
  // Comparators used for sorting list based on the given type
  //
  public static final Comparator compareEntityReference =
      Comparator.comparing(EntityReference::getName);
  public static final Comparator compareEntityReferenceById =
      Comparator.comparing(EntityReference::getId).thenComparing(EntityReference::getType);
  public static final Comparator compareVersion =
      Comparator.comparing(EntityVersionPair::getVersion);
  public static final Comparator compareTagLabel =
      Comparator.comparing(TagLabel::getTagFQN);
  public static final Comparator compareFieldChange =
      Comparator.comparing(FieldChange::getName);
  public static final Comparator compareTableConstraint =
      Comparator.comparing(TableConstraint::getConstraintType);
  public static final Comparator compareChangeEvent =
      Comparator.comparing(ChangeEvent::getTimestamp);
  public static final Comparator compareGlossaryTerm =
      Comparator.comparing(GlossaryTerm::getName);
  public static final Comparator compareCustomProperty =
      Comparator.comparing(CustomProperty::getName);

  //
  // Matchers used for matching two items in a list
  //
  public static final BiPredicate objectMatch = Object::equals;

  public static final BiPredicate entityReferenceMatch =
      (ref1, ref2) -> ref1.getId().equals(ref2.getId()) && ref1.getType().equals(ref2.getType());

  public static final BiPredicate tagLabelMatch =
      (tag1, tag2) ->
          tag1.getTagFQN().equals(tag2.getTagFQN()) && tag1.getSource().equals(tag2.getSource());

  public static final BiPredicate taskMatch =
      (task1, task2) -> task1.getName().equals(task2.getName());

  public static final BiPredicate stringMatch = String::equals;

  public static final BiPredicate columnMatch =
      (column1, column2) ->
          column1.getName().equalsIgnoreCase(column2.getName())
              && column1.getDataType() == column2.getDataType()
              && column1.getArrayDataType() == column2.getArrayDataType();

  public static final BiPredicate columnNameMatch =
      (column1, column2) -> column1.getName().equalsIgnoreCase(column2.getName());

  public static final BiPredicate tableConstraintMatch =
      (constraint1, constraint2) ->
          constraint1.getConstraintType() == constraint2.getConstraintType()
              && constraint1.getColumns().equals(constraint2.getColumns())
              && ((constraint1.getReferredColumns() == null
                      && constraint2.getReferredColumns() == null)
                  || (constraint1.getReferredColumns().equals(constraint2.getReferredColumns())));

  public static final BiPredicate mlFeatureMatch = MlFeature::equals;
  public static final BiPredicate mlHyperParameterMatch =
      MlHyperParameter::equals;

  public static final BiPredicate glossaryTermMatch =
      (filter1, filter2) -> filter1.getFullyQualifiedName().equals(filter2.getFullyQualifiedName());

  public static final BiPredicate
      containerFileFormatMatch = Enum::equals;
  public static final BiPredicate termReferenceMatch =
      (ref1, ref2) ->
          ref1.getName().equals(ref2.getName()) && ref1.getEndpoint().equals(ref2.getEndpoint());

  public static final BiPredicate customFieldMatch =
      (ref1, ref2) ->
          ref1.getName().equals(ref2.getName())
              && entityReferenceMatch.test(ref1.getPropertyType(), ref2.getPropertyType());

  public static final BiPredicate ruleMatch =
      (ref1, ref2) -> ref1.getName().equals(ref2.getName());

  public static final BiPredicate schemaFieldMatch =
      (field1, field2) ->
          field1.getName().equalsIgnoreCase(field2.getName())
              && field1.getDataType() == field2.getDataType();

  public static final BiPredicate searchIndexFieldMatch =
      (field1, field2) ->
          field1.getName().equalsIgnoreCase(field2.getName())
              && field1.getDataType() == field2.getDataType();

  private EntityUtil() {}

  /** Validate that JSON payload can be turned into POJO object */
  public static  T validate(Object id, String json, Class clz)
      throws WebApplicationException {
    T entity = null;
    if (json != null) {
      entity = JsonUtils.readValue(json, clz);
    }
    if (entity == null) {
      throw EntityNotFoundException.byMessage(
          CatalogExceptionMessage.entityNotFound(clz.getSimpleName(), id.toString()));
    }
    return entity;
  }

  public static List populateEntityReferences(List list) {
    if (list != null) {
      for (EntityReference ref : list) {
        EntityReference ref2 = Entity.getEntityReference(ref, ALL);
        EntityUtil.copy(ref2, ref);
      }
      list.sort(compareEntityReference);
    }
    return list;
  }

  public static List getEntityReferences(List list) {
    if (nullOrEmpty(list)) {
      return Collections.emptyList();
    }
    List refs = new ArrayList<>();
    for (EntityRelationshipRecord ref : list) {
      refs.add(Entity.getEntityReferenceById(ref.getType(), ref.getId(), ALL));
    }
    refs.sort(compareEntityReference);
    return refs;
  }

  public static List populateEntityReferencesById(
      List list, String entityType) {
    List refs = toEntityReferences(list, entityType);
    return populateEntityReferences(refs);
  }

  public static EntityReference validateEntityLink(EntityLink entityLink) {
    String entityType = entityLink.getEntityType();
    String fqn = entityLink.getEntityFQN();
    return Entity.getEntityReferenceByName(entityType, fqn, ALL);
  }

  public static UsageDetails getLatestUsage(UsageDAO usageDAO, UUID entityId) {
    LOG.debug("Getting latest usage for {}", entityId);
    UsageDetails details = usageDAO.getLatestUsage(entityId.toString());
    if (details == null) {
      LOG.debug("Usage details not found. Sending default usage");
      UsageStats stats = new UsageStats().withCount(0).withPercentileRank(0.0);
      details =
          new UsageDetails()
              .withDailyStats(stats)
              .withWeeklyStats(stats)
              .withMonthlyStats(stats)
              .withDate(RestUtil.DATE_FORMAT.format(new Date()));
    }
    return details;
  }

  /** Merge two sets of tags */
  public static void mergeTags(List mergeTo, List mergeFrom) {
    if (nullOrEmpty(mergeFrom)) {
      return;
    }
    for (TagLabel fromTag : mergeFrom) {
      TagLabel tag =
          mergeTo.stream().filter(t -> tagLabelMatch.test(t, fromTag)).findAny().orElse(null);
      if (tag == null) { // The tag does not exist in the mergeTo list. Add it.
        mergeTo.add(fromTag);
      }
    }
  }

  public static List getJsonDataResources(String path) throws IOException {
    return CommonUtil.getResources(Pattern.compile(path));
  }

  public static  List toFQNs(List entities) {
    if (entities == null) {
      return Collections.emptyList();
    }
    List entityReferences = new ArrayList<>();
    for (T entity : entities) {
      entityReferences.add(entity.getFullyQualifiedName());
    }
    return entityReferences;
  }

  public static List strToIds(List list) {
    return list.stream().map(UUID::fromString).collect(Collectors.toList());
  }

  public static List toEntityReferences(List ids, String entityType) {
    if (ids == null) {
      return null;
    }
    return ids.stream()
        .map(id -> new EntityReference().withId(id).withType(entityType))
        .collect(Collectors.toList());
  }

  public static List refToIds(List refs) {
    if (refs == null) {
      return null;
    }
    return refs.stream().map(EntityReference::getId).collect(Collectors.toList());
  }

  public static  boolean isDescriptionRequired(Class clz) {
    // Returns true if description field in entity is required
    try {
      java.lang.reflect.Field description = clz.getDeclaredField(Entity.FIELD_DESCRIPTION);
      return description.getAnnotation(NotNull.class) != null;
    } catch (NoSuchFieldException e) {
      return false;
    }
  }

  public static class Fields {
    public static final Fields EMPTY_FIELDS = new Fields(Collections.emptySet());
    @Getter private final Set fieldList;

    public Fields(Set fieldList) {
      this.fieldList = fieldList;
    }

    public Fields(Set allowedFields, String fieldsParam) {
      if (nullOrEmpty(fieldsParam)) {
        fieldList = new HashSet<>();
        return;
      }
      fieldList = new HashSet<>(Arrays.asList(fieldsParam.replace(" ", "").split(",")));
      for (String field : fieldList) {
        if (!allowedFields.contains(field)) {
          throw new IllegalArgumentException(CatalogExceptionMessage.invalidField(field));
        }
      }
    }

    public Fields(Set allowedFields, Set fieldsParam) {
      if (CommonUtil.nullOrEmpty(fieldsParam)) {
        fieldList = new HashSet<>();
        return;
      }
      for (String field : fieldsParam) {
        if (!allowedFields.contains(field)) {
          throw new IllegalArgumentException(CatalogExceptionMessage.invalidField(field));
        }
      }
      fieldList = new HashSet<>(fieldsParam);
    }

    public void addField(Set allowedFields, String field) {
      if (!allowedFields.contains(field)) {
        throw new IllegalArgumentException(CatalogExceptionMessage.invalidField(field));
      }
      fieldList.add(field);
    }

    @Override
    public String toString() {
      return String.join(",", fieldList);
    }

    public boolean contains(String field) {
      return fieldList.contains(field);
    }
  }

  /**
   * Entity version extension name formed by entityType.version.versionNumber. Example -
   * `table.version.0.1`
   */
  public static String getVersionExtension(String entityType, Double version) {
    return String.format("%s.%s", getVersionExtensionPrefix(entityType), version.toString());
  }

  /**
   * Entity version extension name prefix formed by `entityType.version`. Example - `table.version`
   */
  public static String getVersionExtensionPrefix(String entityType) {
    return String.format("%s.%s", entityType, "version");
  }

  public static Double getVersion(String extension) {
    String[] s = extension.split("\\.");
    String versionString = s[2] + "." + s[3];
    return Double.valueOf(versionString);
  }

  public static String getLocalColumnName(String tableFqn, String columnFqn) {
    // Return for fqn=service:database:table:c1 -> c1
    // Return for fqn=service:database:table:c1:c2 -> c1:c2 (note different from just the local name
    // of the column c2)
    return columnFqn.replace(tableFqn + Entity.SEPARATOR, "");
  }

  public static String getFieldName(String... strings) {
    return String.join(Entity.SEPARATOR, strings);
  }

  /** Return column field name of format "columns".columnName.columnFieldName */
  public static String getColumnField(Column column, String columnField) {
    // Remove table FQN from column FQN to get the local name
    String localColumnName = column.getName();
    return columnField == null
        ? FullyQualifiedName.build("columns", localColumnName)
        : FullyQualifiedName.build("columns", localColumnName, columnField);
  }

  /** Return schema field name of format "schemaFields".fieldName.fieldName */
  public static String getSchemaField(Topic topic, Field field, String fieldName) {
    // Remove topic FQN from schemaField FQN to get the local name
    String localFieldName =
        EntityUtil.getLocalColumnName(topic.getFullyQualifiedName(), field.getFullyQualifiedName());
    return fieldName == null
        ? FullyQualifiedName.build("schemaFields", localFieldName)
        : FullyQualifiedName.build("schemaFields", localFieldName, fieldName);
  }

  /** Return searchIndex field name of format "fields".fieldName.fieldName */
  public static String getSearchIndexField(
      SearchIndex searchIndex, SearchIndexField field, String fieldName) {
    // Remove topic FQN from schemaField FQN to get the local name
    String localFieldName =
        EntityUtil.getLocalColumnName(
            searchIndex.getFullyQualifiedName(), field.getFullyQualifiedName());
    return fieldName == null
        ? FullyQualifiedName.build("fields", localFieldName)
        : FullyQualifiedName.build("fields", localFieldName, fieldName);
  }

  /** Return rule field name of format "rules".ruleName.ruleFieldName */
  public static String getRuleField(Rule rule, String ruleField) {
    return ruleField == null
        ? FullyQualifiedName.build("rules", rule.getName())
        : FullyQualifiedName.build("rules", rule.getName(), ruleField);
  }

  /** Return customer property field name of format "customProperties".propertyName */
  public static String getCustomField(CustomProperty property, String propertyFieldName) {
    return propertyFieldName == null
        ? FullyQualifiedName.build("customProperties", property.getName())
        : FullyQualifiedName.build("customProperties", property.getName(), propertyFieldName);
  }

  /** Return extension field name of format "extension".fieldName */
  public static String getExtensionField(String key) {
    return FullyQualifiedName.build("extension", key);
  }

  public static Double previousVersion(Double version) {
    return Math.round((version - 0.1) * 10.0) / 10.0;
  }

  public static Double nextVersion(Double version) {
    return Math.round((version + 0.1) * 10.0) / 10.0;
  }

  public static Double nextMajorVersion(Double version) {
    return Math.round((version + 1.0) * 10.0) / 10.0;
  }

  public static void copy(EntityReference from, EntityReference to) {
    to.withType(from.getType())
        .withId(from.getId())
        .withName(from.getName())
        .withDisplayName(from.getDisplayName())
        .withFullyQualifiedName(from.getFullyQualifiedName())
        .withDeleted(from.getDeleted());
  }

  public static List toTagLabels(GlossaryTerm... terms) {
    List list = new ArrayList<>();
    for (GlossaryTerm term : terms) {
      list.add(toTagLabel(term));
    }
    return list;
  }

  public static List toTagLabels(Tag... tags) {
    List list = new ArrayList<>();
    for (Tag tag : tags) {
      list.add(toTagLabel(tag));
    }
    return list;
  }

  public static TagLabel toTagLabel(GlossaryTerm term) {
    return new TagLabel()
        .withName(term.getName())
        .withDisplayName(term.getDisplayName())
        .withDescription(term.getDescription())
        .withStyle(term.getStyle())
        .withTagFQN(term.getFullyQualifiedName())
        .withDescription(term.getDescription())
        .withSource(TagSource.GLOSSARY);
  }

  public static TagLabel toTagLabel(Tag tag) {
    return new TagLabel()
        .withName(tag.getName())
        .withDisplayName(tag.getDisplayName())
        .withDescription(tag.getDescription())
        .withStyle(tag.getStyle())
        .withTagFQN(tag.getFullyQualifiedName())
        .withDescription(tag.getDescription())
        .withSource(TagSource.CLASSIFICATION);
  }

  public static String addField(String fields, String newField) {
    fields = fields == null ? "" : fields;
    return fields.isEmpty() ? newField : fields + ", " + newField;
  }

  public static void fieldAdded(ChangeDescription change, String fieldName, Object newValue) {
    if (change != null) {
      change.getFieldsAdded().add(new FieldChange().withName(fieldName).withNewValue(newValue));
    }
  }

  public static void fieldDeleted(ChangeDescription change, String fieldName, Object oldValue) {
    if (change != null) {
      change.getFieldsDeleted().add(new FieldChange().withName(fieldName).withOldValue(oldValue));
    }
  }

  public static void fieldUpdated(
      ChangeDescription change, String fieldName, Object oldValue, Object newValue) {
    if (change != null) {
      FieldChange fieldChange =
          new FieldChange().withName(fieldName).withOldValue(oldValue).withNewValue(newValue);
      change.getFieldsUpdated().add(fieldChange);
    }
  }

  public static MetadataOperation createOrUpdateOperation(ResourceContext resourceContext) {
    return resourceContext.getEntity() == null
        ? MetadataOperation.CREATE
        : MetadataOperation.EDIT_ALL;
  }

  public static UUID getId(EntityReference ref) {
    return ref == null ? null : ref.getId();
  }

  public static String getFqn(EntityReference ref) {
    return ref == null ? null : ref.getFullyQualifiedName();
  }

  public static String getFqn(EntityInterface entity) {
    return entity == null ? null : entity.getFullyQualifiedName();
  }

  public static List getFqns(List refs) {
    if (nullOrEmpty(refs)) {
      return null;
    }
    List fqns = new ArrayList<>();
    for (EntityReference ref : refs) {
      fqns.add(getFqn(ref));
    }
    return fqns;
  }

  public static EntityReference getEntityReference(EntityInterface entity) {
    return entity == null ? null : entity.getEntityReference();
  }

  public static EntityReference getEntityReference(String entityType, String fqn) {
    return fqn == null
        ? null
        : new EntityReference().withType(entityType).withFullyQualifiedName(fqn);
  }

  public static List getEntityReferences(String entityType, List fqns) {
    if (nullOrEmpty(fqns)) {
      return null;
    }
    List references = new ArrayList<>();
    for (String fqn : fqns) {
      references.add(Entity.getEntityReferenceByName(entityType, fqn, NON_DELETED));
    }
    return references;
  }

  public static Column getColumn(Table table, String columnName) {
    return table.getColumns().stream()
        .filter(c -> c.getName().equals(columnName))
        .findFirst()
        .orElse(null);
  }

  public static void sortByFQN(List entities) {
    // Sort entities by fullyQualifiedName
    entities.sort(Comparator.comparing(EntityInterface::getFullyQualifiedName));
  }

  /**
   * This method is used to populate the entity with all details of EntityReference Users/Tools can
   * send minimum details required to set relationship as id, type are the only required fields in
   * entity reference, whereas we need to send fully populated object such that ElasticSearch index
   * has all the details.
   */
  public static List getEntityReferences(
      List entities, Include include) {
    if (nullOrEmpty(entities)) {
      return Collections.emptyList();
    }
    List refs = new ArrayList<>();
    for (EntityReference entityReference : entities) {
      EntityReference entityRef = Entity.getEntityReference(entityReference, include);
      refs.add(entityRef);
    }
    return refs;
  }

  public static void validateProfileSample(String profileSampleType, double profileSampleValue) {
    if (profileSampleType.equals("PERCENTAGE")
        && (profileSampleValue < 0 || profileSampleValue > 100.0)) {
      throw new IllegalArgumentException("Profile sample value must be between 0 and 100");
    }
  }

  @SneakyThrows
  public static String hash(String input) {
    if (input != null) {
      byte[] checksum = MessageDigest.getInstance("MD5").digest(input.getBytes());
      return Hex.encodeHexString(checksum);
    }
    return null;
  }

  public static boolean isDescriptionTask(TaskType taskType) {
    return taskType == TaskType.RequestDescription || taskType == TaskType.UpdateDescription;
  }

  public static boolean isTagTask(TaskType taskType) {
    return taskType == TaskType.RequestTag || taskType == TaskType.UpdateTag;
  }

  public static boolean isApprovalTask(TaskType taskType) {
    return taskType == TaskType.RequestApproval;
  }

  public static boolean isTestCaseFailureResolutionTask(TaskType taskType) {
    return taskType == TaskType.RequestTestCaseFailureResolution;
  }

  public static Column findColumn(List columns, String columnName) {
    return columns.stream()
        .filter(c -> c.getName().equals(columnName))
        .findFirst()
        .orElseThrow(
            () ->
                new IllegalArgumentException(
                    CatalogExceptionMessage.invalidFieldName("column", columnName)));
  }

  public static  List getFlattenedEntityField(List fields) {
    List flattenedFields = new ArrayList<>();
    fields.forEach(column -> flattenEntityField(column, flattenedFields));
    return flattenedFields;
  }

  private static  void flattenEntityField(
      T field, List flattenedFields) {
    flattenedFields.add(field);
    List children = (List) field.getChildren();
    for (T child : listOrEmpty(children)) {
      flattenEntityField(child, flattenedFields);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy