content: /**
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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
   * <p>
   * http://www.apache.org/licenses/LICENSE-2.0
   * <p>
   * 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.apache.atlas.repository.store.graph.v2;
  
  import org.apache.atlas.exception.AtlasBaseException;
  import org.apache.atlas.model.instance.AtlasClassification;
  import org.apache.atlas.model.instance.AtlasEntity;
  import org.apache.atlas.repository.graphdb.AtlasVertex;
  import org.apache.atlas.type.AtlasEntityType;
  import org.apache.atlas.type.AtlasStructType.AtlasAttribute;
  import org.apache.atlas.type.AtlasTypeRegistry;
  import org.apache.atlas.utils.AtlasEntityUtil;
  import org.apache.commons.collections.MapUtils;
  
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  
  import static org.apache.atlas.repository.graph.GraphHelper.getCustomAttributes;
  
  public class AtlasEntityComparator {
      private final AtlasTypeRegistry    typeRegistry;
      private final EntityGraphRetriever entityRetriever;
      private final Map<String, String>  guidRefMap;
      private final boolean              skipClassificationCompare;
      private final boolean              skipBusinessAttributeCompare;
  
      public AtlasEntityComparator(AtlasTypeRegistry typeRegistry, EntityGraphRetriever entityRetriever, Map<String, String> guidRefMap, boolean skipClassificationCompare, boolean skipBusinessAttributeCompare) {
          this.typeRegistry                 = typeRegistry;
          this.entityRetriever              = entityRetriever;
          this.guidRefMap                   = guidRefMap;
          this.skipClassificationCompare    = skipClassificationCompare;
          this.skipBusinessAttributeCompare = skipBusinessAttributeCompare;
      }
  
      public AtlasEntityDiffResult getDiffResult(AtlasEntity updatedEntity, AtlasEntity storedEntity, boolean findOnlyFirstDiff) throws AtlasBaseException {
          return getDiffResult(updatedEntity, storedEntity, null, findOnlyFirstDiff);
      }
  
      public AtlasEntityDiffResult getDiffResult(AtlasEntity updatedEntity, AtlasVertex storedVertex, boolean findOnlyFirstDiff) throws AtlasBaseException {
          return getDiffResult(updatedEntity, null, storedVertex, findOnlyFirstDiff);
      }
  
      private AtlasEntityDiffResult getDiffResult(AtlasEntity updatedEntity, AtlasEntity storedEntity, AtlasVertex storedVertex, boolean findOnlyFirstDiff) throws AtlasBaseException {
          AtlasEntity                              diffEntity                       = new AtlasEntity(updatedEntity.getTypeName());
          AtlasEntityType                          entityType                       = typeRegistry.getEntityTypeByName(updatedEntity.getTypeName());
          Map<String, AtlasAttribute>              entityTypeAttributes             = entityType.getAllAttributes();
          Map<String, Map<String, AtlasAttribute>> entityTypeRelationshipAttributes = entityType.getRelationshipAttributes();
  
          int     sectionsWithDiff                = 0;
          boolean hasDiffInAttributes             = false;
          boolean hasDiffInRelationshipAttributes = false;
          boolean hasDiffInCustomAttributes       = false;
          boolean hasDiffInBusinessAttributes     = false;
  
          diffEntity.setGuid(updatedEntity.getGuid());
  
          if (MapUtils.isNotEmpty(updatedEntity.getAttributes())) { // check for attribute value change
              for (Map.Entry<String, Object> entry : updatedEntity.getAttributes().entrySet()) {
                  String         attrName  = entry.getKey();
                  AtlasAttribute attribute = entityTypeAttributes.get(attrName);
  
                  if (attribute == null) { // no such attribute
                      continue;
                  }
  
                  Object newVal  = entry.getValue();
                  Object currVal = (storedEntity != null) ? storedEntity.getAttribute(attrName) : entityRetriever.getEntityAttribute(storedVertex, attribute);
  
                  if (!attribute.getAttributeType().areEqualValues(currVal, newVal, guidRefMap)) {
                      hasDiffInAttributes = true;
  
                      diffEntity.setAttribute(attrName, newVal);
  
                      if (findOnlyFirstDiff) {
                          return new AtlasEntityDiffResult(diffEntity, true, false, false);
                      }
                  }
              }
  
              if (hasDiffInAttributes) {
                  sectionsWithDiff++;
              }
          }
  
          if (MapUtils.isNotEmpty(updatedEntity.getRelationshipAttributes())) { // check for relationship-attribute value change
              for (Map.Entry<String, Object> entry : updatedEntity.getRelationshipAttributes().entrySet()) {
                  String attrName = entry.getKey();
  
                  if (!entityTypeRelationshipAttributes.containsKey(attrName)) {  // no such attribute
                      continue;
                  }
  
                  Object         newVal           = entry.getValue();
                  String         relationshipType = AtlasEntityUtil.getRelationshipType(newVal);
                  AtlasAttribute attribute        = entityType.getRelationshipAttribute(attrName, relationshipType);
                  Object         currVal          = (storedEntity != null) ? storedEntity.getRelationshipAttribute(attrName) : entityRetriever.getEntityAttribute(storedVertex, attribute);
  
                  if (!attribute.getAttributeType().areEqualValues(currVal, newVal, guidRefMap)) {
                      hasDiffInRelationshipAttributes = true;
  
                      diffEntity.setRelationshipAttribute(attrName, newVal);
  
                      if (findOnlyFirstDiff) {
                          return new AtlasEntityDiffResult(diffEntity, true, false, false);
                      }
                  }
              }
  
              if (hasDiffInRelationshipAttributes) {
                  sectionsWithDiff++;
              }
          }
  
          if (!skipClassificationCompare) {
              List<AtlasClassification> newVal  = updatedEntity.getClassifications();
              List<AtlasClassification> currVal = (storedEntity != null) ? storedEntity.getClassifications() : entityRetriever.getAllClassifications(storedVertex);
  
              if (!Objects.equals(currVal, newVal)) {
                  diffEntity.setClassifications(newVal);
  
                  sectionsWithDiff++;
  
                  if (findOnlyFirstDiff) {
                      return new AtlasEntityDiffResult(diffEntity, true, false, false);
                  }
              }
          }
  
          if (updatedEntity.getCustomAttributes() != null) {
              // event coming from hook does not have custom attributes, such events must not remove existing attributes
              // UI sends empty object in case of of intended removal.
              Map<String, String> newCustomAttributes  = updatedEntity.getCustomAttributes();
              Map<String, String> currCustomAttributes = (storedEntity != null) ? storedEntity.getCustomAttributes() : getCustomAttributes(storedVertex);
  
              if (!Objects.equals(currCustomAttributes, newCustomAttributes)) {
                  diffEntity.setCustomAttributes(newCustomAttributes);
  
                  hasDiffInCustomAttributes = true;
                  sectionsWithDiff++;
  
                  if (findOnlyFirstDiff && sectionsWithDiff > 1) {
                      return new AtlasEntityDiffResult(diffEntity, true, false, false);
                  }
              }
          }
  
          if (!skipBusinessAttributeCompare) {
              Map<String, Map<String, Object>> newBusinessMetadata  = updatedEntity.getBusinessAttributes();
              Map<String, Map<String, Object>> currBusinessMetadata = (storedEntity != null) ? storedEntity.getBusinessAttributes() : entityRetriever.getBusinessMetadata(storedVertex);
  
              if (!Objects.equals(currBusinessMetadata, newBusinessMetadata)) {
                  diffEntity.setBusinessAttributes(newBusinessMetadata);
  
                  hasDiffInBusinessAttributes = true;
  
                  sectionsWithDiff++;
  
                  if (findOnlyFirstDiff && sectionsWithDiff > 1) {
                      return new AtlasEntityDiffResult(diffEntity, true, false, false);
                  }
              }
          }
  
          return new AtlasEntityDiffResult(diffEntity, sectionsWithDiff > 0, sectionsWithDiff == 1 && hasDiffInCustomAttributes, sectionsWithDiff == 1 && hasDiffInBusinessAttributes);
      }
  
      public static class AtlasEntityDiffResult {
          private final AtlasEntity diffEntity;
          private final boolean     hasDifference;
          private final boolean     hasDifferenceOnlyInCustomAttributes;
          private final boolean     hasDifferenceOnlyInBusinessAttributes;
  
          AtlasEntityDiffResult(AtlasEntity diffEntity, boolean hasDifference, boolean hasDifferenceOnlyInCustomAttributes, boolean hasDifferenceOnlyInBusinessAttributes) {
              this.diffEntity                            = diffEntity;
              this.hasDifference                         = hasDifference;
              this.hasDifferenceOnlyInCustomAttributes   = hasDifferenceOnlyInCustomAttributes;
              this.hasDifferenceOnlyInBusinessAttributes = hasDifferenceOnlyInBusinessAttributes;
          }
  
          public AtlasEntity getDiffEntity() {
              return diffEntity;
          }
  
          public boolean hasDifference() {
              return hasDifference;
          }
  
          public boolean hasDifferenceOnlyInCustomAttributes() {
              return hasDifferenceOnlyInCustomAttributes;
          }
  
          public boolean hasDifferenceOnlyInBusinessAttributes() {
              return hasDifferenceOnlyInBusinessAttributes;
          }
      }
  }
  