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

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

There is a newer version: 1.6.2
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 com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jsonpatch.JsonPatchException;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.json.JsonPatch;
import javax.json.JsonValue;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.schema.EntityInterface;
import org.openmetadata.schema.type.MetadataOperation;
import org.openmetadata.schema.type.TagLabel;
import org.openmetadata.service.ResourceRegistry;
import org.openmetadata.service.security.policyevaluator.ResourceContextInterface;

@Slf4j
public class JsonPatchUtils {
  private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

  private JsonPatchUtils() {}

  public static Set getMetadataOperations(
      ResourceContextInterface resourceContextInterface, JsonPatch jsonPatch) {
    Set uniqueOperations = new HashSet<>();
    EntityInterface originalEntity = resourceContextInterface.getEntity();
    boolean tagsAffected = false;

    for (JsonValue jsonValue : jsonPatch.toJsonArray()) {
      MetadataOperation metadataOperation = getMetadataOperation(jsonValue);
      if (metadataOperation.equals(MetadataOperation.EDIT_ALL)) {
        return Collections.singleton(MetadataOperation.EDIT_ALL);
      }
      if (metadataOperation.equals(MetadataOperation.EDIT_TAGS)) {
        tagsAffected = true;
      } else {
        uniqueOperations.add(metadataOperation);
      }
    }
    if (tagsAffected) {
      try {
        JsonNode originalEntityJson = JsonUtils.pojoToJsonNode(originalEntity);
        JsonNode patchedEntityJson = applyPatch(originalEntityJson, jsonPatch);
        Set originalTags = extractTags(originalEntityJson);
        Set patchedTags = extractTags(patchedEntityJson);
        Set addedTags = new HashSet<>(patchedTags);
        addedTags.removeAll(originalTags);

        Set removedTags = new HashSet<>(originalTags);
        removedTags.removeAll(patchedTags);

        for (TagLabel addedTag : addedTags) {
          uniqueOperations.add(mapTagToOperation(addedTag));
        }
        for (TagLabel removedTag : removedTags) {
          uniqueOperations.add(mapTagToOperation(removedTag));
        }
        LOG.debug("Returning patch operations {}", uniqueOperations);
      } catch (JsonPatchException | IOException e) {
        LOG.error("Failed to process JSON Patch for MetadataOperations", e);
        throw new RuntimeException("Error processing JSON Patch", e);
      }
    }

    return uniqueOperations;
  }

  private static JsonNode applyPatch(JsonNode targetJson, JsonPatch patch)
      throws JsonPatchException, IOException {
    String patchString = patch.toString();
    JsonNode patchNode = OBJECT_MAPPER.readTree(patchString);
    com.github.fge.jsonpatch.JsonPatch jacksonPatch =
        com.github.fge.jsonpatch.JsonPatch.fromJson(patchNode);
    return jacksonPatch.apply(targetJson);
  }

  private static Set extractTags(JsonNode entityJson) {
    Set tags = new HashSet<>();
    traverseForTags(entityJson, tags);
    return tags;
  }

  private static void traverseForTags(JsonNode node, Set tags) {
    if (node == null || node.isNull()) {
      return;
    }

    if (node.isObject()) {
      if (node.has("tags") && node.get("tags").isArray()) {
        for (JsonNode tagNode : node.get("tags")) {
          try {
            TagLabel tag = OBJECT_MAPPER.treeToValue(tagNode, TagLabel.class);
            tags.add(tag);
          } catch (JsonProcessingException e) {
            LOG.warn("Failed to parse TagLabel from node: {}", tagNode, e);
          }
        }
      }

      Iterator> fields = node.fields();
      while (fields.hasNext()) {
        Map.Entry entry = fields.next();
        traverseForTags(entry.getValue(), tags);
      }
    } else if (node.isArray()) {
      for (JsonNode arrayItem : node) {
        traverseForTags(arrayItem, tags);
      }
    }
  }

  private static MetadataOperation mapTagToOperation(TagLabel tag) {
    if (tag == null) {
      return null;
    }
    String source = tag.getSource().value();
    String tagFQN = tag.getTagFQN();
    if (isTierClassification(tagFQN)) {
      return MetadataOperation.EDIT_TIER;
    } else if ("Classification".equalsIgnoreCase(source)) {
      return MetadataOperation.EDIT_TAGS;
    } else if ("Glossary".equalsIgnoreCase(source)) {
      return MetadataOperation.EDIT_GLOSSARY_TERMS;
    }
    // Default to EDIT_ALL if the tag is not recognized
    return MetadataOperation.EDIT_ALL;
  }

  private static boolean isTierClassification(String tagFQN) {
    return tagFQN != null && tagFQN.startsWith("Tier.");
  }

  public static MetadataOperation getMetadataOperation(Object jsonPatchObject) {
    Map jsonPatchMap = JsonUtils.getMap(jsonPatchObject);
    String path = jsonPatchMap.get("path").toString(); // Get "path" node - "/defaultRoles/0"
    return getMetadataOperation(path);
  }

  public static MetadataOperation getMetadataOperation(String path) {
    String[] paths = path.contains("/") ? path.split("/") : new String[] {path};
    for (String p : paths) {
      if (ResourceRegistry.hasEditOperation(p)) {
        return ResourceRegistry.getEditOperation(p);
      }
    }
    LOG.warn("Failed to find specific operation for patch path {}", path);
    return MetadataOperation
        .EDIT_ALL; // If path is not mapped to any edit field, then return edit all
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy